├── .env.example
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
├── .gitignore
├── .prettierignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── app.js
├── cloudinary
└── index.js
├── controllers
├── booking.controller.js
├── garage.controller.js
├── slot.controller.js
└── user.controller.js
├── middleware
├── ip.middleware.js
├── isadmin.middleware.js
├── login.middleware.js
└── sanitizer.middleware.js
├── models
├── booking.model.js
├── garage.model.js
├── slot.model.js
├── transaction.model.js
└── user.model.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── css
│ ├── calendar.css
│ ├── cards.css
│ └── index.css
├── img
│ ├── coverimg.jpg
│ ├── coverpage.png
│ ├── favicon.ico
│ ├── logo-black.png
│ ├── logo-text-black.png
│ ├── logo-text-yellow.png
│ ├── logo-white.png
│ └── logo-yellow.png
└── js
│ ├── allgarage.js
│ ├── clustermap.js
│ ├── newbooking.js
│ ├── validateForms.js
│ └── viewgarage.js
├── routes
├── booking.route.js
├── garage.route.js
├── slot.route.js
└── user.route.js
├── services
├── booking.service.js
├── garage.service.js
├── slot.service.js
└── user.service.js
├── tailwind.config.js
├── utils
└── helper.js
└── views
├── bookings
└── newbooking.ejs
├── garages
├── allgarages.ejs
├── findgarage.ejs
├── foundgarage.ejs
├── newgarages.ejs
└── viewgarage.ejs
├── index.ejs
├── not-found.ejs
├── partials
├── alerts.ejs
├── footer.ejs
├── header.ejs
├── maps.ejs
└── navbar.ejs
├── slots
├── addslot.ejs
├── allslots.ejs
└── slot.ejs
└── users
├── addmoney.ejs
├── bookings.ejs
├── dashboard.ejs
├── login.ejs
├── mybooking.ejs
├── register.ejs
├── transactions.ejs
├── uploadimage.ejs
└── verify.ejs
/.env.example:
--------------------------------------------------------------------------------
1 | ENV=development
2 | PORT=[your_port]
3 | MONGO_URI=[your_mongo_uri]
4 | JWT_SECRET=[your_jwt_secret]
5 | EXPIRY=[your_jwt_expiry_time]
6 | SECRET=[your_secret_for_mongostore]
7 | CLOUDINARY_CLOUD_NAME=[your_cloudinary_cloud_name]
8 | CLOUDINARY_KEY=[your_cloudinary_key]
9 | CLOUDINARY_SECRET=[your_cloudinary_secret]
10 | MAPBOX_TOKEN=[your_mapbox_project]
11 | X_RAPIDAPI_HOST=[your_rapidapi_sendgrid_host]
12 | X_RAPIDAPI_KEY=[your_rapiapi_sendgrid_key]
13 | SENDGRID_EMAIL=[no-reply@your_domain.com]
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .env
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | views/*.ejs
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 4,
4 | "semi": true,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at soumyajitdatta123@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Developer Student Clubs KGEC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
</> with
♥ by
@codehackerone .
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | > Hasslefree way to book your parking space with easy cancellations and timeline extensions
27 |
28 | ---
29 | 
30 | ---
31 | ## Why Parkify?
32 |
33 | The application will make it easier for the drivers to find and pre-book their slots at the parking locations. The application will also save the frustration of the drivers who at times out of urgency park their cars on the street itself, which further adds to the traffic in the locality. With the use of the application, business professionals can focus on their daily tasks and not worry about their parking space.
34 |
35 |
36 | ## Built With
37 |
38 | This application is built on nodeJS, typed in javascript. Vanilla html-css-js is used to built the frontend.
39 |
40 | ## Requirements
41 |
42 | For development, you will only need Node.js and a node global package, npm, installed in your environement.
43 |
44 | ### Node
45 |
46 | - #### Node installation on Windows
47 |
48 | Just go on [official Node.js website](https://nodejs.org/) and download the installer.
49 | Also, be sure to have `git` available in your PATH, `npm` might need it (You can find git [here](https://git-scm.com/)).
50 |
51 | - #### Node installation on Ubuntu
52 |
53 | You can install nodejs and npm easily with apt install, just run the following commands.
54 |
55 | $ sudo apt install nodejs
56 | $ sudo apt install npm
57 |
58 | - #### Other Operating Systems
59 | You can find more information about the installation on the [official Node.js website](https://nodejs.org/) and the [official NPM website](https://npmjs.org/).
60 |
61 | If the installation was successful, you should be able to run the following command.
62 |
63 | $ node --version
64 | v8.11.3
65 |
66 | $ npm --version
67 | 6.1.0
68 |
69 | If you need to update `npm`, you can make it using `npm`! Cool right? After running the following command, just open again the command line and be happy.
70 |
71 | $ npm install npm -g
72 |
73 | ###
74 |
75 | ---
76 |
77 | ## Installation
78 |
79 | $ git clone https://github.com/codehackerone/parkify
80 | $ cd parkify
81 | $ npm install
82 |
83 | ### Configure environmental variables
84 |
85 | Create a `.env` file then edit it with your settings. You will need:
86 |
87 | - ENV=development
88 | - PORT=[your_port]
89 | - MONGO_URI=[your_mongo_uri]
90 | - JWT_SECRET=[your_jwt_secret]
91 | - EXPIRY=[your_jwt_expiry_time]
92 | - SECRET=[your_secret_for_mongostore]
93 | - CLOUDINARY_CLOUD_NAME=[your_cloudinary_cloud_name]
94 | - CLOUDINARY_KEY=[your_cloudinary_key]
95 | - CLOUDINARY_SECRET=[your_cloudinary_secret]
96 | - MAPBOX_TOKEN=[your_mapbox_project]
97 | - X_RAPIDAPI_HOST=[your_rapidapi_sendgrid_host]
98 | - X_RAPIDAPI_KEY=[your_rapiapi_sendgrid_key]
99 | - SENDGRID_EMAIL=[no-reply@your_domain.com]
100 |
101 | ### Running the project
102 |
103 | $ npm start
104 | or
105 | $ npx nodemon
106 |
107 | ---
108 |
109 | ## License
110 |
111 | ```Parkify``` is available under the MIT license. See the LICENSE file for more info.
112 |
113 | ## Contributing
114 |
115 |
116 | 1. Find an issue to work on from [here](https://github.com/codehackerone/parkify/issues)
117 | 2. Ask the owner/maintainer for permission to work on the issue.
118 | 3. Fork this repository. [For help, click here](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
119 | 4. Clone the forked repository in your local machine [For help, click here](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository)
120 | 5. Create a new branch [For help, click here](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)
121 | 6. Add and commit your changes to the new branch [For help, click here](https://stackoverflow.com/questions/14655816/how-to-commit-changes-to-another-pre-existent-branch#:~:text=First%2C%20checkout%20to%20your%20new,show%20up%20on%20the%20remote.)
122 | 7. Create a Pull Request, add proper description, screenshots, comments and ask a review from owner/maintainer [For help, click here](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)
123 | 8. The owner/developer will merge the Pull Request if it aligns with the practises we follow and is valid. One should not merge, and ask for a reviewer to merge it.
124 |
125 | Please read `CODE_OF_CONDUCT.md` for details on the code of conduct, and the process for submitting pull requests.
126 |
127 | ---
128 | This repo is owned/maintained by @codehackerone .
129 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | /* ------------ Imports ----------- */
2 |
3 | const express = require('express');
4 | const cors = require('cors');
5 | const mongoose = require('mongoose');
6 | const cookieParser = require('cookie-parser');
7 | const favicon = require('serve-favicon');
8 | const session = require('express-session');
9 | const MongoDBStore = require('connect-mongo')(session);
10 | const flash = require('express-flash');
11 | const helmet = require('helmet');
12 | const methodOverride = require('method-override');
13 |
14 | /* ------------ Configs ----------- */
15 |
16 | // initialize module to access .env variables
17 | require('dotenv').config();
18 |
19 | // access port from env
20 | const port = Number(process.env.PORT);
21 |
22 | // db connection variables
23 | const uri = String(process.env.MONGO_URI);
24 | const connectOptions = {
25 | useNewUrlParser: true, // used for parsing the uri
26 | useCreateIndex: true, // use mongoose's default index
27 | useUnifiedTopology: true, // use the newer topology engine
28 | useFindAndModify: false, // allow findOneAndUpdate()
29 | };
30 |
31 | /* ------------ MongoDB Setup ----------- */
32 |
33 | // initiate connection to mongodb
34 | mongoose
35 | .connect(uri, connectOptions)
36 | .then()
37 | .catch((err) => console.log('Error:' + err));
38 |
39 | // log message on connection success
40 | mongoose.connection.once('open', () =>
41 | console.log('Connected to MongoDB successfully...')
42 | );
43 |
44 | const app = express();
45 |
46 | const secret = process.env.SECRET;
47 |
48 | const store = new MongoDBStore({
49 | url: uri,
50 | secret,
51 | touchAfter: 24 * 60 * 60,
52 | });
53 |
54 | // session initialization
55 | store.on('error', function (e) {
56 | console.log('SESSION STORE ERROR', e);
57 | });
58 |
59 | const sessionConfig = {
60 | store, // session store
61 | name: 'session', // session name
62 | secret, // session secret
63 | resave: false, // don't save session if unmodified
64 | saveUninitialized: true, // don't create session until something stored
65 | cookie: {
66 | httpOnly: true, // don't let browser javascript access cookies
67 | expires: Date.now() + 1000 * 60 * 60 * 24 * 7, // expire in 7 days
68 | maxAge: 1000 * 60 * 60 * 24 * 7, // expire in 7 days
69 | },
70 | };
71 |
72 | app.use(cors()); // allow cross-origin requests
73 | app.use(express.json()); // parse the incoming requests with JSON payloads
74 | app.use(express.urlencoded({ extended: true })); // parse the incoming requests with URL encoded payloads
75 | app.use(cookieParser()); // parse the incoming requests with cookies
76 | app.use(session(sessionConfig)); // initialize session
77 | app.use(flash()); // initialize flash messages
78 | app.use(methodOverride('_method')); // override HTTP methods
79 | app.use(helmet({ contentSecurityPolicy: false }));
80 | app.use(favicon(__dirname + '/public/img/favicon.ico'));
81 |
82 | // use ejs template engine and allow serving static files
83 | app.use(express.static(__dirname + '/public'));
84 | app.set('view engine', 'ejs');
85 |
86 | //include routes from /
87 | app.get('/', (req, res) => {
88 | res.render('index');
89 | });
90 |
91 | //include routes from /users
92 | const userRouter = require('./routes/user.route');
93 | app.use('/users', userRouter);
94 |
95 | //include routes from /garages
96 | const garageRouter = require('./routes/garage.route');
97 | app.use('/garage', garageRouter);
98 |
99 | //include routes from /slots
100 | const slotRouter = require('./routes/slot.route');
101 | app.use('/slot', slotRouter);
102 |
103 | //include routes from /booking
104 | const bookingRouter = require('./routes/booking.route');
105 | app.use('/booking', bookingRouter);
106 |
107 | // handle all routes without endpoints
108 | app.get('*', (req, res) => {
109 | res.render('not-found');
110 | });
111 |
112 | // start the parkify server
113 | app.listen(port, () =>
114 | console.log(`Parkify running at http://localhost:${port}`)
115 | );
116 |
--------------------------------------------------------------------------------
/cloudinary/index.js:
--------------------------------------------------------------------------------
1 | const cloudinary = require('cloudinary').v2;
2 | const { CloudinaryStorage } = require('multer-storage-cloudinary');
3 |
4 | // cloudinary configuration
5 | cloudinary.config({
6 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
7 | api_key: process.env.CLOUDINARY_KEY,
8 | api_secret: process.env.CLOUDINARY_SECRET,
9 | });
10 |
11 | const storage = new CloudinaryStorage({
12 | cloudinary, // Cloudinary instance
13 | params: {
14 | folder: 'Parkify', // The name of the folder in cloudinary
15 | allowedFormats: ['jpeg', 'png', 'jpg'], // The formats you want to allow
16 | },
17 | });
18 |
19 | module.exports = {
20 | cloudinary,
21 | storage,
22 | };
23 |
--------------------------------------------------------------------------------
/controllers/booking.controller.js:
--------------------------------------------------------------------------------
1 | const bookingService = require('../services/booking.service');
2 | const slotService = require('../services/slot.service');
3 | const garageService = require('../services/garage.service');
4 |
5 | /* ------------ Controllers ----------- */
6 |
7 | //renderNewBooking... renders the new booking page
8 | const renderNewBooking = async (req, res) => {
9 | var slot_id = req.params.id;
10 | var slot = await slotService.FindSlot(slot_id);
11 | if (slot === null) {
12 | req.flash('err', 'Slot doesnt exist');
13 | res.redirect('/garage');
14 | } else {
15 | var garage = await garageService.FindGarage(slot.garage_id);
16 | if (!garage) {
17 | req.flash('err', 'Garage doesnt exist for the slot.');
18 | res.redirect('/garage');
19 | } else {
20 | res.render('bookings/newbooking', {
21 | userid: req.body.user_id,
22 | slot: slot,
23 | body: req.body,
24 | garage: garage,
25 | });
26 | }
27 | }
28 | };
29 |
30 | //newBooking... creates a new booking calculating the price, and redirects to the dashboard
31 | const newBooking = async (req, res) => {
32 | req.body.start_time = new Date(req.body.start_datetime).getTime() / 1000;
33 | req.body.end_time = new Date(req.body.end_datetime).getTime() / 1000;
34 | req.body.amount =
35 | (await bookingService.apiMoney(req.body.slot_id)) *
36 | ((req.body.end_time - req.body.start_time) / 60);
37 | try {
38 | const result = await bookingService.NewBooking(req.body);
39 | req.flash('success', 'Booking Created Successfully');
40 | res.redirect('/users/dashboard');
41 | } catch (err) {
42 | req.flash('err', err);
43 | res.redirect('/booking/new/' + req.body.slot_id);
44 | }
45 | };
46 |
47 | //renderBooking... renders the booking page
48 | const renderBooking = async (req, res) => {
49 | var booking_id = req.params.id;
50 | const booking = await bookingService.FindBooking(booking_id);
51 | if (!booking) {
52 | req.flash('err', 'Booking Not Found');
53 | res.redirect('/users/dashboard');
54 | } else {
55 | res.send(booking);
56 | }
57 | };
58 |
59 | //deleteBooking... deletes a booking and redirects to the dashboard
60 | const deleteBooking = async (req, res) => {
61 | var booking_id = req.params.id;
62 | try {
63 | await bookingService.DeleteBooking(booking_id);
64 | req.flash('success', 'Booking Deleted Successfully');
65 | res.redirect('/users/dashboard');
66 | } catch (err) {
67 | req.flash('err', 'Error :' + err);
68 | res.redirect('/users/dashboard');
69 | }
70 | };
71 |
72 | //cancelBooking... cancels a booking and redirects to the dashboard
73 | const cancelBooking = async (req, res) => {
74 | var id = req.params.id;
75 | try {
76 | await bookingService.cancelBooking(id);
77 | req.flash('success', 'Booking cancelled successfully');
78 | res.redirect('/users/dashboard');
79 | } catch (err) {
80 | req.flash('err', err);
81 | res.redirect('/users/dashboard');
82 | }
83 | };
84 |
85 | module.exports = {
86 | renderNewBooking,
87 | newBooking,
88 | renderBooking,
89 | deleteBooking,
90 | cancelBooking,
91 | };
92 |
--------------------------------------------------------------------------------
/controllers/garage.controller.js:
--------------------------------------------------------------------------------
1 | const garageService = require('../services/garage.service');
2 | const slotService = require('../services/slot.service');
3 | const mbxGeocoding = require('@mapbox/mapbox-sdk/services/geocoding'); // mapbox geocoding api
4 | const mapBoxToken = process.env.MAPBOX_TOKEN;
5 | const geocoder = mbxGeocoding({ accessToken: mapBoxToken });
6 | var axios = require('axios');
7 |
8 | /* ------------ Controllers ----------- */
9 |
10 | //renderAddGarage... renders the add garage page
11 | const renderAddGarage = (req, res) => {
12 | res.render('garages/newgarages', { body: req.body });
13 | };
14 |
15 | //addGarage... adds a new garage to the database
16 | const addGarage = async (req, res) => {
17 | try {
18 | // checking if request body has an image
19 | if (req.file) {
20 | var path = req.file.path;
21 | req.body.picture_url = path;
22 | }
23 | // geocding the location
24 | const geoData = await geocoder
25 | .forwardGeocode({
26 | query: req.body.location,
27 | limit: 1,
28 | })
29 | .send();
30 | // getting the coordinates
31 | req.body.geometry = geoData.body.features[0].geometry;
32 |
33 | // adding the garage
34 | const result = await garageService.AddGarage(req.body);
35 | req.flash('success', 'Garage Added Successfully');
36 | res.redirect('/garage/');
37 | } catch (err) {
38 | req.flash('err', 'Error :' + err);
39 | res.redirect('/garage/add');
40 | }
41 | };
42 |
43 | const renderGarage = async (req, res) => {
44 | var garage_id = req.params.id;
45 | const garage = await garageService.FindGarage(garage_id);
46 | if (!garage) {
47 | req.flash('err', 'Error :Garage Not Found!');
48 | res.redirect('/garage/');
49 | } else {
50 | res.render('garages/viewgarage', {
51 | garage: garage,
52 | maptoken: mapBoxToken,
53 | body: req.body,
54 | });
55 | }
56 | };
57 |
58 | //renderAllGarages... renders all the garages in the database
59 | const renderAllGarages = async (req, res) => {
60 | var garages = await garageService.AllGarages();
61 | res.render('garages/allgarages', {
62 | garages: garages,
63 | maptoken: mapBoxToken,
64 | body: req.body,
65 | });
66 | };
67 |
68 | //apiSlotInfo... returns the slots info of a garage
69 | const apiSlotInfo = async (req, res) => {
70 | var garage_id = req.params.id;
71 | var garage = await garageService.FindGarage(garage_id);
72 | if (!garage) return 'Garage Not Found';
73 | var slots = garage.slots;
74 | var arr_slot = [];
75 | var large = 0,
76 | medium = 0,
77 | small = 0;
78 | for (var slot of slots) {
79 | var slot_size = await slotService.FindSlot(slot);
80 | arr_slot.push(slot_size.type);
81 | }
82 | for (var size of arr_slot) {
83 | if (size === 'Large') large++;
84 | else if (size === 'Medium') medium++;
85 | else small++;
86 | }
87 | var str = `${large} Large, ${medium} Medium and ${small} Small Available.`;
88 | res.send(str);
89 | };
90 |
91 | //renderEditGarage... renders the edit garage page
92 | const deleteGarage = async (req, res) => {
93 | try {
94 | var id = req.params.id;
95 | await garageService.DeleteGarage(id);
96 | req.flash('success', 'Garage Deleted Successfully');
97 | res.redirect('/garage/');
98 | } catch (err) {
99 | req.flash('err', 'Error :' + err);
100 | res.redirect('/garage/');
101 | }
102 | };
103 |
104 | //renderFindGarage... renders the find garage page
105 | const renderfindgarage = async (req, res) => {
106 | res.render('garages/findgarage', { body: req.body });
107 | };
108 |
109 | //TODO: IN PROGRESS
110 | //rendergaragebyIp... renders the garages in progress
111 | const rendergaragebyip = async (req, res) => {
112 | var config = {
113 | method: 'get',
114 | url: 'http://ip-api.com/json/',
115 | };
116 | axios(config)
117 | .then(async function(response) {
118 | const geoData = await geocoder.forwardGeocode({
119 | query: response.data.city,
120 | limit: 1,
121 | })
122 | .send();
123 | var geometry = geoData.body.features[0].geometry;
124 | var garages = await garageService.AllGarages();
125 | var min_distance = 10000000.0;
126 | var dist = {};
127 | for (let garage of garages) {
128 | // getting the distance between the garage and the user
129 | var distance = garageService.DistanceCal(
130 | response.data.lat,
131 | response.data.lon,
132 | garage.geometry.coordinates[1],
133 | garage.geometry.coordinates[0]
134 | );
135 | if (distance <= min_distance) {
136 | dist = garage;
137 | min_distance = distance;
138 | }
139 | }
140 | if (min_distance > 1000.0) {
141 | req.flash(
142 | 'err',
143 | 'Sorry! No garages found within 1000.0 km radius.'
144 | );
145 | res.redirect('/garage/find');
146 | } else {
147 | res.render('garages/foundgarage', {
148 | body: req.body,
149 | by: 'IP',
150 | geometry: geometry,
151 | maptoken: mapBoxToken,
152 | garage: dist,
153 | min_distance: min_distance,
154 | });
155 | }
156 | })
157 | };
158 |
159 | //rendergaragebyloc... renders the garages in progress
160 | const rendergaragebyloc = async (req, res) => {
161 | try {
162 | if (!req.body.location) {
163 | req.flash('err', 'location not given');
164 | res.redirect('/garage/find');
165 | } else {
166 | // geocding the location taken from user
167 | const geoData = await geocoder
168 | .forwardGeocode({
169 | query: req.body.location,
170 | limit: 1,
171 | })
172 | .send();
173 | var geometry = geoData.body.features[0].geometry;;
174 | var garages = await garageService.AllGarages();
175 | var min_distance = 10000000.0;
176 | var dist = {};
177 | for (let garage of garages) {
178 | // getting the distance between the garage and the user
179 | var distance = garageService.DistanceCal(
180 | geometry.coordinates[1],
181 | geometry.coordinates[0],
182 | garage.geometry.coordinates[1],
183 | garage.geometry.coordinates[0]
184 | );
185 | if (distance <= min_distance) {
186 | dist = garage;
187 | min_distance = distance;
188 | }
189 | }
190 | if (min_distance > 1000.0) {
191 | req.flash(
192 | 'err',
193 | 'Sorry! No garages found within 1000.0 km radius.'
194 | );
195 | res.redirect('/garage/find');
196 | } else {
197 | res.render('garages/foundgarage', {
198 | body: req.body,
199 | by: 'Location',
200 | geometry: geometry,
201 | maptoken: mapBoxToken,
202 | garage: dist,
203 | min_distance: min_distance,
204 | });
205 | }
206 | }
207 | } catch (err) {
208 | req.flash('err', 'Err: ' + err);
209 | res.redirect('/garage/find');
210 | }
211 | };
212 |
213 | module.exports = {
214 | renderAddGarage,
215 | addGarage,
216 | renderGarage,
217 | renderAllGarages,
218 | apiSlotInfo,
219 | deleteGarage,
220 | renderfindgarage,
221 | rendergaragebyip,
222 | rendergaragebyloc,
223 | };
--------------------------------------------------------------------------------
/controllers/slot.controller.js:
--------------------------------------------------------------------------------
1 | const slotService = require('../services/slot.service');
2 | const garageService = require('../services/garage.service');
3 | const bookingService = require('../services/booking.service');
4 |
5 | /* ------------ Controllers ----------- */
6 |
7 | //renderAddSlot... renders add slot page
8 | const renderAddSlot = (req, res) => {
9 | var garage_id = req.params.id;
10 | res.render('slots/addslot', { garage_id: garage_id, body: req.body });
11 | };
12 |
13 | //addSlot... adds a new slot to the garage
14 | const addSlot = async (req, res) => {
15 | try {
16 | const result = await slotService.AddSlot(req.body);
17 | req.flash('success', 'Slot Added Successfully');
18 | res.redirect('/garage/' + result.garage_id);
19 | } catch (err) {
20 | req.flash('err', 'Error :' + err);
21 | res.redirect('/garage/' + req.body.garage_id);
22 | }
23 | };
24 |
25 | //renderSlot... renders the slot page
26 | const renderSlot = async (req, res) => {
27 | var slot_id = req.params.id;
28 | const slot = await slotService.FindSlot(slot_id);
29 | if (!slot) {
30 | req.flash('err', 'Slot Not Found');
31 | res.redirect('/garage/');
32 | } else {
33 | var bookings = await findBookings(slot._id);
34 | var garage = await garageService.FindGarage(slot.garage_id);
35 | if (!garage) {
36 | req.flash('err', 'Garage not Found');
37 | res.redirect('/garage');
38 | } else {
39 | res.render('slots/slot', {
40 | slot: slot,
41 | bookings: bookings,
42 | body: req.body,
43 | garage: garage,
44 | });
45 | }
46 | }
47 | };
48 |
49 | //deleteSlot... deletes a slot from the garage
50 | const deleteSlot = async (req, res) => {
51 | var slot_id = req.params.id;
52 | try {
53 | await slotService.DeleteSlot(slot_id);
54 | res.send('Slot Deleted Successfully.');
55 | } catch (err) {
56 | req.flash('err', 'Error :' + err);
57 | res.redirect('/garage/');
58 | }
59 | };
60 |
61 | //renderSlot... renders the slot page
62 | const renderSlots = async (req, res) => {
63 | var garage_id = req.params.id;
64 | var garage = await garageService.FindGarage(garage_id);
65 | if (!garage) {
66 | req.flash('err', 'Garage Not Found!');
67 | res.redirect('/garage/');
68 | } else {
69 | var slots = garage.slots;
70 | var arr_slot = [];
71 | for (var slot of slots) {
72 | var slot_det = await slotService.FindSlot(slot);
73 | arr_slot.push(slot_det);
74 | }
75 | res.render('slots/allslots', {
76 | garage: garage,
77 | slots: arr_slot,
78 | body: req.body,
79 | });
80 | }
81 | };
82 |
83 | //findBookings... returns all bookings for a slot
84 | const findBookings = async (id) => {
85 | var slot = await slotService.FindSlot(id);
86 | if (!slot) return 'Slot Not Found';
87 | var bookings = slot.bookings;
88 | var arr_booking = [];
89 | for (var booking of bookings) {
90 | var booking_det = await bookingService.FindBooking(booking);
91 | arr_booking.push(booking_det);
92 | }
93 | return arr_booking;
94 | };
95 |
96 | //apiBooking... returns all booking details for a slot
97 | const apiBooking = async (req, res) => {
98 | var slot_id = req.params.id;
99 | var arr_booking = await findBookings(slot_id);
100 | res.send(arr_booking);
101 | };
102 | module.exports = {
103 | renderAddSlot,
104 | addSlot,
105 | renderSlot,
106 | deleteSlot,
107 | renderSlots,
108 | apiBooking,
109 | findBookings,
110 | };
111 |
--------------------------------------------------------------------------------
/controllers/user.controller.js:
--------------------------------------------------------------------------------
1 | const userService = require('../services/user.service');
2 | const bookingService = require('../services/booking.service');
3 | const garageService = require('../services/garage.service');
4 | const slotService = require('../services/slot.service');
5 | const jwt = require('jsonwebtoken');
6 |
7 | /* ------------ Configs ----------- */
8 |
9 | // cookie options
10 | let options = {
11 | path: '/',
12 | sameSite: true,
13 | maxAge: 1000 * 60 * 60 * Number(process.env.EXPIRY),
14 | httpOnly: true,
15 | };
16 |
17 | let options_otp = {
18 | path: '/',
19 | sameSite: true,
20 | maxAge: 1000 * 60 * 5,
21 | httpOnly: true,
22 | };
23 |
24 | /* ------------ Controllers ----------- */
25 |
26 | //renderRegister... renders the register page
27 | const renderRegister = (req, res) => {
28 | res.render('users/register');
29 | };
30 |
31 | //renderLogin... renders the login page
32 | const renderLogin = (req, res) => {
33 | res.render('users/login');
34 | };
35 |
36 | //renderDashboard... renders the dashboard page
37 | const renderDashboard = async (req, res) => {
38 | var bookings = await bookingService.FindByUser(req.body.user_id);
39 | for (let booking of bookings) {
40 | if (
41 | booking.status !== 'Completed' &&
42 | booking.end_time <= new Date().getTime() / 1000
43 | ) {
44 | await bookingService.completeBooking(booking._id);
45 | }
46 | }
47 | var bookings = await bookingService.FindByUser(req.body.user_id);
48 | res.render('users/dashboard', { body: req.body, bookings: bookings });
49 | };
50 |
51 | //renderAddMoney... renders the add money page
52 | const renderAddMoney = (req, res) => {
53 | res.render('users/addmoney', { money: req.body.money, body: req.body });
54 | };
55 |
56 | //renderVerify... renders the otp verify page
57 | const renderVerify = async (req, res) => {
58 | if (req.body.verified === true) {
59 | res.send('Already Verified');
60 | } else {
61 | var result = await sendOtp(req.body.username);
62 | res.cookie('otp', result.token, options_otp);
63 | res.render('users/verify', { email: req.body.email });
64 | }
65 | };
66 |
67 | //renderRegister... renders the register page
68 | const register = async (req, res) => {
69 | try {
70 | const result = await userService.Register(req.body);
71 | res.cookie('isloggedin', result.token, options);
72 | req.flash(
73 | 'alert',
74 | 'Registered Successfully. Please Verify your Email.'
75 | );
76 | res.redirect('/users/verify');
77 | } catch (err) {
78 | req.flash('err', 'Error :' + err);
79 | res.redirect('/users/register');
80 | }
81 | };
82 |
83 | //sendOtp... sends otp to the user
84 | const sendOtp = async (username) => {
85 | try {
86 | var gen_otp = await userService.generateOtp(username);
87 | return gen_otp;
88 | } catch (err) {
89 | return err;
90 | }
91 | };
92 |
93 | // login... logs in the user
94 | const login = async (req, res) => {
95 | try {
96 | const result = await userService.Login(
97 | req.body.username,
98 | req.body.password
99 | );
100 | res.cookie('isloggedin', result.token, options);
101 | req.flash('success', 'Logged in Successfully.');
102 | res.redirect('/users/dashboard');
103 | } catch (err) {
104 | req.flash('err', 'Error :' + err);
105 | res.redirect('/users/login');
106 | }
107 | };
108 |
109 | // logout... logs out the user
110 | const logout = (req, res) => {
111 | res.clearCookie('isloggedin');
112 | req.flash('success', 'Logged out Successfully.');
113 | res.redirect('/');
114 | };
115 |
116 | //verify.. verifies the otp and logs in the user
117 | const verify = async (req, res) => {
118 | var otp = req.body.otp;
119 | let token = req.cookies['otp'];
120 | let decoded = jwt.verify(token, process.env.JWT_SECRET);
121 | if (!decoded) return res.json('Expired or Invalid login token');
122 | else {
123 | if (decoded.otp === otp) {
124 | await userService.verified(req.body.username);
125 | res.clearCookie('otp');
126 | req.flash('success', 'Verification Complete. Welcome Aboard.');
127 | res.redirect('/users/dashboard');
128 | } else {
129 | res.send('reenter otp');
130 | }
131 | }
132 | };
133 |
134 | //renderImageChange... renders the upload image page
135 | const renderImage = (req, res) => {
136 | res.render('users/uploadimage', { body: req.body });
137 | };
138 |
139 | //upload Image... uploads the image
140 | const uploadImage = async (req, res) => {
141 | var path = req.file['path'];
142 | var userid = req.body.user_id;
143 | console.log(path + ' ' + userid);
144 | try {
145 | await userService.updateImage(userid, path);
146 | req.flash('success', 'Image Uploaded Successfully');
147 | res.redirect('/users/dashboard');
148 | } catch (err) {
149 | req.flash('err', 'Error :' + err);
150 | res.redirect('/users/changeimage');
151 | }
152 | };
153 |
154 | //addMoney... adds money to the user
155 | const addMoney = async (req, res) => {
156 | var add_money = req.body.added_money;
157 | try {
158 | await userService.addMoney(req.body.user_id, add_money);
159 | req.flash('success', 'Money Added Successfully');
160 | res.redirect('/users/dashboard');
161 | } catch (err) {
162 | req.flash('err', 'Error :' + err);
163 | res.redirect('/users/addmoney');
164 | }
165 | };
166 |
167 | //apiOtp... compares the given otp with the otp stored in cookie
168 | const apiOtp = (req, res) => {
169 | var str = req.params.value;
170 | let token = req.cookies['otp'];
171 | let decoded = jwt.verify(token, process.env.JWT_SECRET);
172 | if (decoded.otp === str) res.send('1');
173 | else res.send('0');
174 | };
175 |
176 | //resendOtp... resends the otp to the user
177 | const resendOTP = (req, res) => {
178 | req.flash('alert', 'Your OTP has been resent successfully to your email.');
179 | res.redirect('/users/verify');
180 | };
181 |
182 | //renderTransactions... renders the transactions page
183 | const renderTransactions = async (req, res) => {
184 | var transactions = await userService.getTransactions(req.body.user_id);
185 | res.render('users/transactions', {
186 | body: req.body,
187 | transactions: transactions,
188 | });
189 | };
190 |
191 | //renderBookings... renders the booking page
192 | const renderBookings = async (req, res) => {
193 | var bookings = await bookingService.FindByUser(req.body.user_id);
194 | for (let booking of bookings) {
195 | if (
196 | booking.status !== 'Completed' &&
197 | booking.end_time <= new Date().getTime() / 1000
198 | ) {
199 | await bookingService.completeBooking(booking._id);
200 | }
201 | }
202 | res.render('users/bookings', { body: req.body, bookings: bookings });
203 | };
204 |
205 | //renderBooking... renders the booking details page
206 | const renderBooking = async (req, res) => {
207 | var id = req.params.id;
208 | var booking = await bookingService.FindBooking(id);
209 | if (!booking) {
210 | req.flash('err', 'No Booking Found');
211 | res.redirect('/users/dashboard');
212 | } else {
213 | var slot = await slotService.FindSlot(booking.slot_id);
214 | var garage = await garageService.FindGarage(slot.garage_id);
215 | res.render('users/mybooking', {
216 | body: req.body,
217 | booking: booking,
218 | slot: slot,
219 | garage: garage,
220 | });
221 | }
222 | };
223 | module.exports = {
224 | renderLogin,
225 | renderRegister,
226 | register,
227 | login,
228 | renderDashboard,
229 | logout,
230 | renderVerify,
231 | verify,
232 | renderImage,
233 | uploadImage,
234 | renderAddMoney,
235 | addMoney,
236 | apiOtp,
237 | resendOTP,
238 | renderTransactions,
239 | renderBookings,
240 | renderBooking,
241 | };
242 |
--------------------------------------------------------------------------------
/middleware/ip.middleware.js:
--------------------------------------------------------------------------------
1 | const RequestIp = require('@supercharge/request-ip');
2 |
3 | // get the client's IP address
4 | const getIpMiddleware = function (req, res, next) {
5 | if (RequestIp.getClientIp(req) !== '::1')
6 | req.ipv2 = RequestIp.getClientIp(req);
7 | next();
8 | };
9 |
10 | module.exports = getIpMiddleware;
11 |
--------------------------------------------------------------------------------
/middleware/isadmin.middleware.js:
--------------------------------------------------------------------------------
1 | // IsAdminMiddleware... is a middleware that checks if the user is an admin
2 | const IsAdminMiddleware = () => {
3 | return async (req, res, next) => {
4 | try {
5 | // Checks if the user is an admin
6 | if (req.body.user_type !== 'admin') {
7 | req.flash('alert', 'Not Authorized. Not Allowed.');
8 | res.redirect('/users/dashboard');
9 | } else {
10 | next();
11 | }
12 | } catch (error) {
13 | return res.status(500).send(error);
14 | }
15 | };
16 | };
17 |
18 | module.exports = IsAdminMiddleware;
19 |
--------------------------------------------------------------------------------
/middleware/login.middleware.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const User = require('../models/user.model');
3 |
4 | /* ------------ Authentication Middleware ----------- */
5 |
6 | // IsloggedInMiddleware ... validates the user from jwt token stored in cookies
7 | const IsLoggedInMiddleware = () => {
8 | return async (req, res, next) => {
9 | // Access token from cookies
10 | var cookie = JSON.parse(JSON.stringify(req.cookies));
11 |
12 | // Token not found
13 | if (cookie['isloggedin'] === undefined) {
14 | req.flash('err', 'Not Logged in. Please login to continue');
15 | res.redirect('/users/login');
16 | } else {
17 | let token = req.cookies['isloggedin'];
18 | if (!token) {
19 | req.flash('alert', 'Not Logged in. Please login to continue');
20 | res.redirect('/users/login');
21 | }
22 | try {
23 | // Unsign and verify the jwt token
24 | let decoded = jwt.verify(token, process.env.JWT_SECRET);
25 | if (!decoded)
26 | return res.status(401).json('Expired or Invalid token');
27 |
28 | // Fetch user from DB
29 | let user = await User.findOne({ username: decoded.username });
30 |
31 | // If user not found
32 | if (!user) {
33 | req.flash('err', 'User Doesnt Exist');
34 | res.redirect('/');
35 | }
36 | // Check for user authenticity
37 | else if (
38 | user.verified === false &&
39 | req.url != '/verify' &&
40 | req.url != '/resendotp'
41 | ) {
42 | req.flash('alert', 'Not Verified.');
43 | res.redirect('/users/verify');
44 | } else {
45 | // Add user properties to the request body
46 | req.body.user_id = user._id;
47 | req.body.email = user.email;
48 | req.body.name = user.name;
49 | req.body.verified = user.verified;
50 | req.body.phone = user.phone;
51 | req.body.username = user.username;
52 | req.body.picture_url = user.picture_url;
53 | req.body.money = user.money;
54 | req.body.user_type = user.type;
55 |
56 | // Call next middleware on successful validation
57 | next();
58 | }
59 | } catch (error) {
60 | return res.status(500).send(error);
61 | }
62 | }
63 | };
64 | };
65 |
66 | module.exports = IsLoggedInMiddleware;
67 |
--------------------------------------------------------------------------------
/middleware/sanitizer.middleware.js:
--------------------------------------------------------------------------------
1 | const sanitizeHtml = require('sanitize-html');
2 |
3 | // sanitizer configuration
4 | const sanitizer = {
5 | allowedTags: [],
6 | allowedAttributes: {},
7 | allowedIframeHostnames: [],
8 | };
9 |
10 | /* ------------ Html sanitizer Middleware ----------- */
11 |
12 | // SanitizerMiddleware ... receives a JSON object as parameter and sanitizes the html
13 | const SanitizerMiddleware = () => {
14 | return async (req, res, next) => {
15 | let m = 0;
16 | for (let [key, value] of Object.entries(req.body)) {
17 | // checks if a value of the object contains html
18 | if (
19 | value !== sanitizeHtml(value, sanitizer) &&
20 | key !== 'user_id' &&
21 | key !== 'verified'
22 | ) {
23 | console.log(value + ' ' + sanitizeHtml(value, sanitizer));
24 | res.status(500).send(key + ' must not include HTML!');
25 | m = 1;
26 | }
27 | }
28 | // pass the request to the next middleware if no html is found
29 | if (m == 0) next();
30 | };
31 | };
32 |
33 | module.exports = SanitizerMiddleware;
34 |
--------------------------------------------------------------------------------
/models/booking.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 | const bookingSchema = new Schema(
5 | {
6 | slot_id: {
7 | type: Schema.Types.ObjectId,
8 | ref: 'Slot',
9 | },
10 | user_id: {
11 | type: Schema.Types.ObjectId,
12 | ref: 'User',
13 | },
14 | start_time: {
15 | type: Number,
16 | required: true,
17 | },
18 | end_time: {
19 | type: Number,
20 | required: true,
21 | },
22 | amount: {
23 | type: Schema.Types.Decimal128,
24 | required: true,
25 | },
26 | status: {
27 | type: String,
28 | enum: ['Completed', 'Cancelled', 'Booked', 'CheckedIn'],
29 | default: 'Booked',
30 | },
31 | },
32 | {
33 | timestamps: true,
34 | }
35 | );
36 |
37 | let Booking = mongoose.model('Booking', bookingSchema);
38 |
39 | module.exports = Booking;
40 |
--------------------------------------------------------------------------------
/models/garage.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 | const garageSchema = new Schema(
5 | {
6 | name: {
7 | type: String,
8 | required: true,
9 | },
10 | location: {
11 | type: String,
12 | required: true,
13 | },
14 | location_cat: {
15 | type: String,
16 | enum: ['outskirts', 'prime', 'nice'],
17 | },
18 | size: {
19 | type: String,
20 | required: true,
21 | },
22 | owner: {
23 | type: String,
24 | required: true,
25 | },
26 | geometry: {
27 | type: {
28 | type: String,
29 | enum: ['Point'],
30 | required: true,
31 | },
32 | coordinates: {
33 | type: [Number],
34 | required: true,
35 | },
36 | },
37 | picture_url: {
38 | type: String,
39 | default:
40 | 'https://res.cloudinary.com/codehackerone/image/upload/v1620559724/Parkify/garagepic_bnozdl.jpg',
41 | },
42 | slots: [
43 | {
44 | type: Schema.Types.ObjectId,
45 | ref: 'Slot',
46 | },
47 | ],
48 | },
49 | {
50 | timestamps: true,
51 | }
52 | );
53 | let Garage = mongoose.model('Garage', garageSchema);
54 |
55 | module.exports = Garage;
56 |
--------------------------------------------------------------------------------
/models/slot.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 | const slotSchema = new Schema(
5 | {
6 | garage_id: {
7 | type: Schema.Types.ObjectId,
8 | ref: 'Garage',
9 | },
10 | name: {
11 | type: String,
12 | required: true,
13 | },
14 | type: {
15 | type: String,
16 | enum: ['Large', 'Medium', 'Small'],
17 | },
18 | price: {
19 | type: Number,
20 | required: true,
21 | },
22 | bookings: [
23 | {
24 | type: Schema.Types.ObjectId,
25 | ref: 'Booking',
26 | },
27 | ],
28 | },
29 | {
30 | timestamps: true,
31 | }
32 | );
33 |
34 | let Slot = mongoose.model('Slot', slotSchema);
35 |
36 | module.exports = Slot;
37 |
--------------------------------------------------------------------------------
/models/transaction.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 | const transactionSchema = new Schema(
5 | {
6 | user_id: {
7 | type: Schema.Types.ObjectId,
8 | ref: 'User',
9 | required: true,
10 | },
11 | type: {
12 | type: String,
13 | enum: ['credit', 'debit'],
14 | },
15 | booking_id: {
16 | type: Schema.Types.ObjectId,
17 | ref: 'Booking',
18 | },
19 | amount: {
20 | type: Schema.Types.Decimal128,
21 | required: true,
22 | },
23 | remarks: {
24 | type: String,
25 | enum: ['add_fund', 'book_slot', 'refund'],
26 | },
27 | },
28 | {
29 | timestamps: true,
30 | }
31 | );
32 |
33 | let Transaction = mongoose.model('Transaction', transactionSchema);
34 |
35 | module.exports = Transaction;
36 |
--------------------------------------------------------------------------------
/models/user.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 | const bcrypt = require('bcrypt');
4 |
5 | const userSchema = new Schema(
6 | {
7 | name: {
8 | type: String,
9 | required: true,
10 | },
11 | email: {
12 | type: String,
13 | required: true,
14 | unique: true,
15 | },
16 | password: {
17 | type: String,
18 | required: true,
19 | },
20 | phone: {
21 | type: Number,
22 | unique: true,
23 | },
24 | username: {
25 | type: String,
26 | unique: true,
27 | },
28 | verified: {
29 | type: Boolean,
30 | default: false,
31 | },
32 | type: {
33 | type: String,
34 | default: 'user',
35 | },
36 | money: {
37 | type: Schema.Types.Decimal128,
38 | default: 0.0,
39 | },
40 | picture_url: {
41 | type: String,
42 | default:
43 | 'https://res.cloudinary.com/codehackerone/image/upload/v1620454218/default_dp_ubrfcg.jpg',
44 | },
45 | },
46 | {
47 | timestamps: true,
48 | }
49 | );
50 |
51 | // hashes the password before saving using bcrypt
52 | userSchema.pre('save', async function (next) {
53 | if (!this.isModified || !this.isNew) {
54 | next();
55 | } else this.isModified('password');
56 | if (this.password)
57 | this.password = await bcrypt.hash(String(this.password), 12);
58 | next();
59 | });
60 |
61 | let User = mongoose.model('User', userSchema);
62 |
63 | module.exports = User;
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parkify",
3 | "version": "1.0.0",
4 | "description": "Parking Web Application.",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node app.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/Codehackerone/parkify.git"
13 | },
14 | "author": "",
15 | "license": "ISC",
16 | "bugs": {
17 | "url": "https://github.com/Codehackerone/parkify/issues"
18 | },
19 | "homepage": "https://github.com/Codehackerone/parkify#readme",
20 | "dependencies": {
21 | "@mapbox/mapbox-sdk": "^0.13.5",
22 | "@supercharge/request-ip": "^1.1.2",
23 | "axios": "^0.21.4",
24 | "bcrypt": "^5.0.1",
25 | "cloudinary": "^1.25.1",
26 | "connect-mongo": "^3.2.0",
27 | "cookie-parser": "^1.4.5",
28 | "cors": "^2.8.5",
29 | "dotenv": "^8.2.0",
30 | "ejs": "^3.1.6",
31 | "express": "^4.17.1",
32 | "express-flash": "0.0.2",
33 | "express-session": "^1.17.1",
34 | "helmet": "^4.5.0",
35 | "jsonwebtoken": "^8.5.1",
36 | "method-override": "^3.0.0",
37 | "mongoose": "^5.12.3",
38 | "multer": "^1.4.2",
39 | "multer-storage-cloudinary": "^4.0.0",
40 | "nodemailer": "^6.6.0",
41 | "nodemon": "^2.0.20",
42 | "request-ip": "^2.1.3",
43 | "sanitize-html": "^2.3.3",
44 | "serve-favicon": "^2.5.0"
45 | },
46 | "devDependencies": {
47 | "autoprefixer": "^10.4.12",
48 | "postcss": "^8.4.17",
49 | "tailwindcss": "^3.1.8"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/css/calendar.css:
--------------------------------------------------------------------------------
1 | #calendar {
2 | width: 358px;
3 | margin: 0 auto;
4 | margin-top: 2%;
5 | margin-bottom: 2%;
6 | border-radius: 5px;
7 | text-align: center;
8 | color: #555;
9 | box-shadow: 0 0 50px -14px rgba(0, 0, 0, 0.8);
10 | }
11 |
12 | #calendar h1 {
13 | background: #ee3333;
14 | border-radius: 5px 5px 0 0;
15 | padding: 20px;
16 | font-size: 140%;
17 | font-weight: 300;
18 | text-transform: uppercase;
19 | letter-spacing: 1px;
20 | color: #fff;
21 | cursor: default;
22 | text-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
23 | }
24 |
25 | #calendar table {
26 | border-top: 1px solid #ddd;
27 | border-left: 1px solid #ddd;
28 | border-spacing: 0;
29 | border-radius: 0 0 5px 5px;
30 | }
31 |
32 | #calendar td {
33 | width: 38px;
34 | height: 38px;
35 | background: #eee;
36 | border-right: 1px solid #ddd;
37 | border-bottom: 1px solid #ddd;
38 | padding: 6px;
39 | cursor: pointer;
40 | }
41 |
42 | #calendar td:hover:not(.current) {
43 | background: #ddd;
44 | }
45 |
46 | #calendar .lastmonth,
47 | #calendar .nextmonth,
48 | #calendar .nextmonth ~ * {
49 | background: #fff;
50 | color: #999;
51 | }
52 |
53 | #calendar .current {
54 | background: #ee3333;
55 | font-weight: 700;
56 | color: #fff;
57 | text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
58 | }
59 |
60 | #calendar .hastask {
61 | font-weight: 700;
62 | }
63 |
64 | #calendar tr:last-of-type td:first-of-type {
65 | border-radius: 0 0 0 5px;
66 | }
67 | #calendar tr:last-of-type td:last-of-type {
68 | border-radius: 0 0 5px 0;
69 | }
70 |
--------------------------------------------------------------------------------
/public/css/cards.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Roboto','Helvetica Neue', Helvetica, Arial, sans-serif;
3 | font-style: normal;
4 | font-weight: 400;
5 | }
6 |
7 | .btn {
8 | background-color: white;
9 | margin-left: 1rem;
10 | padding: 0.5rem;
11 | }
12 |
13 | .cards {
14 | display: flex;
15 | flex-wrap: wrap;
16 | list-style: none;
17 | margin: 0;
18 | padding: 0;
19 | }
20 |
21 | .cardItem {
22 | display: flex;
23 | padding: 1rem;
24 | @media(min-width: 40rem) {
25 | width: 50%;
26 | }
27 | @media(min-width: 56rem) {
28 | width: 33.3333%;
29 | }
30 | }
31 |
32 | .eachCard {
33 | background-color: white;
34 | border-radius: 1rem;
35 | border: 1px solid rgb(190, 189, 189);
36 | box-shadow: 0 20px 40px -14px rgb(190, 183, 183);
37 | display: flex;
38 | flex-direction: column;
39 | align-items: center;
40 | overflow: hidden;
41 | }
42 |
43 | .cardContent {
44 | display: flex;
45 | flex: 1 1 auto;
46 | flex-direction: column;
47 | align-items: center;
48 | padding: 1rem;
49 | }
50 |
51 | .cardTitle {
52 | font-size: 1.25rem;
53 | font-weight: 600;
54 | letter-spacing: 2px;
55 | text-transform: uppercase;
56 | }
57 |
58 | .cardText {
59 | flex: 1 1 auto;
60 | font-size: 0.85rem;
61 | line-height: 1.5;
62 | }
63 |
64 | .startTime{
65 | padding-bottom: 1rem;
66 | }
67 |
68 | .btn{
69 | margin-bottom:1rem;
70 | }
--------------------------------------------------------------------------------
/public/css/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;200&display=swap');
5 | body {
6 | font-family: 'Poppins', sans-serif;
7 | height: 100vh;
8 | background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)),
9 | url('/img/coverimg.jpg');
10 | background-size: cover;
11 | background-position: center;
12 | text-shadow: 0 0.05rem 0.1rem rgba(0, 0, 0, 0.5);
13 | box-shadow: inset 0 0 5rem rgba(0, 0, 0, 0.5);
14 | }
15 | .cover-container {
16 | max-width: 60vw;
17 | }
18 |
19 | .nav-link {
20 | padding: 0.25rem 0;
21 | font-weight: 700;
22 | color: rgba(255, 255, 255, 0.5);
23 | margin-left: 1rem;
24 | border-bottom: 0.25rem solid transparent;
25 | }
26 |
27 | .nav-link:hover {
28 | color: rgba(255, 255, 255, 0.5);
29 | border-bottom-color: rgba(255, 255, 255, 0.5);
30 | }
31 |
32 | .nav-link.active {
33 | color: white;
34 | border-bottom-color: white;
35 | }
36 |
37 | .btn-secondary,
38 | .btn-secondary:hover {
39 | color: #333;
40 | text-shadow: none;
41 | }
42 | .github-link {
43 | color: white;
44 | text-decoration: none;
45 | transition: 0.5s;
46 | }
47 | .github-link:hover {
48 | color: white;
49 | text-decoration: underline;
50 | }
51 |
--------------------------------------------------------------------------------
/public/img/coverimg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codehackerone/parkify/05805222fd5f6b9ea330bdc81faa97bb89715ef1/public/img/coverimg.jpg
--------------------------------------------------------------------------------
/public/img/coverpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codehackerone/parkify/05805222fd5f6b9ea330bdc81faa97bb89715ef1/public/img/coverpage.png
--------------------------------------------------------------------------------
/public/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codehackerone/parkify/05805222fd5f6b9ea330bdc81faa97bb89715ef1/public/img/favicon.ico
--------------------------------------------------------------------------------
/public/img/logo-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codehackerone/parkify/05805222fd5f6b9ea330bdc81faa97bb89715ef1/public/img/logo-black.png
--------------------------------------------------------------------------------
/public/img/logo-text-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codehackerone/parkify/05805222fd5f6b9ea330bdc81faa97bb89715ef1/public/img/logo-text-black.png
--------------------------------------------------------------------------------
/public/img/logo-text-yellow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codehackerone/parkify/05805222fd5f6b9ea330bdc81faa97bb89715ef1/public/img/logo-text-yellow.png
--------------------------------------------------------------------------------
/public/img/logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codehackerone/parkify/05805222fd5f6b9ea330bdc81faa97bb89715ef1/public/img/logo-white.png
--------------------------------------------------------------------------------
/public/img/logo-yellow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codehackerone/parkify/05805222fd5f6b9ea330bdc81faa97bb89715ef1/public/img/logo-yellow.png
--------------------------------------------------------------------------------
/public/js/allgarage.js:
--------------------------------------------------------------------------------
1 | async function getSlotInfo(key) {
2 | var xhttp = new XMLHttpRequest();
3 | xhttp.onreadystatechange = async function () {
4 | //readyState == 4 means the request is done.
5 | //status == 200 means the request is successful.
6 | if (this.readyState == 4 && this.status == 200) {
7 | document.getElementById(key).innerHTML = this.responseText;
8 | }
9 | };
10 | xhttp.open('GET', '/garage/apislot/' + key, true);
11 | xhttp.send();
12 | }
13 |
--------------------------------------------------------------------------------
/public/js/clustermap.js:
--------------------------------------------------------------------------------
1 | mapboxgl.accessToken = mapboxtoken;
2 | var garages = { features: JSON.parse(garage_raw) };
3 | const map = new mapboxgl.Map({
4 | container: 'cluster-map',
5 | style: 'mapbox://styles/mapbox/light-v10',
6 | center: [77.23149, 28.65195],
7 | zoom: 3,
8 | });
9 |
10 | // Add zoom and rotation controls to the map.
11 | map.addControl(new mapboxgl.NavigationControl());
12 |
13 | map.on('load', function () {
14 | // Add a new source from our GeoJSON data and set the
15 | // 'cluster' option to true. GL-JS will add the point_count property to your source data.
16 | map.addSource('garages', {
17 | type: 'geojson',
18 | data: garages,
19 | cluster: true,
20 | clusterMaxZoom: 14,
21 | clusterRadius: 50,
22 | });
23 |
24 | // Add a cluster layer. This layer will cluster all point features in the source layer
25 | map.addLayer({
26 | id: 'clusters',
27 | type: 'circle',
28 | source: 'garages',
29 | filter: ['has', 'point_count'],
30 | // cluster style configurations
31 | paint: {
32 | 'circle-color': [
33 | 'step',
34 | ['get', 'point_count'],
35 | '#00BCD4',
36 | 10,
37 | '#2196F3',
38 | 30,
39 | '#3F51B5',
40 | ],
41 | 'circle-radius': [
42 | 'step',
43 | ['get', 'point_count'],
44 | 15,
45 | 10,
46 | 20,
47 | 30,
48 | 25,
49 | ],
50 | },
51 | });
52 |
53 | // Add a layer for the clusters' count labels
54 | map.addLayer({
55 | id: 'cluster-count',
56 | type: 'symbol',
57 | source: 'garages',
58 | filter: ['has', 'point_count'],
59 | layout: {
60 | 'text-field': '{point_count_abbreviated}',
61 | 'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
62 | 'text-size': 12,
63 | },
64 | });
65 |
66 | // Add a layer for the unclustered point features.
67 | map.addLayer({
68 | id: 'unclustered-point',
69 | type: 'circle',
70 | source: 'garages',
71 | filter: ['!', ['has', 'point_count']],
72 | paint: {
73 | 'circle-color': '#11b4da',
74 | 'circle-radius': 4,
75 | 'circle-stroke-width': 1,
76 | 'circle-stroke-color': '#fff',
77 | },
78 | });
79 |
80 | // inspect a cluster on click on clustered point
81 | map.on('click', 'clusters', function (e) {
82 | const features = map.queryRenderedFeatures(e.point, {
83 | layers: ['clusters'],
84 | });
85 | const clusterId = features[0].properties.cluster_id;
86 | map.getSource('garages').getClusterExpansionZoom(
87 | clusterId,
88 | function (err, zoom) {
89 | if (err) return;
90 |
91 | map.easeTo({
92 | center: features[0].geometry.coordinates,
93 | zoom: zoom,
94 | });
95 | }
96 | );
97 | });
98 |
99 | // inspect a cluster on click on an unclustered point
100 | map.on('click', 'unclustered-point', function (e) {
101 | const { popUpMarkup } = e.features[0].properties;
102 | const coordinates = e.features[0].geometry.coordinates.slice();
103 | while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
104 | coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
105 | }
106 |
107 | // Populate the popup and set its coordinates
108 | new mapboxgl.Popup()
109 | .setLngLat(coordinates)
110 | .setHTML(popUpMarkup)
111 | .addTo(map);
112 | });
113 |
114 | // Change the cursor to a pointer when the mouse is over the places layer.
115 | map.on('mouseenter', 'clusters', function () {
116 | map.getCanvas().style.cursor = 'pointer';
117 | });
118 | map.on('mouseleave', 'clusters', function () {
119 | map.getCanvas().style.cursor = '';
120 | });
121 | });
122 |
--------------------------------------------------------------------------------
/public/js/newbooking.js:
--------------------------------------------------------------------------------
1 | function add_zeroes(num) {
2 | if (num < 10) return '0' + num;
3 | else return num;
4 | }
5 | var epoch1 = new Date().getTime();
6 | var year1 = new Date(epoch1).getFullYear();
7 | var month1 = new Date(epoch1).getMonth() + 1;
8 | var day1 = new Date(epoch1).getDate();
9 | var hour1 = new Date(epoch1).getHours();
10 | var minutes1 = new Date(epoch1).getMinutes();
11 | var mindate =
12 | year1 +
13 | '-' +
14 | add_zeroes(month1) +
15 | '-' +
16 | add_zeroes(day1) +
17 | 'T' +
18 | add_zeroes(hour1) +
19 | ':' +
20 | add_zeroes(minutes1);
21 | epoch2 = epoch1 + 86400000 * 30;
22 | var year1 = new Date(epoch2).getFullYear();
23 | var month1 = new Date(epoch2).getMonth() + 1;
24 | var day1 = new Date(epoch2).getDate();
25 | var hour1 = new Date(epoch2).getHours();
26 | var minutes1 = new Date(epoch2).getMinutes();
27 | var maxdate =
28 | year1 +
29 | '-' +
30 | add_zeroes(month1) +
31 | '-' +
32 | add_zeroes(day1) +
33 | 'T' +
34 | add_zeroes(hour1) +
35 | ':' +
36 | add_zeroes(minutes1);
37 | document.getElementById('start_time').max = maxdate;
38 | document.getElementById('start_time').min = mindate;
39 | document.getElementById('end_time').max = maxdate;
40 | document.getElementById('end_time').min = mindate;
41 |
--------------------------------------------------------------------------------
/public/js/validateForms.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | // Fetch all the forms we want to apply custom Bootstrap validation styles to
5 | const forms = document.querySelectorAll('.validated-form');
6 |
7 | // Loop over them and prevent submission
8 | Array.from(forms).forEach(function (form) {
9 | form.addEventListener(
10 | 'submit',
11 | function (event) {
12 | if (!form.checkValidity()) {
13 | event.preventDefault();
14 | event.stopPropagation();
15 | }
16 |
17 | form.classList.add('was-validated');
18 | },
19 | false
20 | );
21 | });
22 | })();
23 |
--------------------------------------------------------------------------------
/public/js/viewgarage.js:
--------------------------------------------------------------------------------
1 | var garage = JSON.parse(garage_raw);
2 | mapboxgl.accessToken = mapboxtoken;
3 |
4 | // Initialize the map
5 | var map = new mapboxgl.Map({
6 | container: 'map',
7 | style: 'mapbox://styles/mapbox/streets-v11',
8 | center: garage.geometry.coordinates,
9 | zoom: 9,
10 | });
11 |
12 | // Add garage marker to map
13 | var marker = new mapboxgl.Marker()
14 | .setLngLat(garage.geometry.coordinates)
15 | .setPopup(
16 | new mapboxgl.Popup({ offset: 25 }).setHTML(
17 | `${garage.name} ${garage.location}`
18 | )
19 | )
20 | .addTo(map);
21 |
--------------------------------------------------------------------------------
/routes/booking.route.js:
--------------------------------------------------------------------------------
1 | /* ------------ Imports ----------- */
2 |
3 | const express = require('express');
4 | const Router = express.Router();
5 | const bookingController = require('../controllers/booking.controller');
6 | const IsLoggedInMiddleware = require('../middleware/login.middleware');
7 | const sanitizerMiddleware = require('../middleware/sanitizer.middleware');
8 |
9 | /* ------------ Endpoint Definitions ----------- */
10 |
11 | Router.route('/new/:id')
12 | .get(IsLoggedInMiddleware(), bookingController.renderNewBooking)
13 | .post(IsLoggedInMiddleware(), bookingController.newBooking);
14 |
15 | Router.route('/:id')
16 | .get(IsLoggedInMiddleware(), bookingController.renderBooking)
17 | .delete(IsLoggedInMiddleware(), bookingController.deleteBooking);
18 |
19 | Router.route('/cancel/:id').post(
20 | IsLoggedInMiddleware(),
21 | bookingController.cancelBooking
22 | );
23 |
24 | module.exports = Router;
25 |
--------------------------------------------------------------------------------
/routes/garage.route.js:
--------------------------------------------------------------------------------
1 | /* ------------ Imports ----------- */
2 |
3 | const express = require('express');
4 | const Router = express.Router();
5 | const multer = require('multer');
6 | const { storage } = require('../cloudinary');
7 | const upload = multer({ storage });
8 | const garageController = require('../controllers/garage.controller');
9 | const IsLoggedInMiddleware = require('../middleware/login.middleware');
10 | const sanitizerMiddleware = require('../middleware/sanitizer.middleware');
11 | const IsAdminMiddleware = require('../middleware/isadmin.middleware');
12 | const getIpMiddleware = require('../middleware/ip.middleware');
13 |
14 | /* ------------ Endpoint Definitions ----------- */
15 |
16 | Router.route('/').get(
17 | IsLoggedInMiddleware(),
18 | garageController.renderAllGarages
19 | );
20 |
21 | Router.route('/add')
22 | .get(
23 | IsLoggedInMiddleware(),
24 | IsAdminMiddleware(),
25 | garageController.renderAddGarage
26 | )
27 | .post(
28 | IsLoggedInMiddleware(),
29 | IsAdminMiddleware(),
30 | upload.single('image'),
31 | garageController.addGarage
32 | );
33 |
34 | Router.route('/find')
35 | .get(IsLoggedInMiddleware(), garageController.renderfindgarage)
36 | .put(
37 | IsLoggedInMiddleware(),
38 | getIpMiddleware,
39 | garageController.rendergaragebyip
40 | )
41 | .patch(IsLoggedInMiddleware(), garageController.rendergaragebyloc);
42 |
43 | Router.route('/:id')
44 | .get(IsLoggedInMiddleware(), garageController.renderGarage)
45 | .delete(
46 | IsLoggedInMiddleware(),
47 | IsAdminMiddleware(),
48 | garageController.deleteGarage
49 | );
50 |
51 | Router.route('/apislot/:id').get(garageController.apiSlotInfo);
52 |
53 | module.exports = Router;
54 |
--------------------------------------------------------------------------------
/routes/slot.route.js:
--------------------------------------------------------------------------------
1 | /* ------------ Imports ----------- */
2 |
3 | const express = require('express');
4 | const Router = express.Router();
5 | const slotController = require('../controllers/slot.controller');
6 | const IsLoggedInMiddleware = require('../middleware/login.middleware');
7 | const sanitizerMiddleware = require('../middleware/sanitizer.middleware');
8 | const IsAdminMiddleware = require('../middleware/isadmin.middleware');
9 |
10 | /* ------------ Endpoint Definitions ----------- */
11 |
12 | Router.route('/add/:id')
13 | .get(
14 | IsLoggedInMiddleware(),
15 | IsAdminMiddleware(),
16 | slotController.renderAddSlot
17 | )
18 | .post(IsLoggedInMiddleware(), IsAdminMiddleware(), slotController.addSlot);
19 |
20 | Router.route('/:id')
21 | .get(IsLoggedInMiddleware(), slotController.renderSlot)
22 | .delete(
23 | IsLoggedInMiddleware(),
24 | IsAdminMiddleware(),
25 | slotController.deleteSlot
26 | );
27 |
28 | Router.route('/garage/:id').get(
29 | IsLoggedInMiddleware(),
30 | slotController.renderSlots
31 | );
32 |
33 | Router.route('/apibooking/:id').get(slotController.apiBooking);
34 |
35 | module.exports = Router;
36 |
--------------------------------------------------------------------------------
/routes/user.route.js:
--------------------------------------------------------------------------------
1 | /* ------------ Imports ----------- */
2 | const express = require('express');
3 | const Router = express.Router();
4 | const multer = require('multer');
5 | const { storage } = require('../cloudinary');
6 | const upload = multer({ storage });
7 | const userController = require('../controllers/user.controller');
8 | const IsLoggedInMiddleware = require('../middleware/login.middleware');
9 | const sanitizerMiddleware = require('../middleware/sanitizer.middleware');
10 |
11 | /* ------------ Endpoint Definitions ----------- */
12 |
13 | Router.route('/register')
14 | .get(userController.renderRegister)
15 | .post(sanitizerMiddleware(), userController.register);
16 |
17 | Router.route('/login')
18 | .get(userController.renderLogin)
19 | .post(sanitizerMiddleware(), userController.login);
20 |
21 | Router.route('/verify')
22 | .get(IsLoggedInMiddleware(), userController.renderVerify)
23 | .post(IsLoggedInMiddleware(), userController.verify);
24 |
25 | Router.route('/dashboard').get(
26 | IsLoggedInMiddleware(),
27 | userController.renderDashboard
28 | );
29 |
30 | Router.route('/changeimage')
31 | .get(IsLoggedInMiddleware(), userController.renderImage)
32 | .post(
33 | upload.single('image'),
34 | IsLoggedInMiddleware(),
35 | userController.uploadImage
36 | );
37 |
38 | Router.route('/addmoney')
39 | .get(IsLoggedInMiddleware(), userController.renderAddMoney)
40 | .post(IsLoggedInMiddleware(), userController.addMoney);
41 |
42 | Router.route('/apiotp/:value').get(userController.apiOtp);
43 |
44 | Router.route('/resendotp').post(
45 | IsLoggedInMiddleware(),
46 | userController.resendOTP
47 | );
48 |
49 | Router.route('/logout').delete(IsLoggedInMiddleware(), userController.logout);
50 |
51 | Router.route('/transactions').get(
52 | IsLoggedInMiddleware(),
53 | userController.renderTransactions
54 | );
55 |
56 | Router.route('/bookings').get(
57 | IsLoggedInMiddleware(),
58 | userController.renderBookings
59 | );
60 |
61 | Router.route('/booking/:id').get(
62 | IsLoggedInMiddleware(),
63 | userController.renderBooking
64 | );
65 |
66 | module.exports = Router;
67 |
--------------------------------------------------------------------------------
/services/booking.service.js:
--------------------------------------------------------------------------------
1 | const Booking = require('../models/booking.model');
2 | const Slot = require('../models/slot.model');
3 | const User = require('../models/user.model');
4 | const Transaction = require('../models/transaction.model');
5 |
6 | //NewBooking... creates a new booking and adds it to the slot.
7 | const NewBooking = async (bookingBody) => {
8 | try {
9 | const slot = await Slot.findById(bookingBody.slot_id);
10 | //check if start time is less than end time
11 | if (bookingBody.start_time >= bookingBody.end_time) {
12 | throw 'Please Choose a Correct Start and End Date!';
13 | }
14 | const all_bookings = slot.bookings;
15 |
16 | //check if the slot is already booked
17 | if (all_bookings.length != 0) {
18 | for (var i = 0; i < all_bookings.length; i++) {
19 | var booking = await Booking.findById(all_bookings[i]);
20 | if (!booking) continue;
21 | if (booking.status === 'Cancelled') continue;
22 | var a = parseInt(booking.start_time),
23 | b = parseInt(booking.end_time);
24 | var c = parseInt(bookingBody.start_time),
25 | d = parseInt(bookingBody.end_time);
26 | if (
27 | (c >= a && c <= b) ||
28 | (d >= a && d <= b) ||
29 | (c <= a && d >= b)
30 | ) {
31 | throw 'Already Booked!';
32 | } else {
33 | continue;
34 | }
35 | }
36 | }
37 | const user = await User.findById(bookingBody.user_id);
38 | if (!user) throw 'User doesnt Exist';
39 |
40 | //check if user has enough money
41 | if (bookingBody.amount > user.money) throw 'Insufficient Funds!!';
42 | const result = await Booking.create(bookingBody);
43 | slot.bookings.push(result);
44 | await slot.save();
45 | var money = parseFloat(user.money);
46 | money = money - parseFloat(bookingBody.amount);
47 | user.money = money;
48 | await user.save();
49 |
50 | //create transaction
51 | const transaction = {
52 | user_id: user._id,
53 | type: 'debit',
54 | amount: parseFloat(bookingBody.amount),
55 | remarks: 'book_slot',
56 | booking_id: result._id,
57 | };
58 | await Transaction.create(transaction);
59 | return result;
60 | } catch (error) {
61 | throw error;
62 | }
63 | };
64 |
65 | //FindBooking... finds a booking by id
66 | const FindBooking = async (id) => {
67 | const booking = await Booking.findOne({ _id: id });
68 | return booking;
69 | };
70 |
71 | //DeleteBooking... deletes a booking by id
72 | const DeleteBooking = async (id) => {
73 | try {
74 | const booking = await Booking.findById(id);
75 | await Slot.findByIdAndUpdate(booking.slot_id, {
76 | $pull: { bookings: id },
77 | });
78 | await Booking.findByIdAndDelete(id);
79 | } catch (err) {
80 | throw err;
81 | }
82 | };
83 |
84 | //apiMoney... returns price of the slot
85 | const apiMoney = async (slot_id) => {
86 | try {
87 | var slot = await Slot.findById(slot_id);
88 | return slot.price;
89 | } catch (err) {
90 | return err;
91 | }
92 | };
93 |
94 | //FindByUser... finds all bookings by user
95 | const FindByUser = async (id) => {
96 | try {
97 | var bookings = await Booking.find({ user_id: id });
98 | return bookings;
99 | } catch (err) {
100 | res.send('User not found.');
101 | }
102 | };
103 |
104 | //cancelBooking... cancels a booking by id
105 | const cancelBooking = async (id) => {
106 | var booking = await Booking.findById(id);
107 | if (!booking) throw 'Booking not Found';
108 | //check if booking is already cancelled
109 | else if (booking.status === 'Cancelled') {
110 | throw 'Booking already Cancelled';
111 | }
112 | //check if booking is already completed
113 | else if (booking.end_time <= new Date().getTime() / 1000) {
114 | booking.status = 'Completed';
115 | await booking.save();
116 | throw 'Booking already Completed.';
117 | } else {
118 | var user = await User.findById(booking.user_id);
119 | if (!user) throw 'User not found';
120 | else {
121 | booking.status = 'Cancelled';
122 |
123 | //calculate refund
124 | var money = parseFloat(user.money);
125 | money = money + parseFloat(booking.amount / 2);
126 | user.money = money;
127 |
128 | //create transaction
129 | const transaction = {
130 | user_id: user._id,
131 | type: 'credit',
132 | amount: parseFloat(booking.amount / 2),
133 | remarks: 'refund',
134 | booking_id: booking._id,
135 | };
136 | await Transaction.create(transaction);
137 | await booking.save();
138 | await user.save();
139 | }
140 | }
141 | };
142 |
143 | //completeBooking... completes a booking by id
144 | const completeBooking = async (id) => {
145 | var booking = await Booking.findById(id);
146 | if (!booking) return;
147 | else {
148 | booking.status = 'Completed';
149 | await booking.save();
150 | }
151 | };
152 |
153 | module.exports = {
154 | NewBooking,
155 | FindBooking,
156 | DeleteBooking,
157 | apiMoney,
158 | FindByUser,
159 | cancelBooking,
160 | completeBooking,
161 | };
162 |
--------------------------------------------------------------------------------
/services/garage.service.js:
--------------------------------------------------------------------------------
1 | const Garage = require('../models/garage.model');
2 | const Slot = require('../models/slot.model');
3 | const Booking = require('../models/booking.model');
4 |
5 | //Addgarage... receives garage object and creates new garage document in the DB
6 | const AddGarage = async (garageBody) => {
7 | try {
8 | return await Garage.create(garageBody);
9 | } catch (error) {
10 | throw error;
11 | }
12 | };
13 |
14 | //FindGarage... receives garage id and returns garage document
15 | const FindGarage = async (id) => {
16 | try {
17 | const garage = await Garage.findById(id);
18 | return garage;
19 | } catch (err) {
20 | const garage = null;
21 | return garage;
22 | }
23 | };
24 |
25 | //AllGarages... returns all garage documents
26 | const AllGarages = async () => {
27 | return await Garage.find({});
28 | };
29 |
30 | //DeleteGarage... receives garage id and deletes garage document from the DB
31 | const DeleteGarage = async (id) => {
32 | var garage = await FindGarage(id);
33 | if (!garage) throw 'Garage not found';
34 | for (let slot_id of garage.slots) {
35 | var slot = await Slot.findById(slot_id);
36 | for (let booking_id of slot.bookings) {
37 | await Booking.findByIdAndDelete(booking_id);
38 | }
39 | await Slot.findByIdAndDelete(slot_id);
40 | }
41 | await Garage.findByIdAndDelete(id);
42 | };
43 |
44 | //ReturnCoords... returns coordinates of all garages
45 | const ReturnCoords = async () => {
46 | const garages = await AllGarages();
47 | var coords = [];
48 | for (let garage of garages) {
49 | coords.push(garage.geometry);
50 | }
51 | return coords;
52 | };
53 |
54 | // DistanceCal... retutrns distance between two points on earth
55 | const DistanceCal = (lat1, lon1, lat2, lon2) => {
56 | lon1 = (lon1 * Math.PI) / 180;
57 | lon2 = (lon2 * Math.PI) / 180;
58 | lat1 = (lat1 * Math.PI) / 180;
59 | lat2 = (lat2 * Math.PI) / 180;
60 | let dlon = lon2 - lon1;
61 | let dlat = lat2 - lat1;
62 | let a =
63 | Math.pow(Math.sin(dlat / 2), 2) +
64 | Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon / 2), 2);
65 | let c = 2 * Math.asin(Math.sqrt(a));
66 | let r = 6371;
67 | return c * r;
68 | };
69 |
70 | module.exports = {
71 | AddGarage,
72 | FindGarage,
73 | AllGarages,
74 | DeleteGarage,
75 | ReturnCoords,
76 | DistanceCal,
77 | };
78 |
--------------------------------------------------------------------------------
/services/slot.service.js:
--------------------------------------------------------------------------------
1 | const Slot = require('../models/slot.model');
2 | const Garage = require('../models/garage.model');
3 | const Booking = require('../models/booking.model');
4 |
5 | //AddSlot... receives slot object and creates new slot document in the DB
6 | const AddSlot = async (slotBody) => {
7 | try {
8 | const result = await Slot.create(slotBody);
9 | const garage = await Garage.findById(slotBody.garage_id);
10 | garage.slots.push(result);
11 | await garage.save();
12 | return result;
13 | } catch (error) {
14 | throw error;
15 | }
16 | };
17 |
18 | //FindSlot... receives slot id and returns slot document
19 | const FindSlot = async (id) => {
20 | try {
21 | const slot = await Slot.findOne({ _id: id });
22 | return slot;
23 | } catch (err) {
24 | return null;
25 | }
26 | };
27 |
28 | //DeleteSlot.. receives slot id and deletes slot document from the DB
29 | const DeleteSlot = async (id) => {
30 | try {
31 | const slot = await Slot.findById(id);
32 | if (!slot) throw 'Slot Not FOund!';
33 | for (let booking_id of slot.bookings) {
34 | await Booking.findByIdAndDelete(booking_id);
35 | }
36 | await Garage.findByIdAndUpdate(slot.garage_id, {
37 | $pull: { slots: id },
38 | });
39 | await Slot.findByIdAndDelete(id);
40 | } catch (err) {
41 | throw err;
42 | }
43 | };
44 |
45 | module.exports = {
46 | AddSlot,
47 | FindSlot,
48 | DeleteSlot,
49 | };
50 |
--------------------------------------------------------------------------------
/services/user.service.js:
--------------------------------------------------------------------------------
1 | const User = require('../models/user.model');
2 | const jwt = require('jsonwebtoken');
3 | const bcrypt = require('bcrypt');
4 | const nodemailer = require('nodemailer');
5 | const helper = require('../utils/helper');
6 | const Transaction = require('../models/transaction.model');
7 | const axios = require('axios');
8 |
9 | /* ------------ JWT Configs ----------- */
10 |
11 | const expiry_length = parseInt(process.env.EXPIRY) * 86400;
12 | const jwt_headers = {
13 | algorithm: 'HS256',
14 | expiresIn: expiry_length,
15 | };
16 |
17 | /* ------------ Services ----------- */
18 |
19 | //sendmail... sends email to the user using nodemailer
20 | async function sendmail(to, subject, otp) {
21 | const options = {
22 | method: 'POST',
23 | url: 'https://rapidprod-sendgrid-v1.p.rapidapi.com/mail/send',
24 | headers: {
25 | 'content-type': 'application/json',
26 | 'X-RapidAPI-Host': process.env.X_RAPIDAPI_HOST,
27 | 'X-RapidAPI-Key': process.env.X_RAPIDAPI_KEY,
28 | },
29 | data: `{"personalizations":[{"to":[{"email":"${to}"}],"subject":"${subject}"}],"from":{"email":"no-reply@parkify-web.herokuapp.com"},"content":[{"type":"text/plain","value":"Your otp is : ${otp}"}]}`,
30 | };
31 | axios
32 | .request(options)
33 | .then(function (response) {
34 | console.log('Email sent: ' + response.data);
35 | })
36 | .catch(function (error) {
37 | console.error(error);
38 | });
39 | }
40 |
41 | // Register... receives user object and creates new user document in the DB
42 | const Register = async (userBody) => {
43 | try {
44 | const user = await User.create(userBody);
45 | const accessToken = jwt.sign(
46 | { username: user.username, user_id: user._id },
47 | process.env.JWT_SECRET,
48 | jwt_headers
49 | );
50 | return {
51 | token: accessToken,
52 | user: user,
53 | };
54 | } catch (error) {
55 | throw error;
56 | }
57 | };
58 |
59 | //GenerateOtp... generates otp, encodes it by jwt and sends it to the user's email
60 | const generateOtp = async (username) => {
61 | var otp = helper.getRandomOtp(6);
62 | const user = await User.findOne({ username });
63 | if (!user) throw 'User doesnt exist';
64 | await sendmail(user.email, 'OTP', otp);
65 | const accessToken = jwt.sign(
66 | { username: user.username, otp: otp },
67 | process.env.JWT_SECRET,
68 | jwt_headers
69 | );
70 | return {
71 | token: accessToken,
72 | otp: otp,
73 | user: user,
74 | };
75 | };
76 |
77 | // Login... receives email, password and validates the user login
78 | const Login = async (username, password) => {
79 | const user = await User.findOne({ username });
80 |
81 | if (!user) throw 'Invalid Username or Password';
82 |
83 | if (!(await bcrypt.compare(password, user.password)))
84 | throw 'Invalid Username or Password';
85 |
86 | const accessToken = jwt.sign(
87 | { username: user.username, user_id: user._id },
88 | process.env.JWT_SECRET,
89 | jwt_headers
90 | );
91 |
92 | return {
93 | token: accessToken,
94 | user: user,
95 | };
96 | };
97 |
98 | //verified... sets the verified field to true
99 | const verified = async (username) => {
100 | const user = await User.findOne({ username });
101 | if (!user) throw 'User Doesnt Exist';
102 | user.verified = true;
103 | await user.save();
104 | };
105 |
106 | //updateImage... updates the user's profile picture
107 | const updateImage = async (userid, path) => {
108 | try {
109 | await User.findOneAndUpdate({ _id: userid }, { picture_url: path });
110 | } catch (err) {
111 | throw err;
112 | }
113 | };
114 |
115 | //addMoney... adds money to the user's wallet
116 | const addMoney = async (user_id, added_money) => {
117 | const user = await User.findById(user_id);
118 | if (!user) throw 'User Doesnt Exist';
119 | else {
120 | var money = parseFloat(user.money);
121 | money += parseFloat(added_money);
122 | user.money = money;
123 | const transaction = {
124 | user_id: user._id,
125 | type: 'credit',
126 | amount: parseFloat(added_money),
127 | remarks: 'add_fund',
128 | };
129 | await Transaction.create(transaction);
130 | await user.save();
131 | }
132 | };
133 |
134 | //getTransactions... returns the user's transactions
135 | const getTransactions = async (id) => {
136 | var transactions = await Transaction.find({ user_id: id });
137 | return transactions;
138 | };
139 |
140 | module.exports = {
141 | Register,
142 | Login,
143 | generateOtp,
144 | verified,
145 | updateImage,
146 | addMoney,
147 | getTransactions,
148 | };
149 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./src/**/*.{js,jsx,ts,tsx,ejs}",
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | }
--------------------------------------------------------------------------------
/utils/helper.js:
--------------------------------------------------------------------------------
1 | // function to get random generated string of specified length
2 | const getRandomOtp = (length) => {
3 | var result = '';
4 | var characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
5 | var charactersLength = characters.length;
6 | for (var i = 0; i < length; i++) {
7 | result += characters.charAt(
8 | Math.floor(Math.random() * charactersLength)
9 | );
10 | }
11 | return result;
12 | };
13 |
14 | module.exports = {
15 | getRandomOtp,
16 | };
17 |
--------------------------------------------------------------------------------
/views/bookings/newbooking.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | New Booking
5 | <%- include('../partials/header')%>
6 |
7 |
8 | <%- include('../partials/navbar.ejs')%>
9 | <%- include('../partials/alerts')%>
10 |
11 |
12 |
13 |
14 |
15 |
16 |
New Booking
17 |
For Slot <%=slot.name%> of <%=garage.name%> Garage.
18 | For <%=slot.type%> Cars. Price/Minute is <%=slot.price%>.
19 |
20 |
You can Book for one month prior only
21 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/views/garages/allgarages.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | All Garages
5 | <%- include('../partials/header')%>
6 |
7 | <%- include('../partials/maps') %>
8 |
9 |
10 |
11 | <%- include('../partials/navbar.ejs')%>
12 | <%- include('../partials/alerts')%>
13 |
14 |
15 |
All Garages
16 |
17 | <% if(body.user_type==="admin") {%>
18 |
Add Garage
19 | <% } %>
20 |
21 |
22 |
28 |
29 | <% for (let garage of garages){%>
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
<%= garage.name %>
38 |
39 | <%= garage.location%>(<%=garage.location_cat%>)
40 |
41 |
42 | Number of Slots:<%=garage.slots.length%> ( )
43 |
44 |
45 |
View Garage
46 |
View Slots
47 |
48 |
49 |
50 |
51 | <% }%>
52 |
53 |
54 |
55 |
59 |
60 |
--------------------------------------------------------------------------------
/views/garages/findgarage.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Find Garages
5 | <%- include('../partials/header')%>
6 |
7 |
8 |
9 | <%- include('../partials/navbar.ejs')%>
10 | <%- include('../partials/alerts')%>
11 |
12 |
13 |
Find Garage
14 |
15 |
18 |
19 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/views/garages/foundgarage.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Found Garages
5 | <%- include('../partials/header')%>
6 |
7 | <%- include('../partials/maps') %>
8 |
9 |
10 | <%- include('../partials/navbar.ejs')%>
11 | <%- include('../partials/alerts')%>
12 |
13 |
14 |
Found Garages by <%=by%> near you
15 | (Within <%=Math.round(min_distance*100)/100%> km radius)
16 |
17 |
18 |
19 |
20 |
21 |
31 |
--------------------------------------------------------------------------------
/views/garages/newgarages.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | New Garages
5 | <%- include('../partials/header')%>
6 |
7 |
8 |
9 | <%- include('../partials/navbar.ejs')%>
10 | <%- include('../partials/alerts')%>
11 | New Garages
12 |
13 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/views/garages/viewgarage.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Garage <%=garage.name%>
5 | <%- include('../partials/header')%>
6 | <%- include('../partials/maps') %>
7 |
8 |
9 |
10 | <%- include('../partials/navbar.ejs')%>
11 | <%- include('../partials/alerts')%>
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
<%= garage.name%>
21 |
Owned by <%= garage.owner%>
22 |
23 |
24 | Size- <%=garage.size%>
25 | Slots- <%=garage.slots.length%>( )
26 | View Slots
27 |
28 | <%= garage.location%> (<%= garage.location_cat%>)
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | <% if(body.user_type==="admin") {%>
38 |
42 | <% } %>
43 |
44 |
45 |
46 |
47 |
48 |
52 |
--------------------------------------------------------------------------------
/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Parkify
8 |
10 |
11 |
12 |
13 |
14 |
15 | <%- include('partials/alerts.ejs') %>
16 |
17 |
28 |
29 | Parkify
30 | Welcome to Parkify! A simple way to solve your complex parking issues.
31 |
32 | Github link for the project
33 |
34 |
35 |
38 |
39 |
40 |
41 |
42 |
43 |
46 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/views/not-found.ejs:
--------------------------------------------------------------------------------
1 | <%- include('partials/navbar.ejs')%>
2 | <%- include('partials/alerts.ejs') %>
3 | Not Found
--------------------------------------------------------------------------------
/views/partials/alerts.ejs:
--------------------------------------------------------------------------------
1 | <% if (locals.messages.err) { %>
2 |
10 | <% } %>
11 | <% if (locals.messages.success) { %>
12 |
20 | <% } %>
21 | <% if (locals.messages.info) { %>
22 |
30 | <% } %>
31 | <% if (locals.messages.alert) { %>
32 |
40 | <% } %>
--------------------------------------------------------------------------------
/views/partials/footer.ejs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/views/partials/header.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/views/partials/maps.ejs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/views/partials/navbar.ejs:
--------------------------------------------------------------------------------
1 |
2 |
67 |
--------------------------------------------------------------------------------
/views/slots/addslot.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | New Slots
5 | <%- include('../partials/header')%>
6 |
7 |
8 | <%- include('../partials/navbar.ejs')%>
9 | <%- include('../partials/alerts')%>
10 | New Slots
11 |
12 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/views/slots/allslots.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | All Slots
5 | <%- include('../partials/header')%>
6 |
7 |
8 | <%- include('../partials/navbar.ejs')%>
9 | <%- include('../partials/alerts')%>
10 |
11 |
12 |
All Slots for <%=garage.name%>.
13 |
14 | <% for (var slot of slots) {%>
15 |
16 |
17 |
18 |
19 |
Slot Name- <%= slot.name %>
20 |
21 | For <%=slot.type%> Cars.
22 | Price/Hour- <%=slot.price%>
23 |
24 |
View Slot
25 |
26 |
Book Slot
27 |
28 |
29 |
30 |
31 | <% }%>
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/views/slots/slot.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Slot
5 | <%- include('../partials/header')%>
6 | <%- include('../partials/navbar')%>
7 |
8 |
9 |
10 | <%- include('../partials/alerts')%>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
<%= slot.name%> of Garage <%=garage.name%>
18 |
For <%=slot.type%> Cars. Price/min- <%=slot.price%>
19 |
20 |
21 |
Book Slot
22 |
Back to Slots from Garage <%=garage.name%>
23 |
24 | <% if(body.user_type==="admin") {%>
25 |
28 | <% } %>
29 |
30 |
31 | <% if(bookings.length!==0){%>
32 |
Bookings
33 |
34 | <%for(let booking of bookings){ if(booking.status==="Cancelled"){continue;}%>
35 | <%=new Date(booking.start_time*1000)%> - <%=new Date(booking.end_time*1000)%>
36 | <% }%>
37 | <% }else{%>
38 | No Bookings Found!
39 | <%} %>
40 |
41 |
42 |
43 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/views/users/addmoney.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Add Money
5 | <%- include('../partials/header')%>
6 |
7 |
8 | <%- include('../partials/navbar.ejs')%>
9 | <%- include('../partials/alerts')%>
10 |
11 |
Add Money
12 | Current Balance - <%=Math.round(money*100)/100%>
13 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/views/users/bookings.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Dashboard
9 | <%- include('../partials/header.ejs') %>
10 |
11 |
12 | <%- include('../partials/navbar.ejs')%>
13 | <%- include('../partials/alerts.ejs') %>
14 |
15 |
All Bookings
16 |
17 |
Your Bookings:
18 |
19 |
20 | <% for(let booking of bookings){%>
21 | <% var startTime = new Date(booking.start_time*1000) %>
22 | <% var endTime = new Date(booking.end_time*1000) %>
23 |
24 |
25 |
26 |
27 | Status-
28 | <%if(booking.status === 'Cancelled') { %>
29 | <%=booking.status %>
30 | <% }else if(booking.status === 'Booked') { %>
31 | <%=booking.status %>
32 | <% }else if(booking.status === 'Completed') { %>
33 | <%=booking.status %>
34 | <% } %>
35 |
36 |
37 |
38 | Starts From- Date : <%=startTime.toDateString()%> Time: <%=startTime.getHours() + ":" + startTime.getMinutes()%>
39 |
40 |
41 | Ends On- Date : <%=endTime.toDateString()%> Time: <%=endTime.getHours() + ":" + endTime.getMinutes()%>
42 |
43 |
44 |
45 | <% if(booking.status==="Booked"){ %>
46 |
49 | <% }else if(booking.status==="Completed") { %>
50 |
53 | <% }else if(booking.status==="Cancelled") { %>
54 |
57 | <% } %>
58 |
59 |
60 | <% } %>
61 |
62 | <% if(!bookings){ %>
63 | No Bookings Found!
64 | <% } %>
65 | %>
66 |
67 |
68 | <%- include('../partials/footer.ejs') %>
--------------------------------------------------------------------------------
/views/users/dashboard.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Dashboard
9 | <%- include('../partials/header.ejs') %>
10 |
11 |
12 | <%- include('../partials/navbar.ejs')%>
13 | <%- include('../partials/alerts.ejs') %>
14 |
15 |
Dashboard
16 | Welcome <%=body.name%>
17 |
18 |
Your Current Bookings:
19 |
20 |
21 | <% for(let booking of bookings){ if(booking.status==="Completed" || booking.status==="Cancelled"){continue;}%>
22 | <% var startTime = new Date(booking.start_time*1000) %>
23 | <% var endTime = new Date(booking.end_time*1000) %>
24 |
25 |
26 |
27 |
28 | Status-
29 | <%=booking.status %>
30 |
31 |
32 |
33 | Starts From- Date : <%=startTime.toDateString()%> Time: <%=startTime.getHours() + ":" + startTime.getMinutes()%>
34 |
35 |
36 | Ends On- Date : <%=endTime.toDateString()%> Time: <%=endTime.getHours() + ":" + endTime.getMinutes()%>
37 |
38 |
39 |
40 |
49 |
50 |
51 | <% } %>
52 |
53 | <% if(!bookings){ %>
54 | No Bookings Found!
55 | <% } %>
56 | %>
57 |
58 |
59 | <%- include('../partials/footer.ejs') %>
--------------------------------------------------------------------------------
/views/users/login.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Dashboard
8 | <%- include('../partials/header.ejs') %>
9 |
10 |
11 | <%- include('../partials/navbar.ejs')%>
12 | <%- include('../partials/alerts.ejs') %>
13 |
14 |
15 |
16 |
17 |
18 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/views/users/mybooking.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Slot
5 | <%- include('../partials/header')%>
6 | <%- include('../partials/navbar')%>
7 |
8 |
9 | <%- include('../partials/alerts')%>
10 |
11 |
12 |
13 |
14 |
15 |
16 |
<%= slot.name%> of Garage <%=garage.name%>
17 |
18 |
19 |
20 |
21 |
22 | Status -<%=booking.status%>
23 | Start Time -<%=new Date(booking.start_time*1000)%>
24 | End Time -<%=new Date(booking.end_time*1000)%>
25 | <% if(booking.status!=="Cancelled" && booking.status!=="Completed"){ %>
26 |
29 | <% } %>
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/views/users/register.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Dashboard
8 | <%- include('../partials/header.ejs') %>
9 |
10 |
11 | <%- include('../partials/navbar.ejs')%>
12 | <%- include('../partials/alerts.ejs') %>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Register
20 |
21 |
This website does not use cookies for tracking.
22 | They are only used for a better experience on its services.
23 | For more information on what types of cookies are stored, please contact the support.
24 | By registering, you agree to our privacy policies.
25 |
26 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | <% include('../partials/footer') %>
71 |
72 |
--------------------------------------------------------------------------------
/views/users/transactions.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Transactions
8 | <%- include('../partials/header.ejs') %>
9 |
10 |
11 | <%- include('../partials/navbar.ejs')%>
12 | <%- include('../partials/alerts.ejs') %>
13 |
14 |
Transactions
15 | Your Transactions:
16 |
17 | <% for(let transaction of transactions){ if(transaction.status==="Completed"){continue;}%>
18 | Type -<%=transaction.type%>
19 | <% if(transaction.booking_id){ %>
20 | Booking Id -<%=transaction.booking_id%>
21 | <% } %>
22 | Amount -<%=transaction.amount%>
23 | Remarks -<%=transaction.remarks%>
24 |
25 | <% }if(!transactions){ %>
26 | No transactions Found!
27 | <% } %>
28 | %>
29 |
30 |
31 | <%- include('../partials/footer.ejs') %>
--------------------------------------------------------------------------------
/views/users/uploadimage.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Change Image
5 | <%- include('../partials/header')%>
6 |
7 |
8 | <%- include('../partials/navbar')%>
9 | <%- include('../partials/alerts')%>
10 |
11 |
Change Image
12 | Current Image
13 |
14 |
15 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/views/users/verify.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Verify Email
8 | <%- include('../partials/header.ejs') %>
9 |
10 |
11 |
12 | <%- include('../partials/alerts.ejs') %>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Enter OTP
21 |
Email has been sent to : <%=email%>
22 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------