├── .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 | PARKIFY_LOGO 6 | 7 | 8 |
9 | 10 | 11 | 12 | PARKIFY_TEXT 13 | 14 |

15 | </> with by @codehackerone.

16 |

17 | version 1.0.0 18 | license MIT 19 | 20 | 21 | author codehackerone 22 |

23 |
24 |
25 | 26 | > Hasslefree way to book your parking space with easy cancellations and timeline extensions
27 | 28 | --- 29 | ![cover image](https://github.com/Codehackerone/parkify/blob/main/public/img/coverpage.png) 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 |
22 | 23 | 24 |
25 | 26 | 27 |
28 | Looks good! 29 |
30 |
31 |
32 | 33 | 34 |
35 | Looks good! 36 |
37 |
38 | 39 |
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 |
23 |
24 | 25 | 26 |
27 |
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 |
16 | 17 |
18 |

19 |
20 |
21 | 22 |
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 |
14 |
15 |
16 |
22 |
23 |
24 | 25 | 26 |
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 |
39 | 40 | Create Slot 41 |

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 |
18 |
19 |

Parkify

20 | 26 |
27 |
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 | -------------------------------------------------------------------------------- /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 |
13 |
14 |
15 |
21 |
22 | 23 |
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 |
Back to Garage
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 |
26 | 27 |
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 |
14 | 15 |
16 | 17 |
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 | 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 | 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 |
19 |
Login
20 |
21 |
22 | 23 | 24 |
25 | Looks good! 26 |
27 |
28 | 29 |
30 | 31 | 32 |
33 | Looks good! 34 |
35 |
36 | 37 |
38 |
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 |
27 | 28 |
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 |
27 |
28 | 29 | 30 |
31 | Looks good! 32 |
33 |
34 |
35 | 36 | 37 |
38 | Looks good! 39 |
40 |
41 |
42 | 43 | 44 |
45 | Looks good! 46 |
47 |
48 |
49 | 50 | 51 |
52 | Looks good! 53 |
54 |
55 |
56 | 57 | 58 |
59 | Looks good! 60 |
61 |
62 | 63 |
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 |
16 | 17 |
18 | 19 |
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 |
23 |
24 | 25 |
26 | Looks good! 27 |
28 |
29 | Please Enter Correct OTP 30 |
31 |
32 |
33 | 34 | 36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | --------------------------------------------------------------------------------