├── .gitignore ├── README.md ├── _data ├── bootcamps.json ├── courses.json ├── reviews.json └── users.json ├── app.js ├── config └── db.js ├── controller ├── auth.js ├── bootcamp.js ├── course.js ├── review.js └── users.js ├── middleware ├── advanceResults.js ├── async.js ├── auth.js └── error.js ├── models ├── Bootcamp.js ├── Course.js ├── Review.js └── User.js ├── package-lock.json ├── package.json ├── public └── uploads │ └── photo5d725a1b7b292f5f8ceff788.jpg ├── routes ├── auth.js ├── bootcamp.js ├── course.js ├── review.js └── user.js ├── seeder.js └── utilis ├── createError.js ├── geocoder.js ├── jwt.js └── sendEmail.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | config/config.env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Udemy-Clone-API 2 | 3 | > This is the backend api for udemy like site. You can see all uses and its end-points [Here](https://udemycloneapisajid.netlify.app) 4 | 5 | ## Project Setup 6 | 7 | ```bash 8 | # install dependencies 9 | npm install 10 | 11 | # install globally nodemon 12 | npm install -g nodemon 13 | 14 | ``` 15 | 16 | ## Configuration Setup 17 | 18 | ``` 19 | 20 | PORT=5000 21 | NODE_ENV=development 22 | 23 | MONGO_URI=MongoDb URI 24 | 25 | GEOCODER_PROVIDER=mapquest 26 | GEOCODER_API_KEY=mapquest api key 27 | FILE_UPLOAD_PATH=./public/uploads 28 | FILE_UPLOAD_SIZE=1000000 29 | 30 | JWT_SECRET=thiskeyisScreteDontBother 31 | JWT_EXPIREIN=30d 32 | JWT_COOKIE_EXPRIE=30 33 | 34 | SMTP_HOST=Your smtp host provider 35 | SMTP_PORT=port 36 | SMTP_EMAIL=username 37 | SMTP_PASSWORD=password 38 | FROM_EMAIL=your email 39 | FROM_NAME=your name 40 | 41 | ``` 42 | 43 | ## Run The Seeder 44 | 45 | ```bash 46 | 47 | # for import 48 | node seeder -i 49 | 50 | # for delete 51 | node seeder -d 52 | 53 | ``` 54 | 55 | ## Run App 56 | 57 | ```bash 58 | 59 | # Run in dev mode 60 | npm run dev 61 | 62 | # Run in prod mode 63 | npm start 64 | 65 | ``` 66 | 67 | - version: 1.0.0 68 | - License: MIT 69 | - author: Sajid Ansari. 70 | -------------------------------------------------------------------------------- /_data/bootcamps.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "5d713995b721c3bb38c1f5d0", 4 | "user": "5d7a514b5d2c12c7449be045", 5 | "name": "Devworks Bootcamp", 6 | "description": "Devworks is a full stack JavaScript Bootcamp located in the heart of Boston that focuses on the technologies you need to get a high paying job as a web developer", 7 | "website": "https://devworks.com", 8 | "phone": "(111) 111-1111", 9 | "email": "enroll@devworks.com", 10 | "address": "233 Bay State Rd Boston MA 02215", 11 | "careers": ["Web Development", "UI/UX", "Business"], 12 | "housing": true, 13 | "jobAssistance": true, 14 | "jobGuarantee": false, 15 | "acceptGi": true 16 | }, 17 | { 18 | "_id": "5d713a66ec8f2b88b8f830b8", 19 | "user": "5d7a514b5d2c12c7449be046", 20 | "name": "ModernTech Bootcamp", 21 | "description": "ModernTech has one goal, and that is to make you a rockstar developer and/or designer with a six figure salary. We teach both development and UI/UX", 22 | "website": "https://moderntech.com", 23 | "phone": "(222) 222-2222", 24 | "email": "enroll@moderntech.com", 25 | "address": "220 Pawtucket St, Lowell, MA 01854", 26 | "careers": ["Web Development", "UI/UX", "Mobile Development"], 27 | "housing": false, 28 | "jobAssistance": true, 29 | "jobGuarantee": false, 30 | "acceptGi": true 31 | }, 32 | { 33 | "_id": "5d725a037b292f5f8ceff787", 34 | "user": "5c8a1d5b0190b214360dc031", 35 | "name": "Codemasters", 36 | "description": "Is coding your passion? Codemasters will give you the skills and the tools to become the best developer possible. We specialize in full stack web development and data science", 37 | "website": "https://codemasters.com", 38 | "phone": "(333) 333-3333", 39 | "email": "enroll@codemasters.com", 40 | "address": "85 South Prospect Street Burlington VT 05405", 41 | "careers": ["Web Development", "Data Science", "Business"], 42 | "housing": false, 43 | "jobAssistance": false, 44 | "jobGuarantee": false, 45 | "acceptGi": false 46 | }, 47 | { 48 | "_id": "5d725a1b7b292f5f8ceff788", 49 | "user": "5c8a1d5b0190b214360dc032", 50 | "name": "Devcentral Bootcamp", 51 | "description": "Is coding your passion? Codemasters will give you the skills and the tools to become the best developer possible. We specialize in front end and full stack web development", 52 | "website": "https://devcentral.com", 53 | "phone": "(444) 444-4444", 54 | "email": "enroll@devcentral.com", 55 | "address": "45 Upper College Rd Kingston RI 02881", 56 | "careers": [ 57 | "Mobile Development", 58 | "Web Development", 59 | "Data Science", 60 | "Business" 61 | ], 62 | "housing": false, 63 | "jobAssistance": true, 64 | "jobGuarantee": true, 65 | "acceptGi": true 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /_data/courses.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "5d725a4a7b292f5f8ceff789", 4 | "title": "Front End Web Development", 5 | "description": "This course will provide you with all of the essentials to become a successful frontend web developer. You will learn to master HTML, CSS and front end JavaScript, along with tools like Git, VSCode and front end frameworks like Vue", 6 | "weeks": 8, 7 | "tuition": 8000, 8 | "minimumSkill": "beginner", 9 | "scholarhipsAvailable": true, 10 | "bootcamp": "5d713995b721c3bb38c1f5d0", 11 | "user": "5d7a514b5d2c12c7449be045" 12 | }, 13 | { 14 | "_id": "5d725c84c4ded7bcb480eaa0", 15 | "title": "Full Stack Web Development", 16 | "description": "In this course you will learn full stack web development, first learning all about the frontend with HTML/CSS/JS/Vue and then the backend with Node.js/Express/MongoDB", 17 | "weeks": 12, 18 | "tuition": 10000, 19 | "minimumSkill": "intermediate", 20 | "scholarhipsAvailable": true, 21 | "bootcamp": "5d713995b721c3bb38c1f5d0", 22 | "user": "5d7a514b5d2c12c7449be045" 23 | }, 24 | { 25 | "_id": "5d725cb9c4ded7bcb480eaa1", 26 | "title": "Full Stack Web Dev", 27 | "description": "In this course you will learn all about the front end with HTML, CSS and JavaScript. You will master tools like Git and Webpack and also learn C# and ASP.NET with Postgres", 28 | "weeks": 10, 29 | "tuition": 12000, 30 | "minimumSkill": "intermediate", 31 | "scholarhipsAvailable": true, 32 | "bootcamp": "5d713a66ec8f2b88b8f830b8", 33 | "user": "5d7a514b5d2c12c7449be046" 34 | }, 35 | { 36 | "_id": "5d725cd2c4ded7bcb480eaa2", 37 | "title": "UI/UX", 38 | "description": "In this course you will learn to create beautiful interfaces. It is a mix of design and development to create modern user experiences on both web and mobile", 39 | "weeks": 12, 40 | "tuition": 10000, 41 | "minimumSkill": "intermediate", 42 | "scholarhipsAvailable": true, 43 | "bootcamp": "5d713a66ec8f2b88b8f830b8", 44 | "user": "5d7a514b5d2c12c7449be046" 45 | }, 46 | { 47 | "_id": "5d725ce8c4ded7bcb480eaa3", 48 | "title": "Web Design & Development", 49 | "description": "Get started building websites and web apps with HTML/CSS/JavaScript/PHP. We teach you", 50 | "weeks": 10, 51 | "tuition": 9000, 52 | "minimumSkill": "beginner", 53 | "scholarhipsAvailable": true, 54 | "bootcamp": "5d725a037b292f5f8ceff787", 55 | "user": "5c8a1d5b0190b214360dc031" 56 | }, 57 | { 58 | "_id": "5d725cfec4ded7bcb480eaa4", 59 | "title": "Data Science Program", 60 | "description": "In this course you will learn Python for data science, machine learning and big data tools", 61 | "weeks": 10, 62 | "tuition": 12000, 63 | "minimumSkill": "intermediate", 64 | "scholarhipsAvailable": false, 65 | "bootcamp": "5d725a037b292f5f8ceff787", 66 | "user": "5c8a1d5b0190b214360dc031" 67 | }, 68 | { 69 | "_id": "5d725cfec4ded7bcb480eaa5", 70 | "title": "Web Development", 71 | "description": "This course will teach you how to build high quality web applications with technologies like React, Node.js, PHP & Laravel", 72 | "weeks": 8, 73 | "tuition": 8000, 74 | "minimumSkill": "beginner", 75 | "scholarhipsAvailable": false, 76 | "bootcamp": "5d725a1b7b292f5f8ceff788", 77 | "user": "5c8a1d5b0190b214360dc032" 78 | }, 79 | { 80 | "_id": "5d725cfec4ded7bcb480eaa6", 81 | "title": "Software QA", 82 | "description": "This course will teach you everything you need to know about quality assurance", 83 | "weeks": 6, 84 | "tuition": 5000, 85 | "minimumSkill": "intermediate", 86 | "scholarhipsAvailable": false, 87 | "bootcamp": "5d725a1b7b292f5f8ceff788", 88 | "user": "5c8a1d5b0190b214360dc032" 89 | }, 90 | { 91 | "_id": "5d725cfec4ded7bcb480eaa7", 92 | "title": "IOS Development", 93 | "description": "Get started building mobile applications for IOS using Swift and other tools", 94 | "weeks": 8, 95 | "tuition": 6000, 96 | "minimumSkill": "intermediate", 97 | "scholarhipsAvailable": false, 98 | "bootcamp": "5d725a1b7b292f5f8ceff788", 99 | "user": "5c8a1d5b0190b214360dc032" 100 | } 101 | ] 102 | -------------------------------------------------------------------------------- /_data/reviews.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "5d7a514b5d2c12c7449be020", 4 | "title": "Learned a ton!", 5 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque", 6 | "rating": "8", 7 | "bootcamp": "5d713995b721c3bb38c1f5d0", 8 | "user": "5c8a1d5b0190b214360dc033" 9 | }, 10 | { 11 | "_id": "5d7a514b5d2c12c7449be021", 12 | "title": "Great bootcamp", 13 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque", 14 | "rating": "10", 15 | "bootcamp": "5d713995b721c3bb38c1f5d0", 16 | "user": "5c8a1d5b0190b214360dc034" 17 | }, 18 | { 19 | "_id": "5d7a514b5d2c12c7449be022", 20 | "title": "Got me a developer job", 21 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque", 22 | "rating": "7", 23 | "bootcamp": "5d713a66ec8f2b88b8f830b8", 24 | "user": "5c8a1d5b0190b214360dc035" 25 | }, 26 | { 27 | "_id": "5d7a514b5d2c12c7449be023", 28 | "title": "Not that great", 29 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque", 30 | "rating": "4", 31 | "bootcamp": "5d713a66ec8f2b88b8f830b8", 32 | "user": "5c8a1d5b0190b214360dc036" 33 | }, 34 | { 35 | "_id": "5d7a514b5d2c12c7449be024", 36 | "title": "Great overall experience", 37 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque", 38 | "rating": "7", 39 | "bootcamp": "5d725a037b292f5f8ceff787", 40 | "user": "5c8a1d5b0190b214360dc037" 41 | }, 42 | { 43 | "_id": "5d7a514b5d2c12c7449be025", 44 | "title": "Not worth the money", 45 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque", 46 | "rating": "5", 47 | "bootcamp": "5d725a037b292f5f8ceff787", 48 | "user": "5c8a1d5b0190b214360dc038" 49 | }, 50 | { 51 | "_id": "5d7a514b5d2c12c7449be026", 52 | "title": "Best instructors", 53 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque", 54 | "rating": "10", 55 | "bootcamp": "5d725a1b7b292f5f8ceff788", 56 | "user": "5c8a1d5b0190b214360dc039" 57 | }, 58 | { 59 | "_id": "5d7a514b5d2c12c7449be027", 60 | "title": "Was worth the investment", 61 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque", 62 | "rating": "7", 63 | "bootcamp": "5d725a1b7b292f5f8ceff788", 64 | "user": "5c8a1d5b0190b214360dc040" 65 | } 66 | ] 67 | -------------------------------------------------------------------------------- /_data/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "5d7a514b5d2c12c7449be042", 4 | "name": "Admin Account", 5 | "email": "admin@gmail.com", 6 | "role": "user", 7 | "password": "12345678" 8 | }, 9 | { 10 | "_id": "5d7a514b5d2c12c7449be043", 11 | "name": "Publisher Account", 12 | "email": "publisher@gmail.com", 13 | "role": "publisher", 14 | "password": "12345678" 15 | }, 16 | { 17 | "_id": "5d7a514b5d2c12c7449be044", 18 | "name": "User Account", 19 | "email": "user@gmail.com", 20 | "role": "user", 21 | "password": "12345678" 22 | }, 23 | { 24 | "_id": "5d7a514b5d2c12c7449be045", 25 | "name": "John Doe", 26 | "email": "john@gmail.com", 27 | "role": "publisher", 28 | "password": "12345678" 29 | }, 30 | { 31 | "_id": "5d7a514b5d2c12c7449be046", 32 | "name": "Kevin Smith", 33 | "email": "kevin@gmail.com", 34 | "role": "publisher", 35 | "password": "12345678" 36 | }, 37 | { 38 | "_id": "5c8a1d5b0190b214360dc031", 39 | "name": "Mary Williams", 40 | "email": "mary@gmail.com", 41 | "role": "publisher", 42 | "password": "12345678" 43 | }, 44 | { 45 | "_id": "5c8a1d5b0190b214360dc032", 46 | "name": "Sasha Ryan", 47 | "email": "sasha@gmail.com", 48 | "role": "publisher", 49 | "password": "12345678" 50 | }, 51 | { 52 | "_id": "5c8a1d5b0190b214360dc033", 53 | "name": "Greg Harris", 54 | "email": "greg@gmail.com", 55 | "role": "user", 56 | "password": "12345678" 57 | }, 58 | { 59 | "_id": "5c8a1d5b0190b214360dc034", 60 | "name": "Derek Glover", 61 | "email": "derek@gmail.com", 62 | "role": "user", 63 | "password": "12345678" 64 | }, 65 | { 66 | "_id": "5c8a1d5b0190b214360dc035", 67 | "name": "Stephanie Hanson", 68 | "email": "steph@gmail.com", 69 | "role": "user", 70 | "password": "12345678" 71 | }, 72 | { 73 | "_id": "5c8a1d5b0190b214360dc036", 74 | "name": "Jerry Wiliams", 75 | "email": "jerry@gmail.com", 76 | "role": "user", 77 | "password": "12345678" 78 | }, 79 | { 80 | "_id": "5c8a1d5b0190b214360dc037", 81 | "name": "Maggie Johnson", 82 | "email": "maggie@gmail.com", 83 | "role": "user", 84 | "password": "12345678" 85 | }, 86 | { 87 | "_id": "5c8a1d5b0190b214360dc038", 88 | "name": "Barry Dickens", 89 | "email": "barry@gmail.com", 90 | "role": "user", 91 | "password": "12345678" 92 | }, 93 | { 94 | "_id": "5c8a1d5b0190b214360dc039", 95 | "name": "Ryan Bolin", 96 | "email": "ryan@gmail.com", 97 | "role": "user", 98 | "password": "12345678" 99 | }, 100 | { 101 | "_id": "5c8a1d5b0190b214360dc040", 102 | "name": "Sara Kensing", 103 | "email": "sara@gmail.com", 104 | "role": "user", 105 | "password": "12345678" 106 | } 107 | ] 108 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const dotenv = require("dotenv"); 3 | const colors = require("colors"); 4 | const { unknownEndpoints, errorHandler } = require("./middleware/error"); 5 | const connectDb = require("./config/db"); 6 | const fileUpload = require("express-fileupload"); 7 | const cookieParser = require("cookie-parser"); 8 | const mongoSanitize = require("express-mongo-sanitize"); 9 | const helmet = require("helmet"); 10 | const xss = require("xss-clean"); 11 | const rateLimit = require("express-rate-limit"); 12 | const hpp = require("hpp"); 13 | const cors = require("cors"); 14 | const path = require("path"); 15 | const app = express(); 16 | 17 | dotenv.config({ path: "./config/config.env" }); 18 | 19 | connectDb(); 20 | 21 | //Routes files 22 | const bootcampRouter = require("./routes/bootcamp"); 23 | const courseRouter = require("./routes/course"); 24 | const authRouter = require("./routes/auth"); 25 | const userRouter = require("./routes/user"); 26 | const reviewRouter = require("./routes/review"); 27 | 28 | app.use(express.json()); 29 | 30 | app.use(fileUpload()); 31 | app.use(cookieParser()); 32 | 33 | //Sanitize data 34 | app.use(mongoSanitize()); 35 | 36 | // Set security headers 37 | app.use(helmet()); 38 | 39 | //Prevent XSS attack 40 | app.use(xss()); 41 | 42 | //Rate Limiting 43 | const limiter = rateLimit({ 44 | windowMs: 10 * 60 * 1000, 45 | max: 100, 46 | }); 47 | 48 | //apply to all requests 49 | app.use(limiter); 50 | 51 | //Prevent http param pollution 52 | app.use(hpp()); 53 | 54 | //Enamble CORS 55 | app.use(cors()); 56 | 57 | app.use(express.static(path.join(__dirname, "public"))); 58 | 59 | app.use("/api/v1/bootcamp", bootcampRouter); 60 | app.use("/api/v1/course", courseRouter); 61 | app.use("/api/v1/auth", authRouter); 62 | app.use("/api/v1/user", userRouter); 63 | app.use("/api/v1/review", reviewRouter); 64 | 65 | app.use(unknownEndpoints); 66 | app.use(errorHandler); 67 | 68 | const PORT = process.env.PORT || 3000; 69 | 70 | const server = app.listen( 71 | PORT, 72 | console.log( 73 | `Server running in ${process.env.NODE_ENV} mode on port ${PORT}`.yellow.bold 74 | ) 75 | ); 76 | 77 | //Handle unhandle promise rejection 78 | 79 | process.on("unhandledRejection", (err, promise) => { 80 | console.log(`Error: ${err.message}`.red.bold); 81 | //close the server 82 | server.close(() => process.exit(1)); 83 | }); 84 | -------------------------------------------------------------------------------- /config/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const connectDB = async () => { 4 | const conn = await mongoose.connect(process.env.MONGO_URI, { 5 | useNewUrlParser: true, 6 | useCreateIndex: true, 7 | useFindAndModify: false, 8 | useUnifiedTopology: true, 9 | }); 10 | console.log( 11 | `Mongo database connected on ${conn.connection.host}`.cyan.underline.bold 12 | ); 13 | }; 14 | 15 | module.exports = connectDB; 16 | -------------------------------------------------------------------------------- /controller/auth.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | const createError = require("../utilis/createError"); 3 | const asyncHandler = require("../middleware/async"); 4 | const sendEmail = require("../utilis/sendEmail"); 5 | const User = require("../models/User"); 6 | 7 | const RegisterUser = asyncHandler(async (req, res, next) => { 8 | const newUser = await User.create(req.body); 9 | 10 | sendTokenResponse(newUser, 200, res); 11 | }); 12 | 13 | const login = asyncHandler(async (req, res, next) => { 14 | const user = await User.findOne({ email: req.body.email }).select( 15 | "+password" 16 | ); 17 | if (!user) throw createError(401, `Email doesn't match`); 18 | 19 | const isPassword = await user.matchPassword(req.body.password); 20 | if (!isPassword) throw createError(401, `Password doesn't match`); 21 | 22 | sendTokenResponse(user, 200, res); 23 | }); 24 | 25 | //Update user details 26 | 27 | const updateDetails = asyncHandler(async (req, res, next) => { 28 | const newDetails = { 29 | name: req.body.name, 30 | email: req.body.email, 31 | }; 32 | 33 | console.log(req.user); 34 | const editDetails = await User.findByIdAndUpdate(req.user._id, newDetails, { 35 | new: true, 36 | runValidators: true, 37 | }); 38 | 39 | const updateDetails = await User.findById(req.user._id); 40 | res.status(200).send({ status: "success", data: updateDetails }); 41 | }); 42 | 43 | //Update Password 44 | 45 | const updatePassword = asyncHandler(async (req, res, next) => { 46 | const user = await User.findById(req.user._id).select("+password"); 47 | 48 | //compare currentPassword 49 | 50 | const isMatch = await user.matchPassword(req.body.currentPassword); 51 | console.log(isMatch); 52 | if (!isMatch) 53 | throw createError( 54 | 400, 55 | `Current password ${req.body.currentPassword} does't match` 56 | ); 57 | 58 | user.password = req.body.newPassword; 59 | 60 | await user.save(); 61 | 62 | sendTokenResponse(user, 200, res); 63 | }); 64 | 65 | //Forgot Password 66 | 67 | const forgotPassword = asyncHandler(async (req, res, next) => { 68 | const user = await User.findOne({ email: req.body.email }); 69 | 70 | if (!user) 71 | throw createError(400, `User with email ${req.body.email} is not found`); 72 | 73 | const resetToken = user.getResetPasswordToken(); 74 | 75 | await user.save({ validateBeforeSave: false }); 76 | 77 | try { 78 | const resetUrl = `${req.protocol}://${req.get( 79 | "host" 80 | )}/api/v1/auth/resetPassword/${resetToken}`; 81 | 82 | const message = `You are receiving this email because you (or someone else ) has 83 | requested the reset of a password. Please make a PUT request to:\n\n ${resetUrl}`; 84 | 85 | const options = { 86 | email: user.email, 87 | subject: "Password reset token", 88 | message, 89 | }; 90 | 91 | await sendEmail(options); 92 | 93 | res 94 | .status(200) 95 | .send({ status: "success", data: "ResetPassword Email sent" }); 96 | } catch (error) { 97 | console.log(error); 98 | 99 | user.resetPasswordToken = undefined; 100 | user.resetPasswordExpire = undefined; 101 | 102 | await user.save({ validateBeforeSave: false }); 103 | 104 | throw createError(500, "Email cound't be sent"); 105 | } 106 | }); 107 | 108 | //ResetPassword 109 | 110 | const resetPassword = asyncHandler(async (req, res, next) => { 111 | //Hash the resetToken 112 | 113 | const resetToken = crypto 114 | .createHash("sha256") 115 | .update(req.params.resetToken) 116 | .digest("hex"); 117 | 118 | const user = await User.findOne({ 119 | resetPasswordToken: resetToken, 120 | resetPasswordExpire: { $gt: Date.now() }, 121 | }); 122 | 123 | if (!user) throw createError(400, `Invalid token ${req.params.resetToken}`); 124 | 125 | user.password = req.body.newPassword; 126 | user.resetPasswordToken = undefined; 127 | user.resetPasswordExpire = undefined; 128 | 129 | await user.save(); 130 | 131 | sendTokenResponse(user, 200, res); 132 | }); 133 | 134 | const sendTokenResponse = (user, statusCode, res) => { 135 | const token = user.genAuthToken(); 136 | 137 | const options = { 138 | expire: new Date( 139 | Date.now() + process.env.JWT_COOKIE_EXPRIE * 24 * 60 * 60 * 1000 140 | ), 141 | httpOnly: true, 142 | }; 143 | 144 | if (process.env.NODE_ENV === "production") { 145 | options.secure = true; 146 | } 147 | 148 | res 149 | .status(statusCode) 150 | .cookie("token", token, options) 151 | .send({ status: "success", token }); 152 | }; 153 | 154 | const logout = asyncHandler(async (req, res, next) => { 155 | res.cookie("token", "none", { 156 | expire: new Date(Date.now() * 10 * 60 * 1000), 157 | httpOnly: true, 158 | }); 159 | res.status(200).send({ status: "success", data: {} }); 160 | }); 161 | 162 | module.exports = { 163 | RegisterUser, 164 | login, 165 | updateDetails, 166 | updatePassword, 167 | forgotPassword, 168 | resetPassword, 169 | logout, 170 | }; 171 | -------------------------------------------------------------------------------- /controller/bootcamp.js: -------------------------------------------------------------------------------- 1 | const createError = require("../utilis/createError"); 2 | const asyncHandler = require("../middleware/async"); 3 | const path = require("path"); 4 | const geocoder = require("../utilis/geocoder"); 5 | const Bootcamp = require("../models/Bootcamp"); 6 | 7 | const getAllbootcamps = asyncHandler(async (req, res, next) => { 8 | res.status(200).send(res.advanceResults); 9 | }); 10 | 11 | const getBootcamps = asyncHandler(async (req, res, next) => { 12 | const getBootcampsByid = await Bootcamp.findById(req.params.id); 13 | 14 | if (!getBootcampsByid) 15 | throw createError(404, `Bootcamp is not found of id ${req.params.id}`); 16 | 17 | res.status(200).send({ status: "success", data: getBootcampsByid }); 18 | }); 19 | 20 | const createBootcamps = asyncHandler(async (req, res, next) => { 21 | //check if user has one bootcamp 22 | 23 | const bootcamp = await Bootcamp.findOne({ user: req.user._id }); 24 | 25 | //If user is not admin,then he can add one bootcamp 26 | if (bootcamp && req.user.role !== "Admin") 27 | throw createError( 28 | 400, 29 | `The User with id ${req.user._id} has already published a bootcamp` 30 | ); 31 | 32 | const newBootcamp = await Bootcamp.create({ 33 | ...req.body, 34 | user: req.user._id, 35 | }); 36 | 37 | res.status(201).send({ status: "success", data: newBootcamp }); 38 | }); 39 | 40 | const updateBootcamps = asyncHandler(async (req, res, next) => { 41 | const bootcamp = await Bootcamp.findById(req.params.id); 42 | 43 | if (!bootcamp) 44 | throw createError(404, `Bootcamp is not found of id ${req.params.id}`); 45 | 46 | const findbootcamp = await Bootcamp.findOne({ 47 | _id: req.params.id, 48 | user: req.user._id, 49 | }); 50 | 51 | //check if user is owner of the bootcamp or user is admin 52 | if (!findbootcamp && req.user.role !== "Admin") 53 | throw createError(400, "Not authorize to update this bootcamp"); 54 | 55 | const editBootcamp = await Bootcamp.findByIdAndUpdate( 56 | req.params.id, 57 | req.body, 58 | { 59 | new: true, 60 | runValidators: true, 61 | } 62 | ); 63 | 64 | const updatedBootcamp = await Bootcamp.findById(req.params.id); 65 | 66 | res.status(200).send({ 67 | status: "success", 68 | data: updatedBootcamp, 69 | }); 70 | }); 71 | 72 | const deleteBootcamps = asyncHandler(async (req, res, next) => { 73 | const deleteBootcamp = await Bootcamp.findById(req.params.id); 74 | 75 | if (!deleteBootcamp) 76 | throw createError(404, `Bootcamp is not found of id ${req.params.id}`); 77 | 78 | const bootcamp = await Bootcamp.findOne({ 79 | _id: req.params.id, 80 | user: req.user._id, 81 | }); 82 | 83 | //check if user is owner of the bootcamp or user is admin 84 | if (!bootcamp && req.user.role !== "Admin") 85 | throw createError(400, "Not authorize to delete this bootcamp"); 86 | 87 | await deleteBootcamp.remove(); 88 | res.status(204).send({ 89 | status: "success", 90 | data: {}, 91 | }); 92 | }); 93 | 94 | const getBootcampsInRadius = asyncHandler(async (req, res, next) => { 95 | const { zipcode, distance } = req.params; 96 | 97 | const loc = await geocoder.geocode(zipcode); 98 | console.log(loc); 99 | 100 | const lat = loc[0].latitude; 101 | const lon = loc[0].longitude; 102 | 103 | const Radius = distance / 3958; 104 | 105 | const bootcamps = await Bootcamp.find({ 106 | location: { 107 | $geoWithin: { $centerSphere: [[lon, lat], Radius] }, 108 | }, 109 | }); 110 | 111 | res 112 | .status(200) 113 | .send({ status: "success", count: bootcamps.length, data: bootcamps }); 114 | }); 115 | 116 | const bootcampUploadPhoto = asyncHandler(async (req, res, next) => { 117 | const findbootcamp = await Bootcamp.findById(req.params.id); 118 | 119 | if (!findbootcamp) 120 | throw createError(404, `Bootcamp is not found with id of ${req.params.id}`); 121 | 122 | const bootcamp = await Bootcamp.findOne({ 123 | _id: req.params.id, 124 | user: req.user._id, 125 | }); 126 | 127 | //check if user is owner of the bootcamp or is admin 128 | if (!bootcamp && req.user.role !== "Admin") 129 | throw createError(400, "Not authorize to this bootcamp"); 130 | 131 | if (!req.files) throw createError(400, "Please add a photo"); 132 | 133 | const file = req.files.file; 134 | 135 | //Check file type 136 | if (!file.mimetype.startsWith("image")) 137 | throw createError(400, "This file is not supported"); 138 | 139 | //Check file size 140 | if (file.size > process.env.FILE_UPLOAD_SIZE) 141 | throw createError( 142 | 400, 143 | `Please upload a image of size less than ${process.env.FILE_UPLOAD_SIZE}` 144 | ); 145 | 146 | const fileName = `photo${req.params.id}${path.parse(file.name).ext}`; 147 | 148 | //Upload file to path public/upload 149 | file.mv(`${process.env.FILE_UPLOAD_PATH}/${fileName}`, async (err) => { 150 | if (err) throw err; 151 | 152 | await Bootcamp.findByIdAndUpdate(req.params.id, { 153 | photo: fileName, 154 | }); 155 | 156 | res.status(200).send({ status: "success", photo: fileName }); 157 | 158 | console.log(req.files); 159 | }); 160 | }); 161 | 162 | module.exports = { 163 | getAllbootcamps, 164 | getBootcamps, 165 | createBootcamps, 166 | updateBootcamps, 167 | deleteBootcamps, 168 | getBootcampsInRadius, 169 | bootcampUploadPhoto, 170 | }; 171 | -------------------------------------------------------------------------------- /controller/course.js: -------------------------------------------------------------------------------- 1 | const createError = require("../utilis/createError"); 2 | const asyncHandler = require("../middleware/async"); 3 | const Course = require("../models/Course"); 4 | const Bootcamp = require("../models/Bootcamp"); 5 | 6 | const getCourses = asyncHandler(async (req, res, next) => { 7 | const { bootcampId } = req.params; 8 | 9 | if (req.params.bootcampId) { 10 | const findBootcamp = await Bootcamp.findById(req.params.bootcampId); 11 | if (!findBootcamp) 12 | throw createError( 13 | 404, 14 | `Bootcamp is not found with id of ${req.params.bootcampId}` 15 | ); 16 | 17 | const bootcampCourses = await Course.find({ 18 | bootcamp: req.params.bootcampId, 19 | }).populate({ 20 | path: "bootcamp", 21 | select: "name description", 22 | }); 23 | 24 | return res.status(200).send({ 25 | status: "success", 26 | count: bootcampCourses.length, 27 | data: bootcampCourses, 28 | }); 29 | } else { 30 | res.status(200).send(res.advanceResults); 31 | } 32 | }); 33 | 34 | const getCourse = asyncHandler(async (req, res, next) => { 35 | const course = await Course.findById(req.params.id).populate({ 36 | path: "bootcamp", 37 | select: "name description", 38 | }); 39 | 40 | if (!course) 41 | throw createError(404, `Course is not found with id of ${req.params.id}`); 42 | 43 | res.status(200).send({ status: "success", data: course }); 44 | }); 45 | 46 | const createCourse = asyncHandler(async (req, res, next) => { 47 | const { bootcampId } = req.params; 48 | 49 | const findBootcamp = await Bootcamp.findById(bootcampId); 50 | 51 | if (!findBootcamp) 52 | throw createError(404, `Bootcamp is not found with id of ${bootcampId}`); 53 | 54 | const bootcamp = await Bootcamp.findOne({ 55 | _id: bootcampId, 56 | user: req.user._id, 57 | }); 58 | 59 | //check if owner of bootcamp can add course to if or admin 60 | if (!bootcamp && req.user.role !== "Admin") 61 | throw createError(400, "Not authorize to add course to this bootcamp"); 62 | 63 | const newCourse = await Course.create({ 64 | ...req.body, 65 | bootcamp: bootcampId, 66 | user: req.user._id, 67 | }); 68 | 69 | res.status(201).send({ status: "success", data: newCourse }); 70 | }); 71 | 72 | const updateCourse = asyncHandler(async (req, res, next) => { 73 | const course = await Course.findById(req.params.id); 74 | 75 | if (!course) 76 | throw createError(404, `Course is not found with id of ${req.params.id}`); 77 | 78 | //Check if user is owner of the course 79 | const findCourse = await Course.findOne({ 80 | _id: req.params.id, 81 | user: req.user._id, 82 | }); 83 | 84 | if (!findCourse && req.user.role !== "Admin") 85 | throw createError(400, "Not authorize to update this course"); 86 | 87 | const editCourse = await Course.findByIdAndUpdate(req.params.id, req.body, { 88 | new: true, 89 | runValidators: true, 90 | }); 91 | 92 | const updatedCourse = await Course.findById(req.params.id); 93 | 94 | res.status(200).send({ status: "success", data: updatedCourse }); 95 | }); 96 | 97 | const updateTutuionCost = asyncHandler(async (req, res, next) => { 98 | const course = await Course.findById(req.params.id); 99 | 100 | if (!course) 101 | throw createError(404, `Course is not found with id of ${req.params.id}`); 102 | 103 | //Check if user is owner of the course 104 | const findCourse = await Course.findOne({ 105 | _id: req.params.id, 106 | user: req.user._id, 107 | }); 108 | 109 | if (!findCourse && req.user.role !== "Admin") 110 | throw createError(400, "Not authorize to update this course"); 111 | 112 | course.tuition = req.body.newTutionCost; 113 | 114 | await course.save(); 115 | 116 | const updatedCourse = await Course.findById(req.params.id); 117 | 118 | res.status(200).send({ status: "success", data: updatedCourse }); 119 | }); 120 | 121 | const deleteCourse = asyncHandler(async (req, res, next) => { 122 | const deleteCourse = await Course.findById(req.params.id); 123 | 124 | if (!deleteCourse) 125 | throw createError(404, `Course is not found with id of ${req.params.id}`); 126 | 127 | //Check if user is owner of the course 128 | const findCourse = await Course.findOne({ 129 | _id: req.params.id, 130 | user: req.user._id, 131 | }); 132 | 133 | if (!findCourse && req.user.role !== "Admin") 134 | throw createError(400, "Not authorize to update this course"); 135 | 136 | await deleteCourse.remove(); 137 | res.status(204).send({ status: "success", data: {} }); 138 | }); 139 | 140 | module.exports = { 141 | getCourses, 142 | getCourse, 143 | createCourse, 144 | updateCourse, 145 | deleteCourse, 146 | updateTutuionCost, 147 | }; 148 | -------------------------------------------------------------------------------- /controller/review.js: -------------------------------------------------------------------------------- 1 | const asyncHandler = require("../middleware/async"); 2 | const createError = require("../utilis/createError"); 3 | const Review = require("../models/Review"); 4 | const Bootcamp = require("../models/Bootcamp"); 5 | 6 | const getReviews = asyncHandler(async (req, res, next) => { 7 | const { bootcampId } = req.params; 8 | 9 | if (req.params.bootcampId) { 10 | const findBootcamp = await Bootcamp.findById(req.params.bootcampId); 11 | if (!findBootcamp) 12 | throw createError( 13 | 404, 14 | `Bootcamp is not found with id of ${req.params.bootcampId}` 15 | ); 16 | 17 | const bootcampReviews = await Review.find({ 18 | bootcamp: req.params.bootcampId, 19 | }).populate({ 20 | path: "bootcamp", 21 | select: "name description", 22 | }); 23 | 24 | return res.status(200).send({ 25 | status: "success", 26 | count: bootcampReviews.length, 27 | data: bootcampReviews, 28 | }); 29 | } else { 30 | res.status(200).send(res.advanceResults); 31 | } 32 | }); 33 | 34 | const getReview = asyncHandler(async (req, res, next) => { 35 | const review = await Review.findById(req.params.id).populate({ 36 | path: "bootcamp", 37 | select: "name description", 38 | }); 39 | 40 | if (!review) 41 | throw createError(404, `Review is not found with id of ${req.params.id}`); 42 | 43 | res.status(200).send({ 44 | status: "success", 45 | count: review.length, 46 | data: review, 47 | }); 48 | }); 49 | 50 | const createReview = asyncHandler(async (req, res, next) => { 51 | const bootcamp = await Bootcamp.findById(req.params.bootcampId); 52 | 53 | if (!bootcamp) 54 | throw createError( 55 | 404, 56 | `Bootcamp is not found with id of ${req.params.bootcampId}` 57 | ); 58 | 59 | const review = await Review.create({ 60 | ...req.body, 61 | bootcamp: req.params.bootcampId, 62 | user: req.user._id, 63 | }); 64 | 65 | res.status(201).send({ status: "success", data: review }); 66 | }); 67 | 68 | const updateReview = asyncHandler(async (req, res, next) => { 69 | const review = await Review.findById(req.params.id); 70 | 71 | if (!review) 72 | throw createError(404, `Review is not found with id of ${req.params.id}`); 73 | 74 | //check if review belongs to user created or user is admin 75 | 76 | const findReview = await Review.findOne({ 77 | _id: req.params.id, 78 | user: req.user._id, 79 | }); 80 | 81 | if (!findReview && req.user.role !== "admin") 82 | throw createError(400, "Not authorized to update this review"); 83 | 84 | const editReview = await Review.findByIdAndUpdate(req.params.id, req.body, { 85 | new: true, 86 | runValidators: true, 87 | }); 88 | 89 | const updatedreview = await Review.findById(req.params.id); 90 | 91 | res.status(200).send({ status: "success", data: updatedreview }); 92 | }); 93 | 94 | const updateRating = asyncHandler(async (req, res, next) => { 95 | const review = await Review.findById(req.params.id); 96 | 97 | if (!review) 98 | throw createError(404, `Review is not found with id of ${req.params.id}`); 99 | 100 | //check if review belongs to user created or user is admin 101 | 102 | const findReview = await Review.findOne({ 103 | _id: req.params.id, 104 | user: req.user._id, 105 | }); 106 | 107 | if (!findReview && req.user.role !== "admin") 108 | throw createError(400, "Not authorized to update this review"); 109 | 110 | review.rating = req.body.editRating; 111 | 112 | await review.save(); 113 | 114 | const updatedreview = await Review.findById(req.params.id); 115 | 116 | res.status(200).send({ status: "success", data: updatedreview }); 117 | }); 118 | 119 | const deleteReview = asyncHandler(async (req, res, next) => { 120 | const review = await Review.findById(req.params.id); 121 | 122 | if (!review) 123 | throw createError(404, `Review is not found with id of ${req.params.id}`); 124 | 125 | //check if review belongs to user created or user is admin 126 | const findReview = await Review.findOne({ 127 | _id: req.params.id, 128 | user: req.user._id, 129 | }); 130 | 131 | if (!findReview && req.user.role !== "admin") 132 | throw createError(400, "Not authorized to update this review"); 133 | 134 | await review.remove(); 135 | 136 | res.status(204).send({ status: "success", data: {} }); 137 | }); 138 | 139 | module.exports = { 140 | getReviews, 141 | getReview, 142 | createReview, 143 | updateReview, 144 | deleteReview, 145 | updateRating, 146 | }; 147 | -------------------------------------------------------------------------------- /controller/users.js: -------------------------------------------------------------------------------- 1 | const asyncHandler = require("../middleware/async"); 2 | const createError = require("../utilis/createError"); 3 | const User = require("../models/User"); 4 | 5 | const getUsers = asyncHandler(async (req, res, next) => { 6 | res.status(200).send({ status: "success", data: res.advanceResults }); 7 | }); 8 | 9 | const getUser = asyncHandler(async (req, res, next) => { 10 | const user = await User.findById(req.params.id); 11 | 12 | if (!user) 13 | throw createError(404, `User is not found with id of ${req.params.id}`); 14 | 15 | res.status(200).send({ status: "success", data: user }); 16 | }); 17 | 18 | const createUser = asyncHandler(async (req, res, next) => { 19 | const user = await User.create(req.body); 20 | 21 | res.status(200).send({ status: "success", data: user }); 22 | }); 23 | 24 | const updateUser = asyncHandler(async (req, res, next) => { 25 | const editUser = await User.findByIdAndUpdate(req.params.id, req.body); 26 | 27 | if (!editUser) 28 | throw createError(404, `User is not found with id of ${req.params.id}`); 29 | 30 | const updatedUser = await User.findById(req.params.id); 31 | 32 | res.status(201).send({ status: "success", data: updatedUser }); 33 | }); 34 | 35 | const deleteUser = asyncHandler(async (req, res, next) => { 36 | const deleteUser = await User.findByIdAndDelete(req.params.id); 37 | 38 | if (!deleteUser) 39 | throw createError(404, `User is not found with id of ${req.params.id}`); 40 | 41 | res.status(204).send({ status: "success", data: {} }); 42 | }); 43 | module.exports = { 44 | getUsers, 45 | getUser, 46 | createUser, 47 | updateUser, 48 | deleteUser, 49 | }; 50 | -------------------------------------------------------------------------------- /middleware/advanceResults.js: -------------------------------------------------------------------------------- 1 | const advanceResults = (model, populate) => async (req, res, next) => { 2 | //JSON.Stringify Convert the javascript object into string | when sending data to server, the data has to be string 3 | //JSON.parse Convert the string into javascript Object | when server send data, data is always a string, so parse the data to convert into js Object 4 | 5 | let query; 6 | 7 | const reqQuery = { ...req.query }; 8 | 9 | //Feild to exclude 10 | 11 | const removeFeilds = ["select", "sort", "limit", "page"]; 12 | 13 | removeFeilds.forEach((param) => delete reqQuery[param]); 14 | 15 | //Create query string 16 | let queryStr = JSON.stringify(reqQuery); 17 | 18 | //Create operators ($gt,gte,lt,lte) 19 | queryStr = queryStr.replace(/\b(gt|gte|lt|lte|in)/g, (match) => `$${match}`); 20 | 21 | //Finding resource 22 | query = model.find(JSON.parse(queryStr)); 23 | 24 | //Select 25 | if (req.query.select) { 26 | const select = req.query.select.split(",").join(" "); 27 | query = query.select(select); 28 | } 29 | 30 | //Sort 31 | if (req.query.sort) { 32 | const sort = req.query.sort.split(",").join(" "); 33 | query = query.sort(sort); 34 | } else { 35 | query = query.sort("-createdAt"); 36 | } 37 | 38 | //Pagination 39 | 40 | const page = parseInt(req.query.page, 10) || 1; 41 | const limit = parseInt(req.query.limit, 10) || 25; 42 | const startIndex = (page - 1) * limit; 43 | 44 | const endIndex = page * limit; 45 | const total = await model.countDocuments(); 46 | 47 | query = query.skip(startIndex).limit(limit); 48 | 49 | if (populate) { 50 | query = query.populate(populate); 51 | } 52 | 53 | //Execute Query 54 | const results = await query; 55 | 56 | //Pagination Result 57 | const pagination = {}; 58 | 59 | if (endIndex < total) { 60 | pagination.next = { 61 | page: page + 1, 62 | limit, 63 | }; 64 | } 65 | if (startIndex > 0) { 66 | pagination.prev = { 67 | page: page - 1, 68 | limit, 69 | }; 70 | } 71 | 72 | res.advanceResults = { 73 | status: "success", 74 | count: results.length, 75 | pagination, 76 | data: results, 77 | }; 78 | next(); 79 | }; 80 | 81 | module.exports = advanceResults; 82 | -------------------------------------------------------------------------------- /middleware/async.js: -------------------------------------------------------------------------------- 1 | const asyncHandler = (fn) => (req, res, next) => 2 | Promise.resolve(fn(req, res, next)).catch(next); 3 | 4 | module.exports = asyncHandler; 5 | -------------------------------------------------------------------------------- /middleware/auth.js: -------------------------------------------------------------------------------- 1 | const createError = require("../utilis/createError"); 2 | const verifyToken = require("../utilis/jwt"); 3 | const asyncHandler = require("../middleware/async"); 4 | const User = require("../models/User"); 5 | 6 | const protect = asyncHandler(async (req, res, next) => { 7 | const authorization = req.headers["authorization"]; 8 | if (!(authorization && authorization.toLowerCase().startsWith("bearer"))) 9 | throw createError(401, "Not authorized"); 10 | //Or check for cookie... 11 | 12 | const token = authorization.split(" ")[1]; 13 | 14 | const decodeToken = verifyToken(token, process.env.JWT_SECRET); 15 | 16 | req.user = await User.findById(decodeToken._id); 17 | console.log(req.user); 18 | next(); 19 | }); 20 | 21 | const permission = (...roles) => (req, res, next) => { 22 | if (!roles.includes(req.user.role)) 23 | throw createError( 24 | 401, 25 | `User role ${req.user.role} is not allowed to access this resource` 26 | ); 27 | 28 | next(); 29 | }; 30 | module.exports = { protect, permission }; 31 | -------------------------------------------------------------------------------- /middleware/error.js: -------------------------------------------------------------------------------- 1 | const unknownEndpoints = () => { 2 | const error = new Error("Unknown Endpoints"); 3 | error.status = 404; 4 | throw error; 5 | }; 6 | 7 | const errorHandler = (err, req, res, next) => { 8 | //Mongoose Bad ObjectId 9 | if (err.name === "CastError" && err.kind === "ObjectId") { 10 | return res.status(404).send({ 11 | status: "Error", 12 | error: `Resource is not found`, 13 | }); 14 | } 15 | 16 | //Mongoose validation Error 17 | if (err.name === "ValidationError") { 18 | console.log(err); 19 | const message = Object.values(err.errors).map((val) => val.message); 20 | const arrayIntoString = message.join(","); 21 | return res.status(400).send({ status: "Error", error: arrayIntoString }); 22 | } 23 | 24 | //Mongoose Dublicat key 25 | if (err.code === 11000) { 26 | return res 27 | .status(400) 28 | .send({ status: "Error", error: "Dublicate value entered" }); 29 | } 30 | 31 | res.status(err.status || 500).send({ status: "Error", error: err.message }); 32 | }; 33 | 34 | module.exports = { 35 | unknownEndpoints, 36 | errorHandler, 37 | }; 38 | -------------------------------------------------------------------------------- /models/Bootcamp.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const slugify = require("slugify"); 3 | const geocoder = require("../utilis/geocoder"); 4 | 5 | const BootcampSchema = new mongoose.Schema( 6 | { 7 | name: { 8 | type: String, 9 | required: [true, "Please add a name"], 10 | trim: true, 11 | minlength: [5, "Name cannot be less than 5 characters"], 12 | maxlength: [50, "Name cannot be more than 50 characters"], 13 | unique: true, 14 | }, 15 | slug: String, 16 | description: { 17 | type: String, 18 | required: [true, "Please add a description"], 19 | minlength: [10, "description cannot be less than 10 characters"], 20 | maxlength: [500, "description cannot be more than 500 characters"], 21 | }, 22 | website: { 23 | type: String, 24 | match: [ 25 | /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/, 26 | "Please privode a valid URL with HTTP or HTTPS", 27 | ], 28 | }, 29 | phone: { 30 | type: String, 31 | maxlength: [20, "Phone number cannot be longer than 20 character"], 32 | required: [true, "Please add a phone number"], 33 | }, 34 | email: { 35 | type: String, 36 | match: [ 37 | /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 38 | "Please add a valid email", 39 | ], 40 | required: [true, "Please add a email"], 41 | }, 42 | address: { 43 | type: String, 44 | required: [true, "Please add an address"], 45 | }, 46 | location: { 47 | type: { 48 | type: String, 49 | enum: ["Point"], 50 | }, 51 | coordinates: { 52 | type: [Number], 53 | index: "2dsphere", 54 | }, 55 | formattedAddress: String, 56 | street: String, 57 | city: String, 58 | state: String, 59 | zipcode: String, 60 | country: String, 61 | }, 62 | careers: { 63 | type: [String], 64 | require: [true, "PLease add careers"], 65 | enum: [ 66 | "Web Development", 67 | "Mobile Development", 68 | "UI/UX", 69 | "Data Science", 70 | "Business", 71 | "Other", 72 | ], 73 | }, 74 | averageRating: { 75 | type: Number, 76 | min: [1, "Rating must be at least 1"], 77 | max: [10, "Rating must can not be more than 10"], 78 | }, 79 | averageCost: Number, 80 | photo: { 81 | type: String, 82 | default: "no-photo.jpeg", 83 | }, 84 | housing: { 85 | type: Boolean, 86 | default: false, 87 | }, 88 | jobAssistance: { 89 | type: Boolean, 90 | default: false, 91 | }, 92 | jobGaurantee: { 93 | type: Boolean, 94 | default: false, 95 | }, 96 | acceptGi: { 97 | type: Boolean, 98 | default: false, 99 | }, 100 | createdAt: { 101 | type: Date, 102 | default: Date.now, 103 | }, 104 | user: { 105 | type: mongoose.Schema.ObjectId, 106 | ref: "User", 107 | required: true, 108 | }, 109 | }, 110 | { 111 | toJSON: { virtuals: true }, 112 | } 113 | ); 114 | 115 | BootcampSchema.pre("save", function (next) { 116 | this.slug = slugify(this.name, { lower: true }); 117 | console.log("Slugify run", this.slug); 118 | 119 | next(); 120 | }); 121 | 122 | //Geocode & create location feild 123 | BootcampSchema.pre("save", async function (next) { 124 | const loc = await geocoder.geocode(this.address); 125 | 126 | this.location = { 127 | type: "Point", 128 | coordinates: [loc[0].longitude, loc[0].latitude], 129 | formattedAddress: loc[0].formattedAddress, 130 | street: loc[0].streetName, 131 | city: loc[0].city, 132 | state: loc[0].stateCode, 133 | zipcode: loc[0].zipcode, 134 | country: loc[0].countryCode, 135 | }; 136 | 137 | next(); 138 | }); 139 | 140 | BootcampSchema.pre("remove", async function (next) { 141 | await this.model("Course").deleteMany({ bootcamp: this._id }); 142 | 143 | next(); 144 | }); 145 | 146 | BootcampSchema.virtual("Courses", { 147 | ref: "Course", 148 | localField: "_id", 149 | foreignField: "bootcamp", 150 | justOne: false, 151 | }); 152 | 153 | module.exports = mongoose.model("Bootcamp", BootcampSchema); 154 | -------------------------------------------------------------------------------- /models/Course.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const { findOneAndUpdate } = require("./Bootcamp"); 3 | 4 | const CourseSchema = new mongoose.Schema({ 5 | title: { 6 | type: String, 7 | trim: true, 8 | required: [true, "Please add a title"], 9 | }, 10 | description: { 11 | type: String, 12 | required: [true, "Please add a description"], 13 | }, 14 | weeks: { 15 | type: String, 16 | required: [true, "Please add number of weeks"], 17 | }, 18 | tuition: { 19 | type: Number, 20 | required: [true, "Please add a tuition cost"], 21 | }, 22 | minimumSkill: { 23 | type: String, 24 | required: [true, "Please add a minimum skill"], 25 | enum: ["beginner", "intermediate", "advanced"], 26 | }, 27 | scholarhipsAvailable: { 28 | type: Boolean, 29 | default: false, 30 | }, 31 | createdAt: { 32 | type: Date, 33 | default: Date.now, 34 | }, 35 | bootcamp: { 36 | type: mongoose.Schema.ObjectId, 37 | ref: "Bootcamp", 38 | required: true, 39 | }, 40 | user: { 41 | type: mongoose.Schema.ObjectId, 42 | ref: "User", 43 | required: true, 44 | }, 45 | }); 46 | 47 | CourseSchema.statics.getAverageCost = async function (bootcampId) { 48 | const obj = await this.aggregate([ 49 | { 50 | $match: { bootcamp: bootcampId }, 51 | }, 52 | { 53 | $group: { 54 | _id: "$bootcamp", 55 | averageCost: { $avg: "$tuition" }, 56 | }, 57 | }, 58 | ]); 59 | try { 60 | await this.model("Bootcamp").findByIdAndUpdate(bootcampId, { 61 | averageCost: Math.ceil(obj[0].averageCost / 10) * 10, 62 | }); 63 | } catch (error) { 64 | console.log(error); 65 | } 66 | }; 67 | 68 | CourseSchema.post("save", function () { 69 | this.constructor.getAverageCost(this.bootcamp); 70 | }); 71 | 72 | CourseSchema.pre("remove", function () { 73 | this.constructor.getAverageCost(this.bootcamp); 74 | }); 75 | module.exports = mongoose.model("Course", CourseSchema); 76 | -------------------------------------------------------------------------------- /models/Review.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const ReviewSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: [true, "Please add a title"], 7 | trim: true, 8 | maxlength: [100, "title cannot be longer than 100 character"], 9 | }, 10 | text: { 11 | type: String, 12 | required: [true, "Please add a text"], 13 | }, 14 | rating: { 15 | type: Number, 16 | min: 1, 17 | max: 10, 18 | required: [true, "Please add a rating between 1 and 10"], 19 | }, 20 | createdAt: { 21 | type: Date, 22 | default: Date.now, 23 | }, 24 | bootcamp: { 25 | type: mongoose.Schema.ObjectId, 26 | ref: "Bootcamp", 27 | required: true, 28 | }, 29 | user: { 30 | type: mongoose.Schema.ObjectId, 31 | ref: "User", 32 | required: true, 33 | }, 34 | }); 35 | 36 | ReviewSchema.index({ bootcamp: 1, user: 1 }, { unique: true }); 37 | 38 | ReviewSchema.statics.getRating = async function (bootcampId) { 39 | const obj = await this.aggregate([ 40 | { 41 | $match: { bootcamp: bootcampId }, 42 | }, 43 | { 44 | $group: { 45 | _id: "$bootcamp", 46 | averageRating: { $avg: "$rating" }, 47 | }, 48 | }, 49 | ]); 50 | try { 51 | await this.model("Bootcamp").findByIdAndUpdate(bootcampId, { 52 | averageRating: obj[0].averageRating, 53 | }); 54 | } catch (error) { 55 | console.log(error); 56 | } 57 | }; 58 | 59 | ReviewSchema.post("save", function () { 60 | this.constructor.getRating(this.bootcamp); 61 | }); 62 | 63 | ReviewSchema.pre("remove", function () { 64 | this.constructor.getRating(this.bootcamp); 65 | }); 66 | 67 | module.exports = mongoose.model("Review", ReviewSchema); 68 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | const mongoose = require("mongoose"); 3 | const bcrypt = require("bcrypt"); 4 | const jwt = require("jsonwebtoken"); 5 | 6 | const UserSchema = new mongoose.Schema({ 7 | name: { 8 | type: String, 9 | required: [true, "Please add a name"], 10 | trim: true, 11 | }, 12 | email: { 13 | type: String, 14 | required: [true, "Please add an email"], 15 | match: [ 16 | /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 17 | "Please add a valid email", 18 | ], 19 | unique: true, 20 | }, 21 | password: { 22 | type: String, 23 | minlength: [8, "Password should be 8 character long"], 24 | required: [true, "Please add a password"], 25 | select: false, 26 | }, 27 | role: { 28 | type: String, 29 | enum: ["user", "publisher"], 30 | default: "user", 31 | }, 32 | resetPasswordToken: String, 33 | resetPasswordExpire: Date, 34 | createdAt: { 35 | type: Date, 36 | default: Date.now, 37 | }, 38 | }); 39 | 40 | UserSchema.pre("save", async function (next) { 41 | if (!this.isModified("password")) { 42 | next(); 43 | } 44 | const SaltFactor = await bcrypt.genSalt(10); 45 | 46 | this.password = await bcrypt.hash(this.password, SaltFactor); 47 | 48 | next(); 49 | }); 50 | 51 | UserSchema.methods.genAuthToken = function () { 52 | return jwt.sign({ _id: this._id }, process.env.JWT_SECRET, { 53 | expiresIn: process.env.JWT_EXPIREIN, 54 | }); 55 | }; 56 | 57 | UserSchema.methods.matchPassword = async function (enteredPassword) { 58 | return await bcrypt.compare(enteredPassword, this.password); 59 | }; 60 | 61 | UserSchema.methods.getResetPasswordToken = function () { 62 | const resetToken = crypto.randomBytes(20).toString("hex"); 63 | 64 | //hash the resetToken and set it to this.resetPasswordToken 65 | 66 | this.resetPasswordToken = crypto 67 | .createHash("sha256") 68 | .update(resetToken) 69 | .digest("hex"); 70 | 71 | this.resetPasswordExpire = Date.now() + 10 * 60 * 1000; 72 | 73 | return resetToken; 74 | }; 75 | 76 | module.exports = mongoose.model("User", UserSchema); 77 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devcamper", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "abbrev": { 8 | "version": "1.1.1", 9 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 10 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 11 | }, 12 | "accepts": { 13 | "version": "1.3.7", 14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 15 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 16 | "requires": { 17 | "mime-types": "~2.1.24", 18 | "negotiator": "0.6.2" 19 | } 20 | }, 21 | "ajv": { 22 | "version": "6.12.2", 23 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", 24 | "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", 25 | "requires": { 26 | "fast-deep-equal": "^3.1.1", 27 | "fast-json-stable-stringify": "^2.0.0", 28 | "json-schema-traverse": "^0.4.1", 29 | "uri-js": "^4.2.2" 30 | } 31 | }, 32 | "ansi-regex": { 33 | "version": "2.1.1", 34 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 35 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 36 | }, 37 | "aproba": { 38 | "version": "1.2.0", 39 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 40 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 41 | }, 42 | "are-we-there-yet": { 43 | "version": "1.1.5", 44 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 45 | "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 46 | "requires": { 47 | "delegates": "^1.0.0", 48 | "readable-stream": "^2.0.6" 49 | } 50 | }, 51 | "array-flatten": { 52 | "version": "1.1.1", 53 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 54 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 55 | }, 56 | "asn1": { 57 | "version": "0.2.4", 58 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 59 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 60 | "requires": { 61 | "safer-buffer": "~2.1.0" 62 | } 63 | }, 64 | "assert-plus": { 65 | "version": "1.0.0", 66 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 67 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 68 | }, 69 | "asynckit": { 70 | "version": "0.4.0", 71 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 72 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 73 | }, 74 | "aws-sign2": { 75 | "version": "0.7.0", 76 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 77 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 78 | }, 79 | "aws4": { 80 | "version": "1.10.0", 81 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", 82 | "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" 83 | }, 84 | "balanced-match": { 85 | "version": "1.0.0", 86 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 87 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 88 | }, 89 | "bcrypt": { 90 | "version": "5.0.0", 91 | "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.0.tgz", 92 | "integrity": "sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==", 93 | "requires": { 94 | "node-addon-api": "^3.0.0", 95 | "node-pre-gyp": "0.15.0" 96 | } 97 | }, 98 | "bcrypt-pbkdf": { 99 | "version": "1.0.2", 100 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 101 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 102 | "requires": { 103 | "tweetnacl": "^0.14.3" 104 | } 105 | }, 106 | "bl": { 107 | "version": "2.2.0", 108 | "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", 109 | "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", 110 | "requires": { 111 | "readable-stream": "^2.3.5", 112 | "safe-buffer": "^5.1.1" 113 | } 114 | }, 115 | "bluebird": { 116 | "version": "3.5.1", 117 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 118 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 119 | }, 120 | "body-parser": { 121 | "version": "1.19.0", 122 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 123 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 124 | "requires": { 125 | "bytes": "3.1.0", 126 | "content-type": "~1.0.4", 127 | "debug": "2.6.9", 128 | "depd": "~1.1.2", 129 | "http-errors": "1.7.2", 130 | "iconv-lite": "0.4.24", 131 | "on-finished": "~2.3.0", 132 | "qs": "6.7.0", 133 | "raw-body": "2.4.0", 134 | "type-is": "~1.6.17" 135 | }, 136 | "dependencies": { 137 | "debug": { 138 | "version": "2.6.9", 139 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 140 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 141 | "requires": { 142 | "ms": "2.0.0" 143 | } 144 | }, 145 | "ms": { 146 | "version": "2.0.0", 147 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 148 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 149 | } 150 | } 151 | }, 152 | "bowser": { 153 | "version": "2.9.0", 154 | "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", 155 | "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" 156 | }, 157 | "brace-expansion": { 158 | "version": "1.1.11", 159 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 160 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 161 | "requires": { 162 | "balanced-match": "^1.0.0", 163 | "concat-map": "0.0.1" 164 | } 165 | }, 166 | "bson": { 167 | "version": "1.1.4", 168 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.4.tgz", 169 | "integrity": "sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q==" 170 | }, 171 | "buffer-equal-constant-time": { 172 | "version": "1.0.1", 173 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 174 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 175 | }, 176 | "busboy": { 177 | "version": "0.3.1", 178 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", 179 | "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", 180 | "requires": { 181 | "dicer": "0.3.0" 182 | } 183 | }, 184 | "bytes": { 185 | "version": "3.1.0", 186 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 187 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 188 | }, 189 | "camelize": { 190 | "version": "1.0.0", 191 | "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", 192 | "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" 193 | }, 194 | "caseless": { 195 | "version": "0.12.0", 196 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 197 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 198 | }, 199 | "chownr": { 200 | "version": "1.1.4", 201 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 202 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 203 | }, 204 | "code-point-at": { 205 | "version": "1.1.0", 206 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 207 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 208 | }, 209 | "colors": { 210 | "version": "1.4.0", 211 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 212 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 213 | }, 214 | "combined-stream": { 215 | "version": "1.0.8", 216 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 217 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 218 | "requires": { 219 | "delayed-stream": "~1.0.0" 220 | } 221 | }, 222 | "concat-map": { 223 | "version": "0.0.1", 224 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 225 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 226 | }, 227 | "console-control-strings": { 228 | "version": "1.1.0", 229 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 230 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 231 | }, 232 | "content-disposition": { 233 | "version": "0.5.3", 234 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 235 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 236 | "requires": { 237 | "safe-buffer": "5.1.2" 238 | } 239 | }, 240 | "content-security-policy-builder": { 241 | "version": "2.1.0", 242 | "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz", 243 | "integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ==" 244 | }, 245 | "content-type": { 246 | "version": "1.0.4", 247 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 248 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 249 | }, 250 | "cookie": { 251 | "version": "0.4.0", 252 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 253 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 254 | }, 255 | "cookie-parser": { 256 | "version": "1.4.5", 257 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", 258 | "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", 259 | "requires": { 260 | "cookie": "0.4.0", 261 | "cookie-signature": "1.0.6" 262 | } 263 | }, 264 | "cookie-signature": { 265 | "version": "1.0.6", 266 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 267 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 268 | }, 269 | "core-util-is": { 270 | "version": "1.0.2", 271 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 272 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 273 | }, 274 | "cors": { 275 | "version": "2.8.5", 276 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 277 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 278 | "requires": { 279 | "object-assign": "^4", 280 | "vary": "^1" 281 | } 282 | }, 283 | "dashdash": { 284 | "version": "1.14.1", 285 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 286 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 287 | "requires": { 288 | "assert-plus": "^1.0.0" 289 | } 290 | }, 291 | "dasherize": { 292 | "version": "2.0.0", 293 | "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", 294 | "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" 295 | }, 296 | "debug": { 297 | "version": "3.2.6", 298 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 299 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 300 | "requires": { 301 | "ms": "^2.1.1" 302 | } 303 | }, 304 | "deep-extend": { 305 | "version": "0.6.0", 306 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 307 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 308 | }, 309 | "delayed-stream": { 310 | "version": "1.0.0", 311 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 312 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 313 | }, 314 | "delegates": { 315 | "version": "1.0.0", 316 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 317 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 318 | }, 319 | "denque": { 320 | "version": "1.4.1", 321 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", 322 | "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" 323 | }, 324 | "depd": { 325 | "version": "1.1.2", 326 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 327 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 328 | }, 329 | "destroy": { 330 | "version": "1.0.4", 331 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 332 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 333 | }, 334 | "detect-libc": { 335 | "version": "1.0.3", 336 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 337 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 338 | }, 339 | "dicer": { 340 | "version": "0.3.0", 341 | "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", 342 | "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", 343 | "requires": { 344 | "streamsearch": "0.1.2" 345 | } 346 | }, 347 | "dont-sniff-mimetype": { 348 | "version": "1.1.0", 349 | "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz", 350 | "integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug==" 351 | }, 352 | "dotenv": { 353 | "version": "8.2.0", 354 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 355 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 356 | }, 357 | "ecc-jsbn": { 358 | "version": "0.1.2", 359 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 360 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 361 | "requires": { 362 | "jsbn": "~0.1.0", 363 | "safer-buffer": "^2.1.0" 364 | } 365 | }, 366 | "ecdsa-sig-formatter": { 367 | "version": "1.0.11", 368 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 369 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 370 | "requires": { 371 | "safe-buffer": "^5.0.1" 372 | } 373 | }, 374 | "ee-first": { 375 | "version": "1.1.1", 376 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 377 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 378 | }, 379 | "encodeurl": { 380 | "version": "1.0.2", 381 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 382 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 383 | }, 384 | "escape-html": { 385 | "version": "1.0.3", 386 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 387 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 388 | }, 389 | "etag": { 390 | "version": "1.8.1", 391 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 392 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 393 | }, 394 | "express": { 395 | "version": "4.17.1", 396 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 397 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 398 | "requires": { 399 | "accepts": "~1.3.7", 400 | "array-flatten": "1.1.1", 401 | "body-parser": "1.19.0", 402 | "content-disposition": "0.5.3", 403 | "content-type": "~1.0.4", 404 | "cookie": "0.4.0", 405 | "cookie-signature": "1.0.6", 406 | "debug": "2.6.9", 407 | "depd": "~1.1.2", 408 | "encodeurl": "~1.0.2", 409 | "escape-html": "~1.0.3", 410 | "etag": "~1.8.1", 411 | "finalhandler": "~1.1.2", 412 | "fresh": "0.5.2", 413 | "merge-descriptors": "1.0.1", 414 | "methods": "~1.1.2", 415 | "on-finished": "~2.3.0", 416 | "parseurl": "~1.3.3", 417 | "path-to-regexp": "0.1.7", 418 | "proxy-addr": "~2.0.5", 419 | "qs": "6.7.0", 420 | "range-parser": "~1.2.1", 421 | "safe-buffer": "5.1.2", 422 | "send": "0.17.1", 423 | "serve-static": "1.14.1", 424 | "setprototypeof": "1.1.1", 425 | "statuses": "~1.5.0", 426 | "type-is": "~1.6.18", 427 | "utils-merge": "1.0.1", 428 | "vary": "~1.1.2" 429 | }, 430 | "dependencies": { 431 | "debug": { 432 | "version": "2.6.9", 433 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 434 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 435 | "requires": { 436 | "ms": "2.0.0" 437 | } 438 | }, 439 | "ms": { 440 | "version": "2.0.0", 441 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 442 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 443 | } 444 | } 445 | }, 446 | "express-fileupload": { 447 | "version": "1.1.7-alpha.3", 448 | "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.7-alpha.3.tgz", 449 | "integrity": "sha512-2YRJQqjgfFcYiMr8inico+UQ0UsxuOUyO9wkWkx+vjsEcUI7c1ae38Nv5NKdGjHqL5+J01P6StT9mjZTI7Qzjg==", 450 | "requires": { 451 | "busboy": "^0.3.1" 452 | } 453 | }, 454 | "express-mongo-sanitize": { 455 | "version": "2.0.0", 456 | "resolved": "https://registry.npmjs.org/express-mongo-sanitize/-/express-mongo-sanitize-2.0.0.tgz", 457 | "integrity": "sha512-tqGqnKsibDfKqypC6QDYjp4VRLqtTlwuHDfK7KECZvq9fDOq8yi0MdzCJe2DWhv54/IoQV+7uXR7h9eD+Fc5LA==" 458 | }, 459 | "express-rate-limit": { 460 | "version": "5.1.3", 461 | "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.3.tgz", 462 | "integrity": "sha512-TINcxve5510pXj4n9/1AMupkj3iWxl3JuZaWhCdYDlZeoCPqweGZrxbrlqTCFb1CT5wli7s8e2SH/Qz2c9GorA==" 463 | }, 464 | "extend": { 465 | "version": "3.0.2", 466 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 467 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 468 | }, 469 | "extsprintf": { 470 | "version": "1.3.0", 471 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 472 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 473 | }, 474 | "fast-deep-equal": { 475 | "version": "3.1.3", 476 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 477 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 478 | }, 479 | "fast-json-stable-stringify": { 480 | "version": "2.1.0", 481 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 482 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 483 | }, 484 | "feature-policy": { 485 | "version": "0.3.0", 486 | "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", 487 | "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==" 488 | }, 489 | "finalhandler": { 490 | "version": "1.1.2", 491 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 492 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 493 | "requires": { 494 | "debug": "2.6.9", 495 | "encodeurl": "~1.0.2", 496 | "escape-html": "~1.0.3", 497 | "on-finished": "~2.3.0", 498 | "parseurl": "~1.3.3", 499 | "statuses": "~1.5.0", 500 | "unpipe": "~1.0.0" 501 | }, 502 | "dependencies": { 503 | "debug": { 504 | "version": "2.6.9", 505 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 506 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 507 | "requires": { 508 | "ms": "2.0.0" 509 | } 510 | }, 511 | "ms": { 512 | "version": "2.0.0", 513 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 514 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 515 | } 516 | } 517 | }, 518 | "forever-agent": { 519 | "version": "0.6.1", 520 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 521 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 522 | }, 523 | "form-data": { 524 | "version": "2.3.3", 525 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 526 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 527 | "requires": { 528 | "asynckit": "^0.4.0", 529 | "combined-stream": "^1.0.6", 530 | "mime-types": "^2.1.12" 531 | } 532 | }, 533 | "forwarded": { 534 | "version": "0.1.2", 535 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 536 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 537 | }, 538 | "fresh": { 539 | "version": "0.5.2", 540 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 541 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 542 | }, 543 | "fs-minipass": { 544 | "version": "1.2.7", 545 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", 546 | "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", 547 | "requires": { 548 | "minipass": "^2.6.0" 549 | } 550 | }, 551 | "fs.realpath": { 552 | "version": "1.0.0", 553 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 554 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 555 | }, 556 | "gauge": { 557 | "version": "2.7.4", 558 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 559 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 560 | "requires": { 561 | "aproba": "^1.0.3", 562 | "console-control-strings": "^1.0.0", 563 | "has-unicode": "^2.0.0", 564 | "object-assign": "^4.1.0", 565 | "signal-exit": "^3.0.0", 566 | "string-width": "^1.0.1", 567 | "strip-ansi": "^3.0.1", 568 | "wide-align": "^1.1.0" 569 | } 570 | }, 571 | "getpass": { 572 | "version": "0.1.7", 573 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 574 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 575 | "requires": { 576 | "assert-plus": "^1.0.0" 577 | } 578 | }, 579 | "glob": { 580 | "version": "7.1.6", 581 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 582 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 583 | "requires": { 584 | "fs.realpath": "^1.0.0", 585 | "inflight": "^1.0.4", 586 | "inherits": "2", 587 | "minimatch": "^3.0.4", 588 | "once": "^1.3.0", 589 | "path-is-absolute": "^1.0.0" 590 | } 591 | }, 592 | "har-schema": { 593 | "version": "2.0.0", 594 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 595 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 596 | }, 597 | "har-validator": { 598 | "version": "5.1.3", 599 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 600 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 601 | "requires": { 602 | "ajv": "^6.5.5", 603 | "har-schema": "^2.0.0" 604 | } 605 | }, 606 | "has-unicode": { 607 | "version": "2.0.1", 608 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 609 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 610 | }, 611 | "helmet": { 612 | "version": "3.23.3", 613 | "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.23.3.tgz", 614 | "integrity": "sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA==", 615 | "requires": { 616 | "depd": "2.0.0", 617 | "dont-sniff-mimetype": "1.1.0", 618 | "feature-policy": "0.3.0", 619 | "helmet-crossdomain": "0.4.0", 620 | "helmet-csp": "2.10.0", 621 | "hide-powered-by": "1.1.0", 622 | "hpkp": "2.0.0", 623 | "hsts": "2.2.0", 624 | "nocache": "2.1.0", 625 | "referrer-policy": "1.2.0", 626 | "x-xss-protection": "1.3.0" 627 | }, 628 | "dependencies": { 629 | "depd": { 630 | "version": "2.0.0", 631 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 632 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 633 | } 634 | } 635 | }, 636 | "helmet-crossdomain": { 637 | "version": "0.4.0", 638 | "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz", 639 | "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" 640 | }, 641 | "helmet-csp": { 642 | "version": "2.10.0", 643 | "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz", 644 | "integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==", 645 | "requires": { 646 | "bowser": "2.9.0", 647 | "camelize": "1.0.0", 648 | "content-security-policy-builder": "2.1.0", 649 | "dasherize": "2.0.0" 650 | } 651 | }, 652 | "hide-powered-by": { 653 | "version": "1.1.0", 654 | "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz", 655 | "integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==" 656 | }, 657 | "hpkp": { 658 | "version": "2.0.0", 659 | "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", 660 | "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" 661 | }, 662 | "hpp": { 663 | "version": "0.2.3", 664 | "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", 665 | "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", 666 | "requires": { 667 | "lodash": "^4.17.12", 668 | "type-is": "^1.6.12" 669 | } 670 | }, 671 | "hsts": { 672 | "version": "2.2.0", 673 | "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", 674 | "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==", 675 | "requires": { 676 | "depd": "2.0.0" 677 | }, 678 | "dependencies": { 679 | "depd": { 680 | "version": "2.0.0", 681 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 682 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 683 | } 684 | } 685 | }, 686 | "http-errors": { 687 | "version": "1.7.2", 688 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 689 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 690 | "requires": { 691 | "depd": "~1.1.2", 692 | "inherits": "2.0.3", 693 | "setprototypeof": "1.1.1", 694 | "statuses": ">= 1.5.0 < 2", 695 | "toidentifier": "1.0.0" 696 | }, 697 | "dependencies": { 698 | "inherits": { 699 | "version": "2.0.3", 700 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 701 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 702 | } 703 | } 704 | }, 705 | "http-signature": { 706 | "version": "1.2.0", 707 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 708 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 709 | "requires": { 710 | "assert-plus": "^1.0.0", 711 | "jsprim": "^1.2.2", 712 | "sshpk": "^1.7.0" 713 | } 714 | }, 715 | "iconv-lite": { 716 | "version": "0.4.24", 717 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 718 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 719 | "requires": { 720 | "safer-buffer": ">= 2.1.2 < 3" 721 | } 722 | }, 723 | "ignore-walk": { 724 | "version": "3.0.3", 725 | "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", 726 | "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", 727 | "requires": { 728 | "minimatch": "^3.0.4" 729 | } 730 | }, 731 | "inflight": { 732 | "version": "1.0.6", 733 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 734 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 735 | "requires": { 736 | "once": "^1.3.0", 737 | "wrappy": "1" 738 | } 739 | }, 740 | "inherits": { 741 | "version": "2.0.4", 742 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 743 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 744 | }, 745 | "ini": { 746 | "version": "1.3.5", 747 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 748 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" 749 | }, 750 | "ipaddr.js": { 751 | "version": "1.9.1", 752 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 753 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 754 | }, 755 | "is-fullwidth-code-point": { 756 | "version": "1.0.0", 757 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 758 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 759 | "requires": { 760 | "number-is-nan": "^1.0.0" 761 | } 762 | }, 763 | "is-typedarray": { 764 | "version": "1.0.0", 765 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 766 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 767 | }, 768 | "isarray": { 769 | "version": "1.0.0", 770 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 771 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 772 | }, 773 | "isstream": { 774 | "version": "0.1.2", 775 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 776 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 777 | }, 778 | "jsbn": { 779 | "version": "0.1.1", 780 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 781 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 782 | }, 783 | "json-schema": { 784 | "version": "0.2.3", 785 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 786 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 787 | }, 788 | "json-schema-traverse": { 789 | "version": "0.4.1", 790 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 791 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 792 | }, 793 | "json-stringify-safe": { 794 | "version": "5.0.1", 795 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 796 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 797 | }, 798 | "jsonwebtoken": { 799 | "version": "8.5.1", 800 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 801 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 802 | "requires": { 803 | "jws": "^3.2.2", 804 | "lodash.includes": "^4.3.0", 805 | "lodash.isboolean": "^3.0.3", 806 | "lodash.isinteger": "^4.0.4", 807 | "lodash.isnumber": "^3.0.3", 808 | "lodash.isplainobject": "^4.0.6", 809 | "lodash.isstring": "^4.0.1", 810 | "lodash.once": "^4.0.0", 811 | "ms": "^2.1.1", 812 | "semver": "^5.6.0" 813 | } 814 | }, 815 | "jsprim": { 816 | "version": "1.4.1", 817 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 818 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 819 | "requires": { 820 | "assert-plus": "1.0.0", 821 | "extsprintf": "1.3.0", 822 | "json-schema": "0.2.3", 823 | "verror": "1.10.0" 824 | } 825 | }, 826 | "jwa": { 827 | "version": "1.4.1", 828 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 829 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 830 | "requires": { 831 | "buffer-equal-constant-time": "1.0.1", 832 | "ecdsa-sig-formatter": "1.0.11", 833 | "safe-buffer": "^5.0.1" 834 | } 835 | }, 836 | "jws": { 837 | "version": "3.2.2", 838 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 839 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 840 | "requires": { 841 | "jwa": "^1.4.1", 842 | "safe-buffer": "^5.0.1" 843 | } 844 | }, 845 | "kareem": { 846 | "version": "2.3.1", 847 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", 848 | "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" 849 | }, 850 | "lodash": { 851 | "version": "4.17.19", 852 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", 853 | "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" 854 | }, 855 | "lodash.includes": { 856 | "version": "4.3.0", 857 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 858 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 859 | }, 860 | "lodash.isboolean": { 861 | "version": "3.0.3", 862 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 863 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 864 | }, 865 | "lodash.isinteger": { 866 | "version": "4.0.4", 867 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 868 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 869 | }, 870 | "lodash.isnumber": { 871 | "version": "3.0.3", 872 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 873 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 874 | }, 875 | "lodash.isplainobject": { 876 | "version": "4.0.6", 877 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 878 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 879 | }, 880 | "lodash.isstring": { 881 | "version": "4.0.1", 882 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 883 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 884 | }, 885 | "lodash.once": { 886 | "version": "4.1.1", 887 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 888 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 889 | }, 890 | "media-typer": { 891 | "version": "0.3.0", 892 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 893 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 894 | }, 895 | "memory-pager": { 896 | "version": "1.5.0", 897 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 898 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 899 | "optional": true 900 | }, 901 | "merge-descriptors": { 902 | "version": "1.0.1", 903 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 904 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 905 | }, 906 | "methods": { 907 | "version": "1.1.2", 908 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 909 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 910 | }, 911 | "mime": { 912 | "version": "1.6.0", 913 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 914 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 915 | }, 916 | "mime-db": { 917 | "version": "1.44.0", 918 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 919 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 920 | }, 921 | "mime-types": { 922 | "version": "2.1.27", 923 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 924 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 925 | "requires": { 926 | "mime-db": "1.44.0" 927 | } 928 | }, 929 | "minimatch": { 930 | "version": "3.0.4", 931 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 932 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 933 | "requires": { 934 | "brace-expansion": "^1.1.7" 935 | } 936 | }, 937 | "minimist": { 938 | "version": "1.2.5", 939 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 940 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 941 | }, 942 | "minipass": { 943 | "version": "2.9.0", 944 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", 945 | "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", 946 | "requires": { 947 | "safe-buffer": "^5.1.2", 948 | "yallist": "^3.0.0" 949 | } 950 | }, 951 | "minizlib": { 952 | "version": "1.3.3", 953 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", 954 | "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", 955 | "requires": { 956 | "minipass": "^2.9.0" 957 | } 958 | }, 959 | "mkdirp": { 960 | "version": "0.5.5", 961 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 962 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 963 | "requires": { 964 | "minimist": "^1.2.5" 965 | } 966 | }, 967 | "mongodb": { 968 | "version": "3.5.9", 969 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.9.tgz", 970 | "integrity": "sha512-vXHBY1CsGYcEPoVWhwgxIBeWqP3dSu9RuRDsoLRPTITrcrgm1f0Ubu1xqF9ozMwv53agmEiZm0YGo+7WL3Nbug==", 971 | "requires": { 972 | "bl": "^2.2.0", 973 | "bson": "^1.1.4", 974 | "denque": "^1.4.1", 975 | "require_optional": "^1.0.1", 976 | "safe-buffer": "^5.1.2", 977 | "saslprep": "^1.0.0" 978 | } 979 | }, 980 | "mongoose": { 981 | "version": "5.9.19", 982 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.19.tgz", 983 | "integrity": "sha512-wJ5FR2ykvyd17MRHA6sku/N1CMaC/kf4CnN357htD48RpzJhW60YDkxPSPLbkLg8Woa+i7jYi0glhzC0EcBcRQ==", 984 | "requires": { 985 | "bson": "^1.1.4", 986 | "kareem": "2.3.1", 987 | "mongodb": "3.5.9", 988 | "mongoose-legacy-pluralize": "1.0.2", 989 | "mpath": "0.7.0", 990 | "mquery": "3.2.2", 991 | "ms": "2.1.2", 992 | "regexp-clone": "1.0.0", 993 | "safe-buffer": "5.1.2", 994 | "sift": "7.0.1", 995 | "sliced": "1.0.1" 996 | } 997 | }, 998 | "mongoose-legacy-pluralize": { 999 | "version": "1.0.2", 1000 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 1001 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 1002 | }, 1003 | "mpath": { 1004 | "version": "0.7.0", 1005 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", 1006 | "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" 1007 | }, 1008 | "mquery": { 1009 | "version": "3.2.2", 1010 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", 1011 | "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", 1012 | "requires": { 1013 | "bluebird": "3.5.1", 1014 | "debug": "3.1.0", 1015 | "regexp-clone": "^1.0.0", 1016 | "safe-buffer": "5.1.2", 1017 | "sliced": "1.0.1" 1018 | }, 1019 | "dependencies": { 1020 | "debug": { 1021 | "version": "3.1.0", 1022 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 1023 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 1024 | "requires": { 1025 | "ms": "2.0.0" 1026 | } 1027 | }, 1028 | "ms": { 1029 | "version": "2.0.0", 1030 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1031 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1032 | } 1033 | } 1034 | }, 1035 | "ms": { 1036 | "version": "2.1.2", 1037 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1038 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1039 | }, 1040 | "needle": { 1041 | "version": "2.5.0", 1042 | "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", 1043 | "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", 1044 | "requires": { 1045 | "debug": "^3.2.6", 1046 | "iconv-lite": "^0.4.4", 1047 | "sax": "^1.2.4" 1048 | } 1049 | }, 1050 | "negotiator": { 1051 | "version": "0.6.2", 1052 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1053 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 1054 | }, 1055 | "nocache": { 1056 | "version": "2.1.0", 1057 | "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", 1058 | "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" 1059 | }, 1060 | "node-addon-api": { 1061 | "version": "3.0.0", 1062 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", 1063 | "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==" 1064 | }, 1065 | "node-fetch": { 1066 | "version": "2.6.0", 1067 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", 1068 | "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" 1069 | }, 1070 | "node-geocoder": { 1071 | "version": "3.27.0", 1072 | "resolved": "https://registry.npmjs.org/node-geocoder/-/node-geocoder-3.27.0.tgz", 1073 | "integrity": "sha512-fNMi9smx56wFhG+2sd0qVsq5RgNlkUuQQ7UWqPwynoMb0GjxSP9joAn8wah4YDv6UzZu3b41cNmd0BglEPkC+Q==", 1074 | "requires": { 1075 | "bluebird": "^3.5.2", 1076 | "node-fetch": "^2.6.0", 1077 | "request": "^2.88.0", 1078 | "request-promise": "^4.2.2" 1079 | }, 1080 | "dependencies": { 1081 | "bluebird": { 1082 | "version": "3.7.2", 1083 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 1084 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" 1085 | } 1086 | } 1087 | }, 1088 | "node-pre-gyp": { 1089 | "version": "0.15.0", 1090 | "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", 1091 | "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", 1092 | "requires": { 1093 | "detect-libc": "^1.0.2", 1094 | "mkdirp": "^0.5.3", 1095 | "needle": "^2.5.0", 1096 | "nopt": "^4.0.1", 1097 | "npm-packlist": "^1.1.6", 1098 | "npmlog": "^4.0.2", 1099 | "rc": "^1.2.7", 1100 | "rimraf": "^2.6.1", 1101 | "semver": "^5.3.0", 1102 | "tar": "^4.4.2" 1103 | } 1104 | }, 1105 | "nodemailer": { 1106 | "version": "6.4.10", 1107 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.10.tgz", 1108 | "integrity": "sha512-j+pS9CURhPgk6r0ENr7dji+As2xZiHSvZeVnzKniLOw1eRAyM/7flP0u65tCnsapV8JFu+t0l/5VeHsCZEeh9g==" 1109 | }, 1110 | "nopt": { 1111 | "version": "4.0.3", 1112 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", 1113 | "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", 1114 | "requires": { 1115 | "abbrev": "1", 1116 | "osenv": "^0.1.4" 1117 | } 1118 | }, 1119 | "npm-bundled": { 1120 | "version": "1.1.1", 1121 | "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", 1122 | "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", 1123 | "requires": { 1124 | "npm-normalize-package-bin": "^1.0.1" 1125 | } 1126 | }, 1127 | "npm-normalize-package-bin": { 1128 | "version": "1.0.1", 1129 | "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", 1130 | "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" 1131 | }, 1132 | "npm-packlist": { 1133 | "version": "1.4.8", 1134 | "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", 1135 | "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", 1136 | "requires": { 1137 | "ignore-walk": "^3.0.1", 1138 | "npm-bundled": "^1.0.1", 1139 | "npm-normalize-package-bin": "^1.0.1" 1140 | } 1141 | }, 1142 | "npmlog": { 1143 | "version": "4.1.2", 1144 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 1145 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 1146 | "requires": { 1147 | "are-we-there-yet": "~1.1.2", 1148 | "console-control-strings": "~1.1.0", 1149 | "gauge": "~2.7.3", 1150 | "set-blocking": "~2.0.0" 1151 | } 1152 | }, 1153 | "number-is-nan": { 1154 | "version": "1.0.1", 1155 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1156 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 1157 | }, 1158 | "oauth-sign": { 1159 | "version": "0.9.0", 1160 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 1161 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 1162 | }, 1163 | "object-assign": { 1164 | "version": "4.1.1", 1165 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1166 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1167 | }, 1168 | "on-finished": { 1169 | "version": "2.3.0", 1170 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1171 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1172 | "requires": { 1173 | "ee-first": "1.1.1" 1174 | } 1175 | }, 1176 | "once": { 1177 | "version": "1.4.0", 1178 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1179 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1180 | "requires": { 1181 | "wrappy": "1" 1182 | } 1183 | }, 1184 | "os-homedir": { 1185 | "version": "1.0.2", 1186 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1187 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" 1188 | }, 1189 | "os-tmpdir": { 1190 | "version": "1.0.2", 1191 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1192 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 1193 | }, 1194 | "osenv": { 1195 | "version": "0.1.5", 1196 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", 1197 | "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", 1198 | "requires": { 1199 | "os-homedir": "^1.0.0", 1200 | "os-tmpdir": "^1.0.0" 1201 | } 1202 | }, 1203 | "parseurl": { 1204 | "version": "1.3.3", 1205 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1206 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 1207 | }, 1208 | "path-is-absolute": { 1209 | "version": "1.0.1", 1210 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1211 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1212 | }, 1213 | "path-to-regexp": { 1214 | "version": "0.1.7", 1215 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1216 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1217 | }, 1218 | "performance-now": { 1219 | "version": "2.1.0", 1220 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 1221 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 1222 | }, 1223 | "process-nextick-args": { 1224 | "version": "2.0.1", 1225 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1226 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 1227 | }, 1228 | "proxy-addr": { 1229 | "version": "2.0.6", 1230 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 1231 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 1232 | "requires": { 1233 | "forwarded": "~0.1.2", 1234 | "ipaddr.js": "1.9.1" 1235 | } 1236 | }, 1237 | "psl": { 1238 | "version": "1.8.0", 1239 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", 1240 | "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" 1241 | }, 1242 | "punycode": { 1243 | "version": "2.1.1", 1244 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1245 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 1246 | }, 1247 | "qs": { 1248 | "version": "6.7.0", 1249 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 1250 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 1251 | }, 1252 | "range-parser": { 1253 | "version": "1.2.1", 1254 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1255 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 1256 | }, 1257 | "raw-body": { 1258 | "version": "2.4.0", 1259 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 1260 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 1261 | "requires": { 1262 | "bytes": "3.1.0", 1263 | "http-errors": "1.7.2", 1264 | "iconv-lite": "0.4.24", 1265 | "unpipe": "1.0.0" 1266 | } 1267 | }, 1268 | "rc": { 1269 | "version": "1.2.8", 1270 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 1271 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 1272 | "requires": { 1273 | "deep-extend": "^0.6.0", 1274 | "ini": "~1.3.0", 1275 | "minimist": "^1.2.0", 1276 | "strip-json-comments": "~2.0.1" 1277 | } 1278 | }, 1279 | "readable-stream": { 1280 | "version": "2.3.7", 1281 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 1282 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 1283 | "requires": { 1284 | "core-util-is": "~1.0.0", 1285 | "inherits": "~2.0.3", 1286 | "isarray": "~1.0.0", 1287 | "process-nextick-args": "~2.0.0", 1288 | "safe-buffer": "~5.1.1", 1289 | "string_decoder": "~1.1.1", 1290 | "util-deprecate": "~1.0.1" 1291 | } 1292 | }, 1293 | "referrer-policy": { 1294 | "version": "1.2.0", 1295 | "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", 1296 | "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" 1297 | }, 1298 | "regexp-clone": { 1299 | "version": "1.0.0", 1300 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", 1301 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" 1302 | }, 1303 | "request": { 1304 | "version": "2.88.2", 1305 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", 1306 | "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", 1307 | "requires": { 1308 | "aws-sign2": "~0.7.0", 1309 | "aws4": "^1.8.0", 1310 | "caseless": "~0.12.0", 1311 | "combined-stream": "~1.0.6", 1312 | "extend": "~3.0.2", 1313 | "forever-agent": "~0.6.1", 1314 | "form-data": "~2.3.2", 1315 | "har-validator": "~5.1.3", 1316 | "http-signature": "~1.2.0", 1317 | "is-typedarray": "~1.0.0", 1318 | "isstream": "~0.1.2", 1319 | "json-stringify-safe": "~5.0.1", 1320 | "mime-types": "~2.1.19", 1321 | "oauth-sign": "~0.9.0", 1322 | "performance-now": "^2.1.0", 1323 | "qs": "~6.5.2", 1324 | "safe-buffer": "^5.1.2", 1325 | "tough-cookie": "~2.5.0", 1326 | "tunnel-agent": "^0.6.0", 1327 | "uuid": "^3.3.2" 1328 | }, 1329 | "dependencies": { 1330 | "qs": { 1331 | "version": "6.5.2", 1332 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 1333 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 1334 | } 1335 | } 1336 | }, 1337 | "request-promise": { 1338 | "version": "4.2.5", 1339 | "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", 1340 | "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", 1341 | "requires": { 1342 | "bluebird": "^3.5.0", 1343 | "request-promise-core": "1.1.3", 1344 | "stealthy-require": "^1.1.1", 1345 | "tough-cookie": "^2.3.3" 1346 | } 1347 | }, 1348 | "request-promise-core": { 1349 | "version": "1.1.3", 1350 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", 1351 | "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", 1352 | "requires": { 1353 | "lodash": "^4.17.15" 1354 | } 1355 | }, 1356 | "require_optional": { 1357 | "version": "1.0.1", 1358 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 1359 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 1360 | "requires": { 1361 | "resolve-from": "^2.0.0", 1362 | "semver": "^5.1.0" 1363 | } 1364 | }, 1365 | "resolve-from": { 1366 | "version": "2.0.0", 1367 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 1368 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 1369 | }, 1370 | "rimraf": { 1371 | "version": "2.7.1", 1372 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 1373 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 1374 | "requires": { 1375 | "glob": "^7.1.3" 1376 | } 1377 | }, 1378 | "safe-buffer": { 1379 | "version": "5.1.2", 1380 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1381 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1382 | }, 1383 | "safer-buffer": { 1384 | "version": "2.1.2", 1385 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1386 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1387 | }, 1388 | "saslprep": { 1389 | "version": "1.0.3", 1390 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 1391 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 1392 | "optional": true, 1393 | "requires": { 1394 | "sparse-bitfield": "^3.0.3" 1395 | } 1396 | }, 1397 | "sax": { 1398 | "version": "1.2.4", 1399 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 1400 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 1401 | }, 1402 | "semver": { 1403 | "version": "5.7.1", 1404 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1405 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 1406 | }, 1407 | "send": { 1408 | "version": "0.17.1", 1409 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 1410 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 1411 | "requires": { 1412 | "debug": "2.6.9", 1413 | "depd": "~1.1.2", 1414 | "destroy": "~1.0.4", 1415 | "encodeurl": "~1.0.2", 1416 | "escape-html": "~1.0.3", 1417 | "etag": "~1.8.1", 1418 | "fresh": "0.5.2", 1419 | "http-errors": "~1.7.2", 1420 | "mime": "1.6.0", 1421 | "ms": "2.1.1", 1422 | "on-finished": "~2.3.0", 1423 | "range-parser": "~1.2.1", 1424 | "statuses": "~1.5.0" 1425 | }, 1426 | "dependencies": { 1427 | "debug": { 1428 | "version": "2.6.9", 1429 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1430 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1431 | "requires": { 1432 | "ms": "2.0.0" 1433 | }, 1434 | "dependencies": { 1435 | "ms": { 1436 | "version": "2.0.0", 1437 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1438 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1439 | } 1440 | } 1441 | }, 1442 | "ms": { 1443 | "version": "2.1.1", 1444 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1445 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 1446 | } 1447 | } 1448 | }, 1449 | "serve-static": { 1450 | "version": "1.14.1", 1451 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 1452 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 1453 | "requires": { 1454 | "encodeurl": "~1.0.2", 1455 | "escape-html": "~1.0.3", 1456 | "parseurl": "~1.3.3", 1457 | "send": "0.17.1" 1458 | } 1459 | }, 1460 | "set-blocking": { 1461 | "version": "2.0.0", 1462 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1463 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1464 | }, 1465 | "setprototypeof": { 1466 | "version": "1.1.1", 1467 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 1468 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 1469 | }, 1470 | "sift": { 1471 | "version": "7.0.1", 1472 | "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", 1473 | "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" 1474 | }, 1475 | "signal-exit": { 1476 | "version": "3.0.3", 1477 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 1478 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 1479 | }, 1480 | "sliced": { 1481 | "version": "1.0.1", 1482 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 1483 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 1484 | }, 1485 | "slugify": { 1486 | "version": "1.4.0", 1487 | "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.0.tgz", 1488 | "integrity": "sha512-FtLNsMGBSRB/0JOE2A0fxlqjI6fJsgHGS13iTuVT28kViI4JjUiNqp/vyis0ZXYcMnpR3fzGNkv+6vRlI2GwdQ==" 1489 | }, 1490 | "sparse-bitfield": { 1491 | "version": "3.0.3", 1492 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 1493 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 1494 | "optional": true, 1495 | "requires": { 1496 | "memory-pager": "^1.0.2" 1497 | } 1498 | }, 1499 | "sshpk": { 1500 | "version": "1.16.1", 1501 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 1502 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 1503 | "requires": { 1504 | "asn1": "~0.2.3", 1505 | "assert-plus": "^1.0.0", 1506 | "bcrypt-pbkdf": "^1.0.0", 1507 | "dashdash": "^1.12.0", 1508 | "ecc-jsbn": "~0.1.1", 1509 | "getpass": "^0.1.1", 1510 | "jsbn": "~0.1.0", 1511 | "safer-buffer": "^2.0.2", 1512 | "tweetnacl": "~0.14.0" 1513 | } 1514 | }, 1515 | "statuses": { 1516 | "version": "1.5.0", 1517 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1518 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 1519 | }, 1520 | "stealthy-require": { 1521 | "version": "1.1.1", 1522 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 1523 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 1524 | }, 1525 | "streamsearch": { 1526 | "version": "0.1.2", 1527 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", 1528 | "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" 1529 | }, 1530 | "string-width": { 1531 | "version": "1.0.2", 1532 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1533 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1534 | "requires": { 1535 | "code-point-at": "^1.0.0", 1536 | "is-fullwidth-code-point": "^1.0.0", 1537 | "strip-ansi": "^3.0.0" 1538 | } 1539 | }, 1540 | "string_decoder": { 1541 | "version": "1.1.1", 1542 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1543 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1544 | "requires": { 1545 | "safe-buffer": "~5.1.0" 1546 | } 1547 | }, 1548 | "strip-ansi": { 1549 | "version": "3.0.1", 1550 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1551 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1552 | "requires": { 1553 | "ansi-regex": "^2.0.0" 1554 | } 1555 | }, 1556 | "strip-json-comments": { 1557 | "version": "2.0.1", 1558 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1559 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" 1560 | }, 1561 | "tar": { 1562 | "version": "4.4.13", 1563 | "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", 1564 | "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", 1565 | "requires": { 1566 | "chownr": "^1.1.1", 1567 | "fs-minipass": "^1.2.5", 1568 | "minipass": "^2.8.6", 1569 | "minizlib": "^1.2.1", 1570 | "mkdirp": "^0.5.0", 1571 | "safe-buffer": "^5.1.2", 1572 | "yallist": "^3.0.3" 1573 | } 1574 | }, 1575 | "toidentifier": { 1576 | "version": "1.0.0", 1577 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 1578 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 1579 | }, 1580 | "tough-cookie": { 1581 | "version": "2.5.0", 1582 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 1583 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 1584 | "requires": { 1585 | "psl": "^1.1.28", 1586 | "punycode": "^2.1.1" 1587 | } 1588 | }, 1589 | "tunnel-agent": { 1590 | "version": "0.6.0", 1591 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1592 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1593 | "requires": { 1594 | "safe-buffer": "^5.0.1" 1595 | } 1596 | }, 1597 | "tweetnacl": { 1598 | "version": "0.14.5", 1599 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1600 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 1601 | }, 1602 | "type-is": { 1603 | "version": "1.6.18", 1604 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1605 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1606 | "requires": { 1607 | "media-typer": "0.3.0", 1608 | "mime-types": "~2.1.24" 1609 | } 1610 | }, 1611 | "unpipe": { 1612 | "version": "1.0.0", 1613 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1614 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1615 | }, 1616 | "uri-js": { 1617 | "version": "4.2.2", 1618 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 1619 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 1620 | "requires": { 1621 | "punycode": "^2.1.0" 1622 | } 1623 | }, 1624 | "util-deprecate": { 1625 | "version": "1.0.2", 1626 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1627 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1628 | }, 1629 | "utils-merge": { 1630 | "version": "1.0.1", 1631 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1632 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1633 | }, 1634 | "uuid": { 1635 | "version": "3.4.0", 1636 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 1637 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" 1638 | }, 1639 | "vary": { 1640 | "version": "1.1.2", 1641 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1642 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1643 | }, 1644 | "verror": { 1645 | "version": "1.10.0", 1646 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1647 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 1648 | "requires": { 1649 | "assert-plus": "^1.0.0", 1650 | "core-util-is": "1.0.2", 1651 | "extsprintf": "^1.2.0" 1652 | } 1653 | }, 1654 | "wide-align": { 1655 | "version": "1.1.3", 1656 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 1657 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 1658 | "requires": { 1659 | "string-width": "^1.0.2 || 2" 1660 | } 1661 | }, 1662 | "wrappy": { 1663 | "version": "1.0.2", 1664 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1665 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1666 | }, 1667 | "x-xss-protection": { 1668 | "version": "1.3.0", 1669 | "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz", 1670 | "integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg==" 1671 | }, 1672 | "xss-clean": { 1673 | "version": "0.1.1", 1674 | "resolved": "https://registry.npmjs.org/xss-clean/-/xss-clean-0.1.1.tgz", 1675 | "integrity": "sha1-07poTYXM1SBUlj0BrWqzbWYtsaU=", 1676 | "requires": { 1677 | "xss-filters": "1.2.6" 1678 | } 1679 | }, 1680 | "xss-filters": { 1681 | "version": "1.2.6", 1682 | "resolved": "https://registry.npmjs.org/xss-filters/-/xss-filters-1.2.6.tgz", 1683 | "integrity": "sha1-aLOQicsd/4udvIiUhIObL1B/XFU=" 1684 | }, 1685 | "yallist": { 1686 | "version": "3.1.1", 1687 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1688 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1689 | } 1690 | } 1691 | } 1692 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devcamper", 3 | "version": "1.0.0", 4 | "description": "Devcamper Api", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "NODE_ENV=production node app.js", 8 | "dev": "nodemon app" 9 | }, 10 | "author": "Sajid Ansari", 11 | "license": "MIT", 12 | "dependencies": { 13 | "bcrypt": "^5.0.0", 14 | "colors": "^1.4.0", 15 | "cookie-parser": "^1.4.5", 16 | "cors": "^2.8.5", 17 | "dotenv": "^8.2.0", 18 | "express": "^4.17.1", 19 | "express-fileupload": "^1.1.7-alpha.3", 20 | "express-mongo-sanitize": "^2.0.0", 21 | "express-rate-limit": "^5.1.3", 22 | "helmet": "^3.23.3", 23 | "hpp": "^0.2.3", 24 | "jsonwebtoken": "^8.5.1", 25 | "mongoose": "^5.9.19", 26 | "node-geocoder": "^3.27.0", 27 | "nodemailer": "^6.4.10", 28 | "slugify": "^1.4.0", 29 | "xss-clean": "^0.1.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/uploads/photo5d725a1b7b292f5f8ceff788.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SajidAnTechie/Udemy-Clone-API/df24d6383af10cd1ed85de6919c340f742e96d50/public/uploads/photo5d725a1b7b292f5f8ceff788.jpg -------------------------------------------------------------------------------- /routes/auth.js: -------------------------------------------------------------------------------- 1 | const { 2 | RegisterUser, 3 | login, 4 | updateDetails, 5 | updatePassword, 6 | forgotPassword, 7 | resetPassword, 8 | logout, 9 | } = require("../controller/auth"); 10 | const { protect } = require("../middleware/auth"); 11 | 12 | const router = require("express").Router(); 13 | 14 | router.route("/register").post(RegisterUser); 15 | router.route("/login").post(login); 16 | router.route("/logout").get(logout); 17 | router.route("/update/userDetails").put(protect, updateDetails); 18 | router.route("/update/password").put(protect, updatePassword); 19 | router.route("/forgotPassword").post(forgotPassword); 20 | router.route("/resetPassword/:resetToken").post(resetPassword); 21 | 22 | module.exports = router; 23 | -------------------------------------------------------------------------------- /routes/bootcamp.js: -------------------------------------------------------------------------------- 1 | const { 2 | getAllbootcamps, 3 | getBootcamps, 4 | createBootcamps, 5 | updateBootcamps, 6 | deleteBootcamps, 7 | bootcampUploadPhoto, 8 | getBootcampsInRadius, 9 | } = require("../controller/bootcamp"); 10 | 11 | //Invoked middleware. 12 | const advanceResults = require("../middleware/advanceResults"); 13 | const { protect, permission } = require("../middleware/auth"); 14 | 15 | //Bootcamp model 16 | const Bootcamp = require("../models/Bootcamp"); 17 | 18 | //Include other resource Router 19 | const courseRouter = require("./course"); 20 | const reviewRouter = require("./review"); 21 | 22 | const router = require("express").Router(); 23 | 24 | //Re-route into other resource router 25 | router.use("/:bootcampId/courses", courseRouter); 26 | router.use("/:bootcampId/reviews", reviewRouter); 27 | 28 | router.route("/radius/:zipcode/:distance").get(getBootcampsInRadius); 29 | router 30 | .route("/") 31 | .get( 32 | advanceResults(Bootcamp, { path: "Courses", select: "title description" }), 33 | getAllbootcamps 34 | ) 35 | .post(protect, permission("admin", "publisher"), createBootcamps); 36 | router 37 | .route("/:id/photo") 38 | .put(protect, permission("admin", "publisher"), bootcampUploadPhoto); 39 | router 40 | .route("/:id") 41 | .get(getBootcamps) 42 | .put(protect, permission("admin", "publisher"), updateBootcamps) 43 | .delete(protect, permission("admin", "publisher"), deleteBootcamps); 44 | 45 | module.exports = router; 46 | -------------------------------------------------------------------------------- /routes/course.js: -------------------------------------------------------------------------------- 1 | const { 2 | getCourses, 3 | getCourse, 4 | createCourse, 5 | updateCourse, 6 | deleteCourse, 7 | updateTutuionCost, 8 | } = require("../controller/course"); 9 | 10 | const router = require("express").Router({ mergeParams: true }); 11 | 12 | //Invoked middleware 13 | const advanceResults = require("../middleware/advanceResults"); 14 | const { protect, permission } = require("../middleware/auth"); 15 | 16 | //Course model 17 | const Course = require("../models/Course"); 18 | 19 | router 20 | .route("/") 21 | .get( 22 | advanceResults(Course, { path: "bootcamp", select: "name description" }), 23 | getCourses 24 | ) 25 | .post(protect, permission("admin", "publisher"), createCourse); 26 | router 27 | .route("/:id") 28 | .get(getCourse) 29 | .put(protect, permission("admin", "publisher"), updateCourse) 30 | .delete(protect, permission("admin", "publisher"), deleteCourse); 31 | 32 | router 33 | .route("/updateTutionCost/:id") 34 | .put(protect, permission("admin", "publisher"), updateTutuionCost); 35 | 36 | module.exports = router; 37 | -------------------------------------------------------------------------------- /routes/review.js: -------------------------------------------------------------------------------- 1 | const { 2 | getReview, 3 | getReviews, 4 | createReview, 5 | updateReview, 6 | deleteReview, 7 | updateRating, 8 | } = require("../controller/review"); 9 | 10 | const router = require("express").Router({ mergeParams: true }); 11 | 12 | //Invoked middleware 13 | const advanceResults = require("../middleware/advanceResults"); 14 | const { protect, permission } = require("../middleware/auth"); 15 | 16 | //Review model 17 | const Review = require("../models/Review"); 18 | 19 | router 20 | .route("/") 21 | .get( 22 | advanceResults(Review, { path: "bootcamp", select: "name description" }), 23 | getReviews 24 | ) 25 | .post(protect, permission("user", "admin"), createReview); 26 | 27 | router 28 | .route("/:id") 29 | .get(getReview) 30 | .put(protect, permission("user", "admin"), updateReview) 31 | .delete(protect, permission("user", "admin"), deleteReview); 32 | router 33 | .route("/updateRating/:id") 34 | .put(protect, permission("user", "admin"), updateRating); 35 | 36 | module.exports = router; 37 | -------------------------------------------------------------------------------- /routes/user.js: -------------------------------------------------------------------------------- 1 | const { 2 | getUsers, 3 | getUser, 4 | createUser, 5 | updateUser, 6 | deleteUser, 7 | } = require("../controller/users"); 8 | 9 | //Invoked middleware. 10 | const advanceResults = require("../middleware/advanceResults"); 11 | const { protect, permission } = require("../middleware/auth"); 12 | 13 | //User model 14 | const User = require("../models/User"); 15 | 16 | const router = require("express").Router(); 17 | 18 | router.use(protect); 19 | router.use(permission("admin")); 20 | 21 | router.route("/").get(advanceResults(User), getUsers).post(createUser); 22 | 23 | router.route("/:id").get(getUser).put(updateUser).delete(deleteUser); 24 | 25 | module.exports = router; 26 | -------------------------------------------------------------------------------- /seeder.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const mongoose = require("mongoose"); 3 | const colors = require("colors"); 4 | const dotenv = require("dotenv"); 5 | 6 | //load env vars 7 | dotenv.config({ path: "./config/config.env" }); 8 | 9 | //load models 10 | const Bootcamp = require("./models/Bootcamp"); 11 | const Course = require("./models/Course"); 12 | const User = require("./models/User"); 13 | const Review = require("./models/Review"); 14 | 15 | // Connect to DB 16 | 17 | mongoose.connect(process.env.MONGO_URI, { 18 | useNewUrlParser: true, 19 | useCreateIndex: true, 20 | useFindAndModify: false, 21 | useUnifiedTopology: true, 22 | }); 23 | 24 | //Read JSON files 25 | 26 | const bootcamps = JSON.parse( 27 | fs.readFileSync(`${__dirname}/_data/bootcamps.json`, "utf-8") 28 | ); 29 | const courses = JSON.parse( 30 | fs.readFileSync(`${__dirname}/_data/courses.json`, "utf-8") 31 | ); 32 | const users = JSON.parse( 33 | fs.readFileSync(`${__dirname}/_data/users.json`, "utf-8") 34 | ); 35 | const reviews = JSON.parse( 36 | fs.readFileSync(`${__dirname}/_data/reviews.json`, "utf-8") 37 | ); 38 | 39 | //Import into DB 40 | 41 | const importData = async () => { 42 | try { 43 | await Bootcamp.create(bootcamps); 44 | await Course.create(courses); 45 | await User.create(users); 46 | await Review.create(reviews); 47 | console.log(`Data Imported`.green.inverse); 48 | process.exit(); 49 | } catch (error) { 50 | console.log(error); 51 | } 52 | }; 53 | 54 | const deleteData = async () => { 55 | try { 56 | await Bootcamp.deleteMany(); 57 | await Course.deleteMany(); 58 | await User.deleteMany(); 59 | await Review.deleteMany(); 60 | console.log("Data Destroy".red.inverse); 61 | process.exit(); 62 | } catch (error) { 63 | console.log(error); 64 | } 65 | }; 66 | 67 | if (process.argv[2] === "-i") { 68 | importData(); 69 | } else if (process.argv[2] === "-d") { 70 | deleteData(); 71 | } 72 | -------------------------------------------------------------------------------- /utilis/createError.js: -------------------------------------------------------------------------------- 1 | module.exports = (status, message) => { 2 | const error = new Error(message); 3 | error.status = status; 4 | return error; 5 | }; 6 | -------------------------------------------------------------------------------- /utilis/geocoder.js: -------------------------------------------------------------------------------- 1 | const NodeGeocoder = require("node-geocoder"); 2 | const options = { 3 | provider: "openstreetmap", 4 | httpAdapter: "https", 5 | //apiKey: process.env.GEOCODER_API_KEY, 6 | formatter: null, 7 | }; 8 | 9 | const geocoder = NodeGeocoder(options); 10 | 11 | module.exports = geocoder; 12 | -------------------------------------------------------------------------------- /utilis/jwt.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const createError = require("./createError"); 3 | 4 | const verifyToken = (token, secret) => { 5 | try { 6 | return jwt.verify(token, secret); 7 | } catch (error) { 8 | if (error.name === "TokenExpiredError") 9 | throw createError(410, "Token is expired. Please Login"); 10 | 11 | throw error; 12 | } 13 | }; 14 | 15 | module.exports = verifyToken; 16 | -------------------------------------------------------------------------------- /utilis/sendEmail.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require("nodemailer"); 2 | 3 | const sendEmail = async (options) => { 4 | let transporter = nodemailer.createTransport({ 5 | host: process.env.SMTP_HOST, 6 | port: process.env.SMTP_PORT, 7 | auth: { 8 | user: process.env.SMTP_EMAIL, 9 | pass: process.env.SMTP_PASSWORD, 10 | }, 11 | }); 12 | 13 | const message = { 14 | from: `${process.env.FROM_NAME} <${process.env.FROM_EMAIL}>`, 15 | to: options.email, 16 | subject: options.subject, 17 | text: options.message, 18 | }; 19 | 20 | let info = await transporter.sendMail(message); 21 | 22 | console.log("Message sent: %s", info.messageId); 23 | }; 24 | module.exports = sendEmail; 25 | --------------------------------------------------------------------------------