From 2687581565b4d0994ea9974b9f3a7e6bd9b40bc6 Mon Sep 17 00:00:00 2001 From: AlbertKhomich Date: Sat, 4 Feb 2023 08:36:20 +0100 Subject: [PATCH] added readme and starter config --- _data/bootcamps.json | 67 + _data/courses.json | 101 + _data/reviews.json | 66 + _data/users.json | 107 + config/config.env.env | 21 + config/db.js | 12 + controllers/auth.js | 211 ++ controllers/bootcamps.js | 206 ++ controllers/courses.js | 145 ++ controllers/reviews.js | 124 ++ controllers/users.js | 61 + middleware/advancedResults.js | 81 + middleware/async.js | 4 + middleware/auth.js | 54 + middleware/error.js | 35 + middleware/logger.js | 9 + models/Bootcamp.js | 156 ++ models/Course.js | 84 + models/Review.js | 72 + models/User.js | 78 + package-lock.json | 1763 ++++++++++++++++- package.json | 18 +- public/fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes public/index.html | 195 ++ .../photo_5d725a1b7b292f5f8ceff788.jpg | Bin 0 -> 59899 bytes readme.md | 26 + routes/auth.js | 26 + routes/bootcamps.js | 44 + routes/courses.js | 34 + routes/reviews.js | 34 + routes/users.js | 24 + seeder.js | 69 + server.js | 88 +- utils/errorResponse.js | 8 + utils/geocoder.js | 12 + utils/sendEmail.js | 25 + 38 files changed, 4052 insertions(+), 8 deletions(-) create mode 100644 _data/bootcamps.json create mode 100644 _data/courses.json create mode 100644 _data/reviews.json create mode 100644 _data/users.json create mode 100644 config/config.env.env create mode 100644 config/db.js create mode 100644 controllers/auth.js create mode 100644 controllers/bootcamps.js create mode 100644 controllers/courses.js create mode 100644 controllers/reviews.js create mode 100644 controllers/users.js create mode 100644 middleware/advancedResults.js create mode 100644 middleware/async.js create mode 100644 middleware/auth.js create mode 100644 middleware/error.js create mode 100644 middleware/logger.js create mode 100644 models/Bootcamp.js create mode 100644 models/Course.js create mode 100644 models/Review.js create mode 100644 models/User.js create mode 100644 public/fonts/glyphicons-halflings-regular.eot create mode 100644 public/fonts/glyphicons-halflings-regular.woff create mode 100644 public/fonts/glyphicons-halflings-regular.woff2 create mode 100644 public/index.html create mode 100644 public/uploads/photo_5d725a1b7b292f5f8ceff788.jpg create mode 100644 readme.md create mode 100644 routes/auth.js create mode 100644 routes/bootcamps.js create mode 100644 routes/courses.js create mode 100644 routes/reviews.js create mode 100644 routes/users.js create mode 100644 seeder.js create mode 100644 utils/errorResponse.js create mode 100644 utils/geocoder.js create mode 100644 utils/sendEmail.js diff --git a/_data/bootcamps.json b/_data/bootcamps.json new file mode 100644 index 0000000..20f791e --- /dev/null +++ b/_data/bootcamps.json @@ -0,0 +1,67 @@ +[ + { + "_id": "5d713995b721c3bb38c1f5d0", + "user": "5d7a514b5d2c12c7449be045", + "name": "Devworks Bootcamp", + "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", + "website": "https://devworks.com", + "phone": "(111) 111-1111", + "email": "enroll@devworks.com", + "address": "233 Bay State Rd Boston MA 02215", + "careers": ["Web Development", "UI/UX", "Business"], + "housing": true, + "jobAssistance": true, + "jobGuarantee": false, + "acceptGi": true + }, + { + "_id": "5d713a66ec8f2b88b8f830b8", + "user": "5d7a514b5d2c12c7449be046", + "name": "ModernTech Bootcamp", + "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", + "website": "https://moderntech.com", + "phone": "(222) 222-2222", + "email": "enroll@moderntech.com", + "address": "220 Pawtucket St, Lowell, MA 01854", + "careers": ["Web Development", "UI/UX", "Mobile Development"], + "housing": false, + "jobAssistance": true, + "jobGuarantee": false, + "acceptGi": true + }, + { + "_id": "5d725a037b292f5f8ceff787", + "user": "5c8a1d5b0190b214360dc031", + "name": "Codemasters", + "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", + "website": "https://codemasters.com", + "phone": "(333) 333-3333", + "email": "enroll@codemasters.com", + "address": "85 South Prospect Street Burlington VT 05405", + "careers": ["Web Development", "Data Science", "Business"], + "housing": false, + "jobAssistance": false, + "jobGuarantee": false, + "acceptGi": false + }, + { + "_id": "5d725a1b7b292f5f8ceff788", + "user": "5c8a1d5b0190b214360dc032", + "name": "Devcentral Bootcamp", + "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", + "website": "https://devcentral.com", + "phone": "(444) 444-4444", + "email": "enroll@devcentral.com", + "address": "45 Upper College Rd Kingston RI 02881", + "careers": [ + "Mobile Development", + "Web Development", + "Data Science", + "Business" + ], + "housing": false, + "jobAssistance": true, + "jobGuarantee": true, + "acceptGi": true + } +] diff --git a/_data/courses.json b/_data/courses.json new file mode 100644 index 0000000..4a0a634 --- /dev/null +++ b/_data/courses.json @@ -0,0 +1,101 @@ +[ + { + "_id": "5d725a4a7b292f5f8ceff789", + "title": "Front End Web Development", + "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", + "weeks": 8, + "tuition": 8000, + "minimumSkill": "beginner", + "scholarhipsAvailable": true, + "bootcamp": "5d713995b721c3bb38c1f5d0", + "user": "5d7a514b5d2c12c7449be045" + }, + { + "_id": "5d725c84c4ded7bcb480eaa0", + "title": "Full Stack Web Development", + "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", + "weeks": 12, + "tuition": 10000, + "minimumSkill": "intermediate", + "scholarhipsAvailable": true, + "bootcamp": "5d713995b721c3bb38c1f5d0", + "user": "5d7a514b5d2c12c7449be045" + }, + { + "_id": "5d725cb9c4ded7bcb480eaa1", + "title": "Full Stack Web Dev", + "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", + "weeks": 10, + "tuition": 12000, + "minimumSkill": "intermediate", + "scholarhipsAvailable": true, + "bootcamp": "5d713a66ec8f2b88b8f830b8", + "user": "5d7a514b5d2c12c7449be046" + }, + { + "_id": "5d725cd2c4ded7bcb480eaa2", + "title": "UI/UX", + "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", + "weeks": 12, + "tuition": 10000, + "minimumSkill": "intermediate", + "scholarhipsAvailable": true, + "bootcamp": "5d713a66ec8f2b88b8f830b8", + "user": "5d7a514b5d2c12c7449be046" + }, + { + "_id": "5d725ce8c4ded7bcb480eaa3", + "title": "Web Design & Development", + "description": "Get started building websites and web apps with HTML/CSS/JavaScript/PHP. We teach you", + "weeks": 10, + "tuition": 9000, + "minimumSkill": "beginner", + "scholarhipsAvailable": true, + "bootcamp": "5d725a037b292f5f8ceff787", + "user": "5c8a1d5b0190b214360dc031" + }, + { + "_id": "5d725cfec4ded7bcb480eaa4", + "title": "Data Science Program", + "description": "In this course you will learn Python for data science, machine learning and big data tools", + "weeks": 10, + "tuition": 12000, + "minimumSkill": "intermediate", + "scholarhipsAvailable": false, + "bootcamp": "5d725a037b292f5f8ceff787", + "user": "5c8a1d5b0190b214360dc031" + }, + { + "_id": "5d725cfec4ded7bcb480eaa5", + "title": "Web Development", + "description": "This course will teach you how to build high quality web applications with technologies like React, Node.js, PHP & Laravel", + "weeks": 8, + "tuition": 8000, + "minimumSkill": "beginner", + "scholarhipsAvailable": false, + "bootcamp": "5d725a1b7b292f5f8ceff788", + "user": "5c8a1d5b0190b214360dc032" + }, + { + "_id": "5d725cfec4ded7bcb480eaa6", + "title": "Software QA", + "description": "This course will teach you everything you need to know about quality assurance", + "weeks": 6, + "tuition": 5000, + "minimumSkill": "intermediate", + "scholarhipsAvailable": false, + "bootcamp": "5d725a1b7b292f5f8ceff788", + "user": "5c8a1d5b0190b214360dc032" + }, + { + "_id": "5d725cfec4ded7bcb480eaa7", + "title": "IOS Development", + "description": "Get started building mobile applications for IOS using Swift and other tools", + "weeks": 8, + "tuition": 6000, + "minimumSkill": "intermediate", + "scholarhipsAvailable": false, + "bootcamp": "5d725a1b7b292f5f8ceff788", + "user": "5c8a1d5b0190b214360dc032" + } +] diff --git a/_data/reviews.json b/_data/reviews.json new file mode 100644 index 0000000..4fea31e --- /dev/null +++ b/_data/reviews.json @@ -0,0 +1,66 @@ +[ + { + "_id": "5d7a514b5d2c12c7449be020", + "title": "Learned a ton!", + "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", + "rating": "8", + "bootcamp": "5d713995b721c3bb38c1f5d0", + "user": "5c8a1d5b0190b214360dc033" + }, + { + "_id": "5d7a514b5d2c12c7449be021", + "title": "Great bootcamp", + "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", + "rating": "10", + "bootcamp": "5d713995b721c3bb38c1f5d0", + "user": "5c8a1d5b0190b214360dc034" + }, + { + "_id": "5d7a514b5d2c12c7449be022", + "title": "Got me a developer job", + "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", + "rating": "7", + "bootcamp": "5d713a66ec8f2b88b8f830b8", + "user": "5c8a1d5b0190b214360dc035" + }, + { + "_id": "5d7a514b5d2c12c7449be023", + "title": "Not that great", + "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", + "rating": "4", + "bootcamp": "5d713a66ec8f2b88b8f830b8", + "user": "5c8a1d5b0190b214360dc036" + }, + { + "_id": "5d7a514b5d2c12c7449be024", + "title": "Great overall experience", + "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", + "rating": "7", + "bootcamp": "5d725a037b292f5f8ceff787", + "user": "5c8a1d5b0190b214360dc037" + }, + { + "_id": "5d7a514b5d2c12c7449be025", + "title": "Not worth the money", + "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", + "rating": "5", + "bootcamp": "5d725a037b292f5f8ceff787", + "user": "5c8a1d5b0190b214360dc038" + }, + { + "_id": "5d7a514b5d2c12c7449be026", + "title": "Best instructors", + "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", + "rating": "10", + "bootcamp": "5d725a1b7b292f5f8ceff788", + "user": "5c8a1d5b0190b214360dc039" + }, + { + "_id": "5d7a514b5d2c12c7449be027", + "title": "Was worth the investment", + "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", + "rating": "7", + "bootcamp": "5d725a1b7b292f5f8ceff788", + "user": "5c8a1d5b0190b214360dc040" + } +] diff --git a/_data/users.json b/_data/users.json new file mode 100644 index 0000000..f7a4c9b --- /dev/null +++ b/_data/users.json @@ -0,0 +1,107 @@ +[ + { + "_id": "5d7a514b5d2c12c7449be042", + "name": "Admin Account", + "email": "admin@gmail.com", + "role": "user", + "password": "123456" + }, + { + "_id": "5d7a514b5d2c12c7449be043", + "name": "Publisher Account", + "email": "publisher@gmail.com", + "role": "publisher", + "password": "123456" + }, + { + "_id": "5d7a514b5d2c12c7449be044", + "name": "User Account", + "email": "user@gmail.com", + "role": "user", + "password": "123456" + }, + { + "_id": "5d7a514b5d2c12c7449be045", + "name": "John Doe", + "email": "john@gmail.com", + "role": "publisher", + "password": "123456" + }, + { + "_id": "5d7a514b5d2c12c7449be046", + "name": "Kevin Smith", + "email": "kevin@gmail.com", + "role": "publisher", + "password": "123456" + }, + { + "_id": "5c8a1d5b0190b214360dc031", + "name": "Mary Williams", + "email": "mary@gmail.com", + "role": "publisher", + "password": "123456" + }, + { + "_id": "5c8a1d5b0190b214360dc032", + "name": "Sasha Ryan", + "email": "sasha@gmail.com", + "role": "publisher", + "password": "123456" + }, + { + "_id": "5c8a1d5b0190b214360dc033", + "name": "Greg Harris", + "email": "greg@gmail.com", + "role": "user", + "password": "123456" + }, + { + "_id": "5c8a1d5b0190b214360dc034", + "name": "Derek Glover", + "email": "derek@gmail.com", + "role": "user", + "password": "123456" + }, + { + "_id": "5c8a1d5b0190b214360dc035", + "name": "Stephanie Hanson", + "email": "steph@gmail.com", + "role": "user", + "password": "123456" + }, + { + "_id": "5c8a1d5b0190b214360dc036", + "name": "Jerry Wiliams", + "email": "jerry@gmail.com", + "role": "user", + "password": "123456" + }, + { + "_id": "5c8a1d5b0190b214360dc037", + "name": "Maggie Johnson", + "email": "maggie@gmail.com", + "role": "user", + "password": "123456" + }, + { + "_id": "5c8a1d5b0190b214360dc038", + "name": "Barry Dickens", + "email": "barry@gmail.com", + "role": "user", + "password": "123456" + }, + { + "_id": "5c8a1d5b0190b214360dc039", + "name": "Ryan Bolin", + "email": "ryan@gmail.com", + "role": "user", + "password": "123456" + }, + { + "_id": "5c8a1d5b0190b214360dc040", + "name": "Sara Kensing", + "email": "sara@gmail.com", + "role": "user", + "password": "123456" + } +] diff --git a/config/config.env.env b/config/config.env.env new file mode 100644 index 0000000..e6a67d3 --- /dev/null +++ b/config/config.env.env @@ -0,0 +1,21 @@ +NODE_ENV=development +PORT=5000 + +MONGO_URI= + +GEOCODER_PROVIDER=mapquest +GEOCODER_API_KEY= + +FILE_UPLOAD_PATH= ./public/uploads +MAX_FILE_UPLOAD=1000000 + +JWT_SECRET= +JWT_EXPIRE=30d +JWT_COOKIE_EXPIRE=30 + +SMTP_HOST=smtp.mailtrap.io +SMTP_PORT=2525 +SMTP_EMAIL= +SMTP_PASSWORD= +FROM_EMAIL= +FROM_NAME= \ No newline at end of file diff --git a/config/db.js b/config/db.js new file mode 100644 index 0000000..e9b3a7b --- /dev/null +++ b/config/db.js @@ -0,0 +1,12 @@ +const mongoose = require('mongoose'); + +const connectDB = async () => { + mongoose.set('strictQuery', false); + const conn = await mongoose.connect(process.env.MONGO_URI, { + useNewUrlParser: true, + }); + + console.log(`MongoDB Connected: ${conn.connection.host}`.cyan.underline.bold); +}; + +module.exports = connectDB; diff --git a/controllers/auth.js b/controllers/auth.js new file mode 100644 index 0000000..926f560 --- /dev/null +++ b/controllers/auth.js @@ -0,0 +1,211 @@ +const crypto = require('crypto'); +const ErrorResponse = require('../utils/errorResponse'); +const asyncHandler = require('../middleware/async'); +const sendEmail = require('../utils/sendEmail'); +const User = require('../models/User'); + +//@desc Register user +//@route POST /api/v1/auth/register +//@access Public +exports.register = asyncHandler(async (req, res, next) => { + const { name, email, password, role } = req.body; + + //Create user + const user = await User.create({ + name, + password, + email, + role, + }); + + sendTokenResponse(user, 200, res); +}); + +//@desc Login user +//@route POST /api/v1/auth/login +//@access Public +exports.login = asyncHandler(async (req, res, next) => { + const { email, password } = req.body; + + //Validate email and password + if (!email || !password) { + return next(new ErrorResponse('Please provide an email and password', 400)); + } + + //Check for user + const user = await User.findOne({ email }).select('+password'); + + if (!user) { + return next(new ErrorResponse('Invalid credentials', 401)); + } + + //Check if password matches + const isMatch = await user.matchPassword(password); + + if (!isMatch) { + return next(new ErrorResponse('Invalid credentials', 401)); + } + + sendTokenResponse(user, 200, res); +}); + +//@desc Log user out / clear cookie +//@route GET /api/v1/auth/logout +//@access Private +exports.logout = asyncHandler(async (req, res, next) => { + res.cookie('token', 'none', { + expires: new Date(Date.now() + 10 * 1000), + httpOnly: true, + }); + + res.status(200).json({ + success: true, + data: {}, + }); +}); + +//@desc Get current logged in user +//@route POST /api/v1/auth/me +//@access Private +exports.getMe = asyncHandler(async (req, res, next) => { + const user = await User.findById(req.user.id); + + res.status(200).json({ + success: true, + data: user, + }); +}); + +//@desc Update user details +//@route PUT /api/v1/auth/updatedetails +//@access Private +exports.updateDetails = asyncHandler(async (req, res, next) => { + const fieldsToUpdate = { + name: req.body.name, + email: req.body.email, + }; + + const user = await User.findByIdAndUpdate(req.user.id, fieldsToUpdate, { + new: true, + runValidators: true, + }); + + res.status(200).json({ + success: true, + data: user, + }); +}); + +//@desc Update password +//@route PUT /api/v1/auth/updatepassword +//@access Private +exports.updatePassword = asyncHandler(async (req, res, next) => { + const user = await User.findById(req.user.id).select('+password'); + + //Check current password + if (!(await user.matchPassword(req.body.currentPassword))) { + return next(new ErrorResponse('Password is incorrect', 401)); + } + + user.password = req.body.newPassword; + await user.save(); + + sendTokenResponse(user, 200, res); +}); + +//@desc Frogot password +//@route POST /api/v1/auth/forgotpassword +//@access Public +exports.forgotPassword = asyncHandler(async (req, res, next) => { + const user = await User.findOne({ email: req.body.email }); + + if (!user) { + return next(new ErrorResponse('There is no user with that email', 404)); + } + + //Get reset token + const resetToken = user.getResetPasswordToken(); + + await user.save({ validateBeforeSave: false }); + + //Create reset url + const resetUrl = `${req.protocol}://${req.get( + 'host' + )}/api/v1/auth/resetpassword/${resetToken}`; + + const message = `You are receiving this email because you (or someone else) + has requested the reset of a password. Please make a PUT request to: \n\n ${resetUrl}`; + + try { + await sendEmail({ + email: user.email, + subject: 'Password reset token', + message, + }); + + res.status(200).json({ success: true, data: 'Email sent' }); + } catch (err) { + console.log(err); + user.resetPasswordToken = undefined; + user.resetPasswordExpire = undefined; + + await user.save({ validateBeforeSave: false }); + + return next(new ErrorResponse('Email could not be sent', 500)); + } + + res.status(200).json({ + success: true, + data: user, + }); +}); + +//@desc Reset password +//@route PUT /api/v1/auth/resetpassword/:resettoken +//@access Public +exports.resetPassword = asyncHandler(async (req, res, next) => { + //Get hashed token + const resetPasswordToken = crypto + .createHash('sha256') + .update(req.params.resettoken) + .digest('hex'); + + const user = await User.findOne({ + resetPasswordToken, + resetPasswordExpire: { $gt: Date.now() }, + }); + + if (!user) { + return next(new ErrorResponse('Invalid token', 400)); + } + + //Set new password + user.password = req.body.password; + user.resetPasswordToken = undefined; + user.resetPasswordExpire = undefined; + await user.save(); + + sendTokenResponse(user, 200, res); +}); + +//Get token from model, create cookie and send response +const sendTokenResponse = (user, statusCode, res) => { + //Create token + const token = user.getSignedJwtToken(); + + const options = { + expires: new Date( + Date.now() + process.env.JWT_COOKIE_EXPIRE * 24 * 60 * 60 * 1000 + ), + httpOnly: true, + }; + + if (process.env.NODE_ENV === 'production') { + options.secure = true; + } + + res.status(statusCode).cookie('token', token, options).json({ + success: true, + token, + }); +}; diff --git a/controllers/bootcamps.js b/controllers/bootcamps.js new file mode 100644 index 0000000..337ad13 --- /dev/null +++ b/controllers/bootcamps.js @@ -0,0 +1,206 @@ +const path = require('path'); +const ErrorResponse = require('../utils/errorResponse'); +const asyncHandler = require('../middleware/async'); +const geocoder = require('../utils/geocoder'); +const Bootcamp = require('../models/Bootcamp'); + +//@desc Get all bootcamps +//@route GET /api/v1/bootcamps +//@access Public + +exports.getBootcamps = asyncHandler(async (req, res, next) => { + res.status(200).json(res.advancedResults); +}); + +//@desc Get single bootcamp +//@route GET /api/v1/bootcamps/:id +//@access Public + +exports.getBootcamp = asyncHandler(async (req, res, next) => { + const bootcamp = await Bootcamp.findById(req.params.id); + + if (!bootcamp) { + return next( + new ErrorResponse(`Bootcamp not found with id of ${req.params.id}`, 404) + ); + } + + res.status(200).json({ success: true, data: bootcamp }); +}); + +//@desc Create new bootcamp +//@route POST /api/v1/bootcamps +//@access Private +exports.createBootcamp = asyncHandler(async (req, res, next) => { + //Add user to req.body + req.body.user = req.user.id; + + //Check for published bootcamp + const publishedBootcamp = await Bootcamp.findOne({ user: req.user.id }); + + //If the user is not an admin, they can only add one bootcamp + if (publishedBootcamp && req.user.role !== 'admin') { + return next( + new ErrorResponse( + `The user with ID ${req.user.id} has already published a bootcamp`, + 400 + ) + ); + } + + const bootcamp = await Bootcamp.create(req.body); + + res.status(201).json({ + success: true, + data: bootcamp, + }); +}); + +//@desc Update bootcamp +//@route PUT /api/v1/bootcamps:id +//@access Private + +exports.updateBootcamp = asyncHandler(async (req, res, next) => { + let bootcamp = await Bootcamp.findById(req.params.id); + + if (!bootcamp) { + return next( + new ErrorResponse(`Bootcamp not found with id of ${req.params.id}`, 404) + ); + } + + //Make sure user is bootcamp owner + if (bootcamp.user.toString() !== req.user.id && req.user.role !== 'admin') { + return next( + new ErrorResponse( + `User ${req.params.id} is not authorized to update this bootcamp`, + 401 + ) + ); + } + + bootcamp = await Bootcamp.findOneAndUpdate(req.params.id, req.body, { + new: true, + runValidators: true, + }); + + res.status(200).json({ success: true, data: bootcamp }); +}); + +//@desc Delete bootcamp +//@route DELETE /api/v1/bootcamps/:id +//@access Private + +exports.deleteBootcamp = asyncHandler(async (req, res, next) => { + const bootcamp = await Bootcamp.findById(req.params.id, req.body); + + if (!bootcamp) { + return next( + new ErrorResponse(`Bootcamp not found with id of ${req.params.id}`, 404) + ); + } + + //Make sure user is bootcamp owner + if (bootcamp.user.toString() !== req.user.id && req.user.role !== 'admin') { + return next( + new ErrorResponse( + `User ${req.params.id} is not authorized to delete this bootcamp`, + 401 + ) + ); + } + + bootcamp.remove(); + + res.status(200).json({ success: true, data: {} }); +}); + +//@desc Get bootcamp within a radius +//@route Get /api/v1/bootcamps/radius/:zipcode/:distance +//@access Private + +exports.getBootcampsInRadius = asyncHandler(async (req, res, next) => { + const { zipcode, distance } = req.params; + + //Get lat/lng from Geocoder + const loc = await geocoder.geocode(zipcode); + const lat = loc[0].latitude; + const lng = loc[0].longitude; + + //Calc radius using radians + //Divide dest by radius of Earth + //Earth radius = 3,963 mi / 6.378 km + const radius = distance / 6378; + + const bootcamps = await Bootcamp.find({ + location: { $geoWithin: { $centerSphere: [[lng, lat], radius] } }, + }); + + res.status(200).json({ + success: true, + count: bootcamps.length, + data: bootcamps, + }); +}); + +//@desc Upload photo for bootcamp +//@route PUT /api/v1/bootcamps/:id/photo +//@access Private + +exports.bootcampPhotoUpload = asyncHandler(async (req, res, next) => { + const bootcamp = await Bootcamp.findById(req.params.id, req.body); + + if (!bootcamp) { + return next( + new ErrorResponse(`Bootcamp not found with id of ${req.params.id}`, 404) + ); + } + + //Make sure user is bootcamp owner + if (bootcamp.user.toString() !== req.user.id && req.user.role !== 'admin') { + return next( + new ErrorResponse( + `User ${req.params.id} is not authorized to update this bootcamp`, + 401 + ) + ); + } + + if (!req.files) { + return next(new ErrorResponse(`Please upload a file`, 400)); + } + + const file = req.files.file; + + //Make sure the image is a photo + if (!file.mimetype.startsWith('image')) { + return next(new ErrorResponse(`Please upload an image file`, 400)); + } + + //Check file size + if (file.size > process.env.MAX_FILE_UPLOAD) { + return next( + new ErrorResponse( + `Please upload an image less than ${process.env.MAX_FILE_UPLOAD}`, + 400 + ) + ); + } + + //Create custom file name + file.name = `photo_${bootcamp._id}${path.parse(file.name).ext}`; + + file.mv(`${process.env.FILE_UPLOAD_PATH}/${file.name}`, async (err) => { + if (err) { + console.error(err); + return next(new ErrorResponse(`Problem with file upload`, 500)); + } + + await Bootcamp.findByIdAndUpdate(req.params.id, { photo: file.name }); + + res.status(200).json({ + success: true, + data: file.name, + }); + }); +}); diff --git a/controllers/courses.js b/controllers/courses.js new file mode 100644 index 0000000..9424c6a --- /dev/null +++ b/controllers/courses.js @@ -0,0 +1,145 @@ +const ErrorResponse = require('../utils/errorResponse'); +const asyncHandler = require('../middleware/async'); +const Course = require('../models/Course'); +const Bootcamp = require('../models/Bootcamp'); + +//@desc Get courses +//@route GET /api/v1/courses +//@route GET /api/v1/bootcamp/:bootcampId/courses +//@access Public +exports.getCourses = asyncHandler(async (req, res, next) => { + if (req.params.bootcampId) { + const courses = await Course.find({ bootcamp: req.params.bootcampId }); + + return res.status(200).json({ + success: true, + count: courses.length, + data: courses, + }); + } else { + res.status(200).json(res.advancedResults); + } +}); + +//@desc Get single course +//@route GET /api/v1/courses/:id +//@access Public +exports.getCourse = asyncHandler(async (req, res, next) => { + const course = await Course.findById(req.params.id).populate({ + path: 'bootcamp', + select: 'name description', + }); + + if (!course) { + return next( + new ErrorResponse(`No course with the id of ${req.params.id}`), + 404 + ); + } + + res.status(200).json({ + success: true, + data: course, + }); +}); + +//@desc Add course +//@route POST /api/v1/bootcamps/:bootcampId/courses +//@access Private +exports.addCourse = asyncHandler(async (req, res, next) => { + req.body.bootcamp = req.params.bootcampId; + req.body.user = req.user.id; + + const bootcamp = await Bootcamp.findById(req.params.bootcampId); + + if (!bootcamp) { + return next( + new ErrorResponse(`No bootcamp with the id of ${req.params.bootcampId}`), + 404 + ); + } + + //Make sure user is bootcamp owner + if (bootcamp.user.toString() !== req.user.id && req.user.role !== 'admin') { + return next( + new ErrorResponse( + `User ${req.user.id} is not authorized to add a course to bootcamp ${bootcamp._id}`, + 401 + ) + ); + } + + const course = await Course.create(req.body); + + res.status(200).json({ + success: true, + data: course, + }); +}); + +//@desc Update course +//@route PUT /api/v1/courses/:id +//@access Private +exports.updateCourse = asyncHandler(async (req, res, next) => { + let course = await Course.findById(req.params.id); + + if (!course) { + return next( + new ErrorResponse(`No course with the id of ${req.params.id}`), + 404 + ); + } + + //Make sure user is course owner + if (course.user.toString() !== req.user.id && req.user.role !== 'admin') { + return next( + new ErrorResponse( + `User ${req.user.id} is not authorized to update course ${course._id}`, + 401 + ) + ); + } + + course = await Course.findByIdAndUpdate(req.params.id, req.body, { + new: true, + runValidators: true, + }); + + await course.save(); + + res.status(200).json({ + success: true, + data: course, + }); +}); + +//@desc Delete course +//@route DELETE /api/v1/courses/:id +//@access Private +exports.deleteCourse = asyncHandler(async (req, res, next) => { + const course = await Course.findById(req.params.id); + + if (!course) { + return next( + new ErrorResponse(`No course with the id of ${req.params.id}`), + 404 + ); + } + + //Make sure user is course owner + if (course.user.toString() !== req.user.id && req.user.role !== 'admin') { + return next( + new ErrorResponse( + `User ${req.user.id} is not authorized to delete course ${course._id}`, + 401 + ) + ); + } + + await course.remove(); + + res.status(200).json({ + success: true, + data: {}, + }); +}); diff --git a/controllers/reviews.js b/controllers/reviews.js new file mode 100644 index 0000000..d7ede4e --- /dev/null +++ b/controllers/reviews.js @@ -0,0 +1,124 @@ +const ErrorResponse = require('../utils/errorResponse'); +const asyncHandler = require('../middleware/async'); +const Review = require('../models/Review'); +const Bootcamp = require('../models/Bootcamp'); + +//@desc Get reviews +//@route GET /api/v1/reviews +//@route GET /api/v1/bootcamp/:bootcampId/reviews +//@access Public +exports.getReviews = asyncHandler(async (req, res, next) => { + if (req.params.bootcampId) { + const reviews = await Review.find({ bootcamp: req.params.bootcampId }); + + return res.status(200).json({ + success: true, + count: reviews.length, + data: reviews, + }); + } else { + res.status(200).json(res.advancedResults); + } +}); + +//@desc Get single review +//@route GET /api/v1/reviews/:id +//@access Public +exports.getReview = asyncHandler(async (req, res, next) => { + const review = await Review.findById(req.params.id).populate({ + path: 'bootcamp', + select: 'name description', + }); + + if (!review) { + return next( + new ErrorResponse(`No review found with the id of ${req.params.id}`, 404) + ); + } + + res.status(200).json({ + success: true, + data: review, + }); +}); + +//@desc Add review +//@route POST /api/v1/bootcamps/:bootcampId/reviews +//@access Private +exports.addReview = asyncHandler(async (req, res, next) => { + req.body.bootcamp = req.params.bootcampId; + req.body.user = req.user.id; + + const bootcamp = await Bootcamp.findById(req.params.bootcampId); + + if (!bootcamp) { + return next( + new ErrorResponse( + `No bootcamp with the id of ${req.params.bootcampId}`, + 404 + ) + ); + } + + const review = await Review.create(req.body); + + res.status(201).json({ + success: true, + data: review, + }); +}); + +//@desc Update review +//@route PUT /api/v1/reviews/:id +//@access Private +exports.updateReview = asyncHandler(async (req, res, next) => { + let review = await Review.findById(req.params.id); + + if (!review) { + return next( + new ErrorResponse(`No review with the id of ${req.params.id}`, 404) + ); + } + + //Make sure review belongs to user or user is an admin + if (review.user.toString() !== req.user.id && req.user.role !== 'admin') { + return next(new ErrorResponse(`Not authorized to update review`, 401)); + } + + review = await Review.findByIdAndUpdate(req.params.id, req.body, { + new: true, + runValidators: true, + }); + + await review.save(); + + res.status(200).json({ + success: true, + data: review, + }); +}); + +//@desc Delete review +//@route DELETE /api/v1/reviews/:id +//@access Private +exports.deleteReview = asyncHandler(async (req, res, next) => { + const review = await Review.findById(req.params.id); + + if (!review) { + return next( + new ErrorResponse(`No review with the id of ${req.params.id}`, 404) + ); + } + + //Make sure review belongs to user or user is an admin + if (review.user.toString() !== req.user.id && req.user.role !== 'admin') { + return next(new ErrorResponse(`Not authorized to update review`, 401)); + } + + await review.remove(); + + res.status(200).json({ + success: true, + data: {}, + }); +}); diff --git a/controllers/users.js b/controllers/users.js new file mode 100644 index 0000000..01cd85d --- /dev/null +++ b/controllers/users.js @@ -0,0 +1,61 @@ +const ErrorResponse = require('../utils/errorResponse'); +const asyncHandler = require('../middleware/async'); +const User = require('../models/User'); + +//@desc Get all users +//@route GET /api/v1/auth/users +//@access Private/admin +exports.getUsers = asyncHandler(async (req, res, next) => { + res.status(200).json(res.advancedResults); +}); + +//@desc Get single user +//@route GET /api/v1/auth/users/:id +//@access Private/admin +exports.getUser = asyncHandler(async (req, res, next) => { + const user = await User.findById(req.params.id); + + res.status(200).json({ + success: true, + data: user, + }); +}); + +//@desc Create user +//@route POST /api/v1/auth/users +//@access Private/admin +exports.createUser = asyncHandler(async (req, res, next) => { + const user = await User.create(req.body); + + res.status(201).json({ + success: true, + data: user, + }); +}); + +//@desc Update user +//@route PUT /api/v1/auth/users/:id +//@access Private/admin +exports.updateUser = asyncHandler(async (req, res, next) => { + const user = await User.findByIdAndUpdate(req.params.id, req.body, { + new: true, + runValidators: true, + }); + + res.status(200).json({ + success: true, + data: user, + }); +}); + +//@desc Delete user +//@route DELETE /api/v1/auth/users/:id +//@access Private/admin +exports.deleteUser = asyncHandler(async (req, res, next) => { + await User.findByIdAndDelete(req.params.id); + + res.status(200).json({ + success: true, + data: {}, + }); +}); diff --git a/middleware/advancedResults.js b/middleware/advancedResults.js new file mode 100644 index 0000000..5aef033 --- /dev/null +++ b/middleware/advancedResults.js @@ -0,0 +1,81 @@ +const advancedResults = (model, populate) => async (req, res, next) => { + let query; + + // Copy req.query + const reqQuery = { ...req.query }; + + //Fields to exclude + const removeFields = ['select', 'sort', 'page', 'limit']; + + //Loop over removeFields and delete them from reqQuery + removeFields.forEach((param) => delete reqQuery[param]); + + //Create query string + let queryStr = JSON.stringify(reqQuery); + + //Create operator like ($gt, $gte, etc) + queryStr = queryStr.replace( + /\b(gt|gte|lt|lte|in)\b/g, + (match) => `$${match}` + ); + + //Finding resource + query = model.find(JSON.parse(queryStr)); + + //Select fields + if (req.query.select) { + const fields = req.query.select.split(',').join(' '); + query = query.select(fields); + } + + // Sort + if (req.query.sort) { + const sortBy = req.query.sort.split(',').join(' '); + query = query.sort(sortBy); + } else { + query = query.sort('-createdAt'); + } + + // Pagination + const page = parseInt(req.query.page, 10) || 1; + const limit = parseInt(req.query.limit, 10) || 25; + const startIndex = (page - 1) * limit; + const endIndex = page * limit; + const total = await model.countDocuments(); + + query = query.skip(startIndex).limit(limit); + + if (populate) { + query = query.populate(populate); + } + + //Executing query + const results = await query; + + // Pagination result + const pagination = {}; + + if (endIndex < total) { + pagination.next = { + page: page + 1, + limit, + }; + } + + if (startIndex > 0) { + pagination.prev = { + page: page - 1, + limit, + }; + } + res.advancedResults = { + success: true, + count: results.length, + pagination, + data: results, + }; + + next(); +}; + +module.exports = advancedResults; diff --git a/middleware/async.js b/middleware/async.js new file mode 100644 index 0000000..126a578 --- /dev/null +++ b/middleware/async.js @@ -0,0 +1,4 @@ +const asyncHandler = (fn) => (req, res, next) => + Promise.resolve(fn(req, res, next)).catch(next); + +module.exports = asyncHandler; diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 0000000..8fb0fb9 --- /dev/null +++ b/middleware/auth.js @@ -0,0 +1,54 @@ +const jwt = require('jsonwebtoken'); +const asyncHandler = require('./async'); +const ErrorResponse = require('../utils/errorResponse'); +const User = require('../models/User'); + +// Protect routes +exports.protect = asyncHandler(async (req, res, next) => { + let token; + + if ( + req.headers.authorization && + req.headers.authorization.startsWith('Bearer') + ) { + // Set token from Bearer token in header + token = req.headers.authorization.split(' ')[1]; + // Set token from cookie + } + // else if (req.cookies.token) { + // token = req.cookies.token; + // } + + //Make sure token exists + if (!token) { + return next(new ErrorResponse('Not authorized to access this route', 401)); + } + + try { + //Verify token + const decoded = jwt.verify(token, process.env.JWT_SECRET); + + console.log(decoded); + + req.user = await User.findById(decoded.id); + + next(); + } catch (err) { + return next(new ErrorResponse('Not authorized to access this route', 401)); + } +}); + +//Grant access to specific roles +exports.authorize = (...roles) => { + return (req, res, next) => { + if (!roles.includes(req.user.role)) { + return next( + new ErrorResponse( + `User role ${req.user.role} is not authorized to acces this route`, + 403 + ) + ); + } + next(); + }; +}; diff --git a/middleware/error.js b/middleware/error.js new file mode 100644 index 0000000..db25a85 --- /dev/null +++ b/middleware/error.js @@ -0,0 +1,35 @@ +const ErrorResponse = require('../utils/errorResponse'); + +const errorHandler = (err, req, res, next) => { + let error = { ...err }; + + error.message = err.message; + + // Log to console for dev + console.log(err); + + // Mongoose bad ObjectId + if (err.name === 'CastError') { + const message = `Resource not found`; + error = new ErrorResponse(message, 404); + } + + // Mongoose duplicate key + if (err.code === 11000) { + const message = 'Duplicate field value entered'; + error = new ErrorResponse(message, 400); + } + + //Mongoose validation error + if (err.name === 'ValidationError') { + const message = Object.values(err.errors).map((val) => val.message); + error = new ErrorResponse(message, 400); + } + + res.status(error.statusCode || 500).json({ + success: false, + error: error.message || 'Server Error', + }); +}; + +module.exports = errorHandler; diff --git a/middleware/logger.js b/middleware/logger.js new file mode 100644 index 0000000..108f66a --- /dev/null +++ b/middleware/logger.js @@ -0,0 +1,9 @@ +//@desc Logs request to console +const logger = (req, res, next) => { + console.log( + `${req.method} ${req.protocol}://${req.get('host')}${req.originalUrl}` + ); + next(); +}; + +module.exports = logger; diff --git a/models/Bootcamp.js b/models/Bootcamp.js new file mode 100644 index 0000000..24c5c00 --- /dev/null +++ b/models/Bootcamp.js @@ -0,0 +1,156 @@ +const mongoose = require('mongoose'); +const slugify = require('slugify'); +const geocoder = require('../utils/geocoder'); + +const BootcampSchema = new mongoose.Schema( + { + name: { + type: String, + required: [true, 'Please add a name'], + unique: true, + trim: true, + maxlenght: [50, 'Name can not ba more than 50 characters'], + }, + slug: String, + description: { + type: String, + required: [true, 'Please add a description'], + maxlenght: [500, 'Description can not ba more than 500 characters'], + }, + website: { + type: String, + match: [ + /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/, + 'Please use a valid URL with HTTP or HTTPS', + ], + }, + phone: { + type: String, + maxlenght: [20, 'Phone can not ba more than 20 characters'], + }, + email: { + type: String, + match: [ + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, + 'Please add a valid email', + ], + }, + address: { + type: String, + required: [true, 'Please add an address'], + }, + location: { + // GeoJSON Point + type: { + type: String, + enum: ['Point'], + //required: true, + }, + coordinates: { + type: [Number], + //required: true, + index: '2dsphere', + }, + formattedAddress: String, + street: String, + city: String, + state: String, + zipcode: String, + counry: String, + }, + careers: { + //Array of strings + type: [String], + required: true, + enum: [ + 'Web Development', + 'Mobile Development', + 'UI/UX', + 'Data Science', + 'Business', + 'Other', + ], + }, + averageRating: { + type: Number, + min: [1, 'Rating must be at least 1'], + max: [10, 'Rating can not be more than 10'], + }, + averageCost: Number, + photo: { + type: String, + default: 'no-photo.jpg', + }, + housing: { + type: Boolean, + default: false, + }, + jobAssistance: { + type: Boolean, + default: false, + }, + jobGuarantee: { + type: Boolean, + default: false, + }, + acceptGi: { + type: Boolean, + default: false, + }, + createdAt: { + type: Date, + default: Date.now, + }, + user: { + type: mongoose.Schema.ObjectId, + ref: 'User', + required: true, + }, + }, + { + toJSON: { virtuals: true }, + toObject: { virtuals: true }, + } +); + +// Create Bootcamp slug from the name +BootcampSchema.pre('save', function (next) { + this.slug = slugify(this.name, { lower: true }); + next(); +}); + +// Geocode & create location field +BootcampSchema.pre('save', async function (next) { + const loc = await geocoder.geocode(this.address); + this.location = { + type: 'Point', + coordinates: [loc[0].longitude, loc[0].latitude], + formattedAddress: loc[0].formattedAddress, + street: loc[0].streetName, + city: loc[0].city, + state: loc[0].stateCode, + zipcode: loc[0].zipcode, + country: loc[0].countryCode, + }; + + // Do not save address in DB + this.address = undefined; + next(); +}); + +//Cascade delete courses when a bootcamp is deleted +BootcampSchema.pre('remove', async function (next) { + console.log(`Courses being removed from bootcamp ${this._id}`); + await this.model('Course').deleteMany({ bootcamp: this._id }); + next(); +}); + +//Reverse populate with virtuals +BootcampSchema.virtual('courses', { + ref: 'Course', + localField: '_id', + foreignField: 'bootcamp', + justOne: false, +}); + +module.exports = mongoose.model('Bootcamp', BootcampSchema); diff --git a/models/Course.js b/models/Course.js new file mode 100644 index 0000000..069c97e --- /dev/null +++ b/models/Course.js @@ -0,0 +1,84 @@ +const mongoose = require('mongoose'); + +const CourseSchema = new mongoose.Schema({ + title: { + type: String, + trim: true, + required: [true, 'Please add a course title'], + }, + description: { + type: String, + required: [true, 'Please add a course description'], + }, + weeks: { + type: String, + required: [true, 'Please add number of weeks'], + }, + tuition: { + type: Number, + required: [true, 'Please add a tuition cost'], + }, + minimumSkill: { + type: String, + required: [true, 'Please add a minimum skill'], + enum: ['beginner', 'intermediate', 'advanced'], + }, + scholarshipAvailable: { + type: Boolean, + default: false, + }, + createdAt: { + type: Date, + default: Date.now, + }, + bootcamp: { + type: mongoose.Schema.ObjectId, + ref: 'Bootcamp', + required: true, + }, + user: { + type: mongoose.Schema.ObjectId, + ref: 'User', + required: true, + }, +}); + +//Static method to get avg of course tuitions +CourseSchema.statics.getAverageCost = async function (bootcampId) { + const obj = await this.aggregate([ + { + $match: { bootcamp: bootcampId }, + }, + { + $group: { + _id: '$bootcamp', + averageCost: { $avg: '$tuition' }, + }, + }, + ]); + + try { + await this.model('Bootcamp').findByIdAndUpdate( + bootcampId, + obj[0] + ? { + averageCost: Math.ceil(obj[0].averageCost), + } + : { averageCost: undefined } + ); + } catch (err) { + console.error(err); + } +}; + +//Call getAverageCost after save +CourseSchema.post('save', function () { + this.constructor.getAverageCost(this.bootcamp); +}); + +//Call getAverageCost before remove +CourseSchema.post('remove', function () { + this.constructor.getAverageCost(this.bootcamp); +}); + +module.exports = mongoose.model('Course', CourseSchema); diff --git a/models/Review.js b/models/Review.js new file mode 100644 index 0000000..08fbee9 --- /dev/null +++ b/models/Review.js @@ -0,0 +1,72 @@ +const mongoose = require('mongoose'); + +const ReviewSchema = new mongoose.Schema({ + title: { + type: String, + trim: true, + required: [true, 'Please add a title for the review'], + maxlength: 100, + }, + text: { + type: String, + required: [true, 'Please add some text'], + }, + rating: { + type: Number, + min: 1, + max: 10, + required: [true, 'Please add a rating between 1 and 10'], + }, + createdAt: { + type: Date, + default: Date.now, + }, + bootcamp: { + type: mongoose.Schema.ObjectId, + ref: 'Bootcamp', + required: true, + }, + user: { + type: mongoose.Schema.ObjectId, + ref: 'User', + required: true, + }, +}); + +//Prevent user from submitting more than one review per bootcamp +ReviewSchema.index({ bootcamp: 1, user: 1 }, { unique: true }); + +//Static method to get avg rating and save +ReviewSchema.statics.getAverageRating = async function (bootcampId) { + const obj = await this.aggregate([ + { + $match: { bootcamp: bootcampId }, + }, + { + $group: { + _id: '$bootcamp', + averageRating: { $avg: '$rating' }, + }, + }, + ]); + + try { + await this.model('Bootcamp').findByIdAndUpdate(bootcampId, { + averageRating: Math.ceil(obj[0].averageRating), + }); + } catch (err) { + console.error(err); + } +}; + +//Call getAverageCost after save +ReviewSchema.post('save', function () { + this.constructor.getAverageRating(this.bootcamp); +}); + +//Call getAverageCost before remove +ReviewSchema.post('remove', function () { + this.constructor.getAverageRating(this.bootcamp); +}); + +module.exports = mongoose.model('Review', ReviewSchema); diff --git a/models/User.js b/models/User.js new file mode 100644 index 0000000..d882483 --- /dev/null +++ b/models/User.js @@ -0,0 +1,78 @@ +const crypto = require('crypto'); +const mongoose = require('mongoose'); +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); + +const UserSchema = new mongoose.Schema({ + name: { + type: String, + required: [true, 'Please add a name'], + }, + email: { + type: String, + required: [true, 'Please add an email'], + unique: true, + match: [ + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, + 'Please add a valid email', + ], + }, + role: { + type: String, + enum: ['user', 'publisher'], + default: 'user', + }, + password: { + type: String, + required: [true, 'Please add a password'], + minlength: 6, + select: false, + }, + resetPasswordToken: String, + resetPasswordExpire: Date, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +//Encrypt password using bcrypt +UserSchema.pre('save', async function (next) { + if (!this.isModified('password')) { + next(); + } + + const salt = await bcrypt.genSalt(10); + this.password = await bcrypt.hash(this.password, salt); +}); + +//Sign JWT and return +UserSchema.methods.getSignedJwtToken = function () { + return jwt.sign({ id: this._id }, process.env.JWT_SECRET, { + expiresIn: process.env.JWT_EXPIRE, + }); +}; + +//Match user entered password to hashed password in database +UserSchema.methods.matchPassword = async function (enteredPassword) { + return await bcrypt.compare(enteredPassword, this.password); +}; + +// Generate and hash password token +UserSchema.methods.getResetPasswordToken = function () { + //Generate token + const resetToken = crypto.randomBytes(20).toString('hex'); + + //Hash token and set to resetPasswordToken field + this.resetPasswordToken = crypto + .createHash('sha256') + .update(resetToken) + .digest('hex'); + + //Set expire 10 min + this.resetPasswordExpire = Date.now() + 10 * 60 * 1000; + + return resetToken; +}; + +module.exports = mongoose.model('User', UserSchema); diff --git a/package-lock.json b/package-lock.json index 4bf3fcb..059156b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,1114 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "bcryptjs": "^2.4.3", + "colors": "^1.4.0", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", "dotenv": "^16.0.3", - "express": "^4.18.2" + "express": "^4.18.2", + "express-fileupload": "^1.4.0", + "express-mongo-sanitize": "^2.2.0", + "express-rate-limit": "^6.7.0", + "helmet": "^6.0.1", + "hpp": "^0.2.3", + "jsonwebtoken": "^9.0.0", + "mongoose": "^6.8.2", + "morgan": "^1.10.0", + "node-geocoder": "^4.2.0", + "nodemailer": "^6.9.0", + "slugify": "^1.6.5", + "xss-clean": "^0.1.1" }, "devDependencies": { "nodemon": "^2.0.20" } }, + "node_modules/@aws-crypto/ie11-detection": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", + "integrity": "sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw==", + "optional": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", + "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==", + "optional": true, + "dependencies": { + "@aws-crypto/ie11-detection": "^2.0.0", + "@aws-crypto/sha256-js": "^2.0.0", + "@aws-crypto/supports-web-crypto": "^2.0.0", + "@aws-crypto/util": "^2.0.0", + "@aws-sdk/types": "^3.1.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz", + "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==", + "optional": true, + "dependencies": { + "@aws-crypto/util": "^2.0.0", + "@aws-sdk/types": "^3.1.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz", + "integrity": "sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ==", + "optional": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true + }, + "node_modules/@aws-crypto/util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.2.tgz", + "integrity": "sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "^3.110.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true + }, + "node_modules/@aws-sdk/abort-controller": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.226.0.tgz", + "integrity": "sha512-cJVzr1xxPBd08voknXvR0RLgtZKGKt6WyDpH/BaPCu3rfSqWCDZKzwqe940eqosjmKrxC6pUZNKASIqHOQ8xxQ==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.241.0.tgz", + "integrity": "sha512-9X/MwcnSwWfB0ijggFjyBWa4gtlUAyI39eBaVSE0AxMcgLlHKedEK6w5F1RrtvWqb7KyJDsyAysVecU4E9zQQQ==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/client-sts": "3.241.0", + "@aws-sdk/config-resolver": "3.234.0", + "@aws-sdk/credential-provider-node": "3.241.0", + "@aws-sdk/fetch-http-handler": "3.226.0", + "@aws-sdk/hash-node": "3.226.0", + "@aws-sdk/invalid-dependency": "3.226.0", + "@aws-sdk/middleware-content-length": "3.226.0", + "@aws-sdk/middleware-endpoint": "3.226.0", + "@aws-sdk/middleware-host-header": "3.226.0", + "@aws-sdk/middleware-logger": "3.226.0", + "@aws-sdk/middleware-recursion-detection": "3.226.0", + "@aws-sdk/middleware-retry": "3.235.0", + "@aws-sdk/middleware-serde": "3.226.0", + "@aws-sdk/middleware-signing": "3.226.0", + "@aws-sdk/middleware-stack": "3.226.0", + "@aws-sdk/middleware-user-agent": "3.226.0", + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/node-http-handler": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/smithy-client": "3.234.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/util-base64": "3.208.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.208.0", + "@aws-sdk/util-defaults-mode-browser": "3.234.0", + "@aws-sdk/util-defaults-mode-node": "3.234.0", + "@aws-sdk/util-endpoints": "3.241.0", + "@aws-sdk/util-retry": "3.229.0", + "@aws-sdk/util-user-agent-browser": "3.226.0", + "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.208.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.241.0.tgz", + "integrity": "sha512-Jm4HR+RYAqKMEYZvvWaq0NYUKKonyInOeubObXH4BLXZpmUBSdYCSjjLdNJY3jkQoxbDVPVMIurVNh5zT5SMRw==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.234.0", + "@aws-sdk/fetch-http-handler": "3.226.0", + "@aws-sdk/hash-node": "3.226.0", + "@aws-sdk/invalid-dependency": "3.226.0", + "@aws-sdk/middleware-content-length": "3.226.0", + "@aws-sdk/middleware-endpoint": "3.226.0", + "@aws-sdk/middleware-host-header": "3.226.0", + "@aws-sdk/middleware-logger": "3.226.0", + "@aws-sdk/middleware-recursion-detection": "3.226.0", + "@aws-sdk/middleware-retry": "3.235.0", + "@aws-sdk/middleware-serde": "3.226.0", + "@aws-sdk/middleware-stack": "3.226.0", + "@aws-sdk/middleware-user-agent": "3.226.0", + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/node-http-handler": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/smithy-client": "3.234.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/util-base64": "3.208.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.208.0", + "@aws-sdk/util-defaults-mode-browser": "3.234.0", + "@aws-sdk/util-defaults-mode-node": "3.234.0", + "@aws-sdk/util-endpoints": "3.241.0", + "@aws-sdk/util-retry": "3.229.0", + "@aws-sdk/util-user-agent-browser": "3.226.0", + "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.208.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.241.0.tgz", + "integrity": "sha512-/Ml2QBGpGfUEeBrPzBZhSTBkHuXFD2EAZEIHGCBH4tKaURDI6/FoGI8P1Rl4BzoFt+II/Cr91Eox6YT9EwChsQ==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.234.0", + "@aws-sdk/fetch-http-handler": "3.226.0", + "@aws-sdk/hash-node": "3.226.0", + "@aws-sdk/invalid-dependency": "3.226.0", + "@aws-sdk/middleware-content-length": "3.226.0", + "@aws-sdk/middleware-endpoint": "3.226.0", + "@aws-sdk/middleware-host-header": "3.226.0", + "@aws-sdk/middleware-logger": "3.226.0", + "@aws-sdk/middleware-recursion-detection": "3.226.0", + "@aws-sdk/middleware-retry": "3.235.0", + "@aws-sdk/middleware-serde": "3.226.0", + "@aws-sdk/middleware-stack": "3.226.0", + "@aws-sdk/middleware-user-agent": "3.226.0", + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/node-http-handler": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/smithy-client": "3.234.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/util-base64": "3.208.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.208.0", + "@aws-sdk/util-defaults-mode-browser": "3.234.0", + "@aws-sdk/util-defaults-mode-node": "3.234.0", + "@aws-sdk/util-endpoints": "3.241.0", + "@aws-sdk/util-retry": "3.229.0", + "@aws-sdk/util-user-agent-browser": "3.226.0", + "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.208.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.241.0.tgz", + "integrity": "sha512-vmlG8cJzRf8skCtTJbA2wBvD2c3NQ5gZryzJvTKDS06KzBzcEpnjlLseuTekcnOiRNekbFUX5hRu5Zj3N2ReLg==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.234.0", + "@aws-sdk/credential-provider-node": "3.241.0", + "@aws-sdk/fetch-http-handler": "3.226.0", + "@aws-sdk/hash-node": "3.226.0", + "@aws-sdk/invalid-dependency": "3.226.0", + "@aws-sdk/middleware-content-length": "3.226.0", + "@aws-sdk/middleware-endpoint": "3.226.0", + "@aws-sdk/middleware-host-header": "3.226.0", + "@aws-sdk/middleware-logger": "3.226.0", + "@aws-sdk/middleware-recursion-detection": "3.226.0", + "@aws-sdk/middleware-retry": "3.235.0", + "@aws-sdk/middleware-sdk-sts": "3.226.0", + "@aws-sdk/middleware-serde": "3.226.0", + "@aws-sdk/middleware-signing": "3.226.0", + "@aws-sdk/middleware-stack": "3.226.0", + "@aws-sdk/middleware-user-agent": "3.226.0", + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/node-http-handler": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/smithy-client": "3.234.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/util-base64": "3.208.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.208.0", + "@aws-sdk/util-defaults-mode-browser": "3.234.0", + "@aws-sdk/util-defaults-mode-node": "3.234.0", + "@aws-sdk/util-endpoints": "3.241.0", + "@aws-sdk/util-retry": "3.229.0", + "@aws-sdk/util-user-agent-browser": "3.226.0", + "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.208.0", + "fast-xml-parser": "4.0.11", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/config-resolver": { + "version": "3.234.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.234.0.tgz", + "integrity": "sha512-uZxy4wzllfvgCQxVc+Iqhde0NGAnfmV2hWR6ejadJaAFTuYNvQiRg9IqJy3pkyDPqXySiJ8Bom5PoJfgn55J/A==", + "optional": true, + "dependencies": { + "@aws-sdk/signature-v4": "3.226.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-config-provider": "3.208.0", + "@aws-sdk/util-middleware": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.241.0.tgz", + "integrity": "sha512-e2hlXWG9DH93uVe2wHIUrUOrgZTLzCV3gBd10D3/usSzS4FvVVU7OmidnRPYCLLnt3EvnL5b4REOedO1q8hv8g==", + "optional": true, + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.241.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.226.0.tgz", + "integrity": "sha512-sd8uK1ojbXxaZXlthzw/VXZwCPUtU3PjObOfr3Evj7MPIM2IH8h29foOlggx939MdLQGboJf9gKvLlvKDWtJRA==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-imds": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.226.0.tgz", + "integrity": "sha512-//z/COQm2AjYFI1Lb0wKHTQSrvLFTyuKLFQGPJsKS7DPoxGOCKB7hmYerlbl01IDoCxTdyL//TyyPxbZEOQD5Q==", + "optional": true, + "dependencies": { + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.241.0.tgz", + "integrity": "sha512-CI+mu6h74Kzmscw35TvNkc/wYHsHPGAwP7humSHoWw53H9mVw21Ggft/dT1iFQQZWQ8BNXkzuXlNo1IlqwMgOA==", + "optional": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.226.0", + "@aws-sdk/credential-provider-imds": "3.226.0", + "@aws-sdk/credential-provider-process": "3.226.0", + "@aws-sdk/credential-provider-sso": "3.241.0", + "@aws-sdk/credential-provider-web-identity": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.241.0.tgz", + "integrity": "sha512-08zPQcD5o9brQmzEipWHeHgU85aQcEF8MWLfpeyjO6e1/l7ysQ35NsS+PYtv77nLpGCx/X+ZuW/KXWoRrbw77w==", + "optional": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.226.0", + "@aws-sdk/credential-provider-imds": "3.226.0", + "@aws-sdk/credential-provider-ini": "3.241.0", + "@aws-sdk/credential-provider-process": "3.226.0", + "@aws-sdk/credential-provider-sso": "3.241.0", + "@aws-sdk/credential-provider-web-identity": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.226.0.tgz", + "integrity": "sha512-iUDMdnrTvbvaCFhWwqyXrhvQ9+ojPqPqXhwZtY1X/Qaz+73S9gXBPJHZaZb2Ke0yKE1Ql3bJbKvmmxC/qLQMng==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.241.0.tgz", + "integrity": "sha512-6Bjd6eEIrVomRTrPrM4dlxusQm+KMJ9hLYKECCpFkwDKIK+pTgZNLRtQdalHyzwneHJPdimrm8cOv1kUQ8hPoA==", + "optional": true, + "dependencies": { + "@aws-sdk/client-sso": "3.241.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/token-providers": "3.241.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.226.0.tgz", + "integrity": "sha512-CCpv847rLB0SFOHz2igvUMFAzeT2fD3YnY4C8jltuJoEkn0ITn1Hlgt13nTJ5BUuvyti2mvyXZHmNzhMIMrIlw==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.241.0.tgz", + "integrity": "sha512-J3Q45t1o35OhUI6gWks7rmosPT+mFWXiaHl2LST509Ovjwx6SFs2PvbGP6n7xqUzxyq5Rk6FzZBwB8ItuAa6Qw==", + "optional": true, + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.241.0", + "@aws-sdk/client-sso": "3.241.0", + "@aws-sdk/client-sts": "3.241.0", + "@aws-sdk/credential-provider-cognito-identity": "3.241.0", + "@aws-sdk/credential-provider-env": "3.226.0", + "@aws-sdk/credential-provider-imds": "3.226.0", + "@aws-sdk/credential-provider-ini": "3.241.0", + "@aws-sdk/credential-provider-node": "3.241.0", + "@aws-sdk/credential-provider-process": "3.226.0", + "@aws-sdk/credential-provider-sso": "3.241.0", + "@aws-sdk/credential-provider-web-identity": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/fetch-http-handler": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.226.0.tgz", + "integrity": "sha512-JewZPMNEBXfi1xVnRa7pVtK/zgZD8/lQ/YnD8pq79WuMa2cwyhDtr8oqCoqsPW+WJT5ScXoMtuHxN78l8eKWgg==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/querystring-builder": "3.226.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-base64": "3.208.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/hash-node": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.226.0.tgz", + "integrity": "sha512-MdlJhJ9/Espwd0+gUXdZRsHuostB2WxEVAszWxobP0FTT9PnicqnfK7ExmW+DUAc0ywxtEbR3e0UND65rlSTVw==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-buffer-from": "3.208.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/invalid-dependency": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.226.0.tgz", + "integrity": "sha512-QXOYFmap8g9QzRjumcRCIo2GEZkdCwd7ePQW0OABWPhKHzlJ74vvBxywjU3s39EEBEluWXtZ7Iufg6GxZM4ifw==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/is-array-buffer": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", + "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-content-length": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.226.0.tgz", + "integrity": "sha512-ksUzlHJN2JMuyavjA46a4sctvnrnITqt2tbGGWWrAuXY1mel2j+VbgnmJUiwHKUO6bTFBBeft5Vd1TSOb4JmiA==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.226.0.tgz", + "integrity": "sha512-EvLFafjtUxTT0AC9p3aBQu1/fjhWdIeK58jIXaNFONfZ3F8QbEYUPuF/SqZvJM6cWfOO9qwYKkRDbCSTYhprIg==", + "optional": true, + "dependencies": { + "@aws-sdk/middleware-serde": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/signature-v4": "3.226.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/util-config-provider": "3.208.0", + "@aws-sdk/util-middleware": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.226.0.tgz", + "integrity": "sha512-haVkWVh6BUPwKgWwkL6sDvTkcZWvJjv8AgC8jiQuSl8GLZdzHTB8Qhi3IsfFta9HAuoLjxheWBE5Z/L0UrfhLA==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.226.0.tgz", + "integrity": "sha512-m9gtLrrYnpN6yckcQ09rV7ExWOLMuq8mMPF/K3DbL/YL0TuILu9i2T1W+JuxSX+K9FMG2HrLAKivE/kMLr55xA==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.226.0.tgz", + "integrity": "sha512-mwRbdKEUeuNH5TEkyZ5FWxp6bL2UC1WbY+LDv6YjHxmSMKpAoOueEdtU34PqDOLrpXXxIGHDFmjeGeMfktyEcA==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-retry": { + "version": "3.235.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.235.0.tgz", + "integrity": "sha512-50WHbJGpD3SNp9763MAlHqIhXil++JdQbKejNpHg7HsJne/ao3ub+fDOfx//mMBjpzBV25BGd5UlfL6blrClSg==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/service-error-classification": "3.229.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/util-retry": "3.229.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sts": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.226.0.tgz", + "integrity": "sha512-NN9T/qoSD1kZvAT+VLny3NnlqgylYQcsgV3rvi/8lYzw/G/2s8VS6sm/VTWGGZhx08wZRv20MWzYu3bftcyqUg==", + "optional": true, + "dependencies": { + "@aws-sdk/middleware-signing": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/signature-v4": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-serde": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.226.0.tgz", + "integrity": "sha512-nPuOOAkSfx9TxzdKFx0X2bDlinOxGrqD7iof926K/AEflxGD1DBdcaDdjlYlPDW2CVE8LV/rAgbYuLxh/E/1VA==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-signing": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.226.0.tgz", + "integrity": "sha512-E6HmtPcl+IjYDDzi1xI2HpCbBq2avNWcjvCriMZWuTAtRVpnA6XDDGW5GY85IfS3A8G8vuWqEVPr8JcYUcjfew==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/signature-v4": "3.226.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-middleware": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-stack": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.226.0.tgz", + "integrity": "sha512-85wF29LvPvpoed60fZGDYLwv1Zpd/cM0C22WSSFPw1SSJeqO4gtFYyCg2squfT3KI6kF43IIkOCJ+L7GtryPug==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.226.0.tgz", + "integrity": "sha512-N1WnfzCW1Y5yWhVAphf8OPGTe8Df3vmV7/LdsoQfmpkCZgLZeK2o0xITkUQhRj1mbw7yp8tVFLFV3R2lMurdAQ==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/node-config-provider": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.226.0.tgz", + "integrity": "sha512-B8lQDqiRk7X5izFEUMXmi8CZLOKCTWQJU9HQf3ako+sF0gexo4nHN3jhoRWyLtcgC5S3on/2jxpAcqtm7kuY3w==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/node-http-handler": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.226.0.tgz", + "integrity": "sha512-xQCddnZNMiPmjr3W7HYM+f5ir4VfxgJh37eqZwX6EZmyItFpNNeVzKUgA920ka1VPz/ZUYB+2OFGiX3LCLkkaA==", + "optional": true, + "dependencies": { + "@aws-sdk/abort-controller": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/querystring-builder": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/property-provider": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.226.0.tgz", + "integrity": "sha512-TsljjG+Sg0LmdgfiAlWohluWKnxB/k8xenjeozZfzOr5bHmNHtdbWv6BtNvD/R83hw7SFXxbJHlD5H4u9p2NFg==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/protocol-http": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.226.0.tgz", + "integrity": "sha512-zWkVqiTA9RXL6y0hhfZc9bcU4DX2NI6Hw9IhQmSPeM59mdbPjJlY4bLlMr5YxywqO3yQ/ylNoAfrEzrDjlOSRg==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/querystring-builder": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.226.0.tgz", + "integrity": "sha512-LVurypuNeotO4lmirKXRC4NYrZRAyMJXuwO0f2a5ZAUJCjauwYrifKue6yCfU7bls7gut7nfcR6B99WBYpHs3g==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-uri-escape": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/querystring-parser": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.226.0.tgz", + "integrity": "sha512-FzB+VrQ47KAFxiPt2YXrKZ8AOLZQqGTLCKHzx4bjxGmwgsjV8yIbtJiJhZLMcUQV4LtGeIY9ixIqQhGvnZHE4A==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/service-error-classification": { + "version": "3.229.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.229.0.tgz", + "integrity": "sha512-dnzWWQ0/NoWMUZ5C0DW3dPm0wC1O76Y/SpKbuJzWPkx1EYy6r8p32Ly4D9vUzrKDbRGf48YHIF2kOkBmu21CLg==", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/shared-ini-file-loader": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.226.0.tgz", + "integrity": "sha512-661VQefsARxVyyV2FX9V61V+nNgImk7aN2hYlFKla6BCwZfMng+dEtD0xVGyg1PfRw0qvEv5LQyxMVgHcUSevA==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.226.0.tgz", + "integrity": "sha512-/R5q5agdPd7HJB68XMzpxrNPk158EHUvkFkuRu5Qf3kkkHebEzWEBlWoVpUe6ss4rP9Tqcue6xPuaftEmhjpYw==", + "optional": true, + "dependencies": { + "@aws-sdk/is-array-buffer": "3.201.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-hex-encoding": "3.201.0", + "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/util-uri-escape": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/smithy-client": { + "version": "3.234.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.234.0.tgz", + "integrity": "sha512-8AtR/k4vsFvjXeQbIzq/Wy7Nbk48Ou0wUEeVYPHWHPSU8QamFWORkOwmKtKMfHAyZvmqiAPeQqHFkq+UJhWyyQ==", + "optional": true, + "dependencies": { + "@aws-sdk/middleware-stack": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.241.0.tgz", + "integrity": "sha512-79okvuOS7V559OIL/RalIPG98wzmWxeFOChFnbEjn2pKOyGQ6FJRwLPYZaVRtNdAtnkBNgRpmFq9dX843QxhtQ==", + "optional": true, + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.241.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.226.0.tgz", + "integrity": "sha512-MmmNHrWeO4man7wpOwrAhXlevqtOV9ZLcH4RhnG5LmRce0RFOApx24HoKENfFCcOyCm5LQBlsXCqi0dZWDWU0A==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/url-parser": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.226.0.tgz", + "integrity": "sha512-p5RLE0QWyP0OcTOLmFcLdVgUcUEzmEfmdrnOxyNzomcYb0p3vUagA5zfa1HVK2azsQJFBv28GfvMnba9bGhObg==", + "optional": true, + "dependencies": { + "@aws-sdk/querystring-parser": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-base64": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz", + "integrity": "sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg==", + "optional": true, + "dependencies": { + "@aws-sdk/util-buffer-from": "3.208.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-body-length-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz", + "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-body-length-node": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz", + "integrity": "sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-buffer-from": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz", + "integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==", + "optional": true, + "dependencies": { + "@aws-sdk/is-array-buffer": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-config-provider": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz", + "integrity": "sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-defaults-mode-browser": { + "version": "3.234.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.234.0.tgz", + "integrity": "sha512-IHMKXjTbOD8XMz5+2oCOsVP94BYb9YyjXdns0aAXr2NAo7k2+RCzXQ2DebJXppGda1F6opFutoKwyVSN0cmbMw==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@aws-sdk/util-defaults-mode-node": { + "version": "3.234.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.234.0.tgz", + "integrity": "sha512-UGjQ+OjBYYhxFVtUY+jtr0ZZgzZh6OHtYwRhFt8IHewJXFCfZTyfsbX20szBj5y1S4HRIUJ7cwBLIytTqMbI5w==", + "optional": true, + "dependencies": { + "@aws-sdk/config-resolver": "3.234.0", + "@aws-sdk/credential-provider-imds": "3.226.0", + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.241.0.tgz", + "integrity": "sha512-jVf8bKrN22Ey0xLmj75sL7EUvm5HFpuOMkXsZkuXycKhCwLBcEUWlvtJYtRjOU1zScPQv9GMJd2QXQglp34iOQ==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-hex-encoding": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz", + "integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.208.0.tgz", + "integrity": "sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-middleware": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.226.0.tgz", + "integrity": "sha512-B96CQnwX4gRvQdaQkdUtqvDPkrptV5+va6FVeJOocU/DbSYMAScLxtR3peMS8cnlOT6nL1Eoa42OI9AfZz1VwQ==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-retry": { + "version": "3.229.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.229.0.tgz", + "integrity": "sha512-0zKTqi0P1inD0LzIMuXRIYYQ/8c1lWMg/cfiqUcIAF1TpatlpZuN7umU0ierpBFud7S+zDgg0oemh+Nj8xliJw==", + "optional": true, + "dependencies": { + "@aws-sdk/service-error-classification": "3.229.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/util-uri-escape": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", + "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.226.0.tgz", + "integrity": "sha512-PhBIu2h6sPJPcv2I7ELfFizdl5pNiL4LfxrasMCYXQkJvVnoXztHA1x+CQbXIdtZOIlpjC+6BjDcE0uhnpvfcA==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.226.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.226.0.tgz", + "integrity": "sha512-othPc5Dz/pkYkxH+nZPhc1Al0HndQT8zHD4e9h+EZ+8lkd8n+IsnLfTS/mSJWrfiC6UlNRVw55cItstmJyMe/A==", + "optional": true, + "dependencies": { + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz", + "integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-utf8-node": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.208.0.tgz", + "integrity": "sha512-jKY87Acv0yWBdFxx6bveagy5FYjz+dtV8IPT7ay1E2WPWH1czoIdMAkc8tSInK31T6CRnHWkLZ1qYwCbgRfERQ==", + "optional": true, + "dependencies": { + "@aws-sdk/util-buffer-from": "3.208.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -58,6 +1159,46 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -67,6 +1208,11 @@ "node": ">=8" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -90,6 +1236,12 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "optional": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -106,10 +1258,60 @@ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bson": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.0.tgz", + "integrity": "sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA==", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">=10.16.0" } }, "node_modules/bytes": { @@ -159,6 +1361,14 @@ "fsevents": "~2.3.2" } }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -192,11 +1402,43 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -230,6 +1472,14 @@ "node": ">=12" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -297,6 +1547,52 @@ "node": ">= 0.10.0" } }, + "node_modules/express-fileupload": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.4.0.tgz", + "integrity": "sha512-RjzLCHxkv3umDeZKeFeMg8w7qe0V09w3B7oGZprr/oO2H/ISCgNzuqzn7gV3HRWb37GjRk429CCpSLS2KNTqMQ==", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express-mongo-sanitize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/express-mongo-sanitize/-/express-mongo-sanitize-2.2.0.tgz", + "integrity": "sha512-PZBs5nwhD6ek9ZuP+W2xmpvcrHwXZxD5GdieX2dsjPbAbH4azOkrHbycBud2QRU+YQF1CT+pki/lZGedHgo/dQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/express-rate-limit": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", + "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", + "engines": { + "node": ">= 12.9.0" + }, + "peerDependencies": { + "express": "^4 || ^5" + } + }, + "node_modules/fast-xml-parser": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", + "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==", + "optional": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -417,6 +1713,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/helmet": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-6.0.1.tgz", + "integrity": "sha512-8wo+VdQhTMVBMCITYZaGTbE4lvlthelPYSvoyNvk4RECTmrVjMerp9RfUOQXZWLvCcAn1pKj7ZRxK4lI9Alrcw==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/hpp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", + "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", + "dependencies": { + "lodash": "^4.17.12", + "type-is": "^1.6.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -443,6 +1759,25 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -454,6 +1789,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -504,6 +1844,83 @@ "node": ">=0.12.0" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kareem": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.0.tgz", + "integrity": "sha512-rVBUGGwvqg130iwYu8k7lutHuDBFj1yGRdnlE44wEhxAmFBad1zcL66PdWC1raw3tIObY6XWhtv3VL04xQb/cg==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -512,6 +1929,12 @@ "node": ">= 0.6" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -567,6 +1990,124 @@ "node": "*" } }, + "node_modules/mongodb": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.12.1.tgz", + "integrity": "sha512-koT87tecZmxPKtxRQD8hCKfn+ockEL2xBiUvx3isQGI6mFmagWt4f4AyCE9J4sKepnLhMacoCTQQA6SLAI2L6w==", + "dependencies": { + "bson": "^4.7.0", + "mongodb-connection-string-url": "^2.5.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=12.9.0" + }, + "optionalDependencies": { + "@aws-sdk/credential-providers": "^3.186.0", + "saslprep": "^1.0.3" + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongoose": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.8.2.tgz", + "integrity": "sha512-cIato5N2w/QuJkkh0w4nyf7ty7DqmmP/W8/6PFSM0DrzbxIMlr6VN15LBIceTSJIxbznNl2Mlbh9Rm4sokMw+A==", + "dependencies": { + "bson": "^4.7.0", + "kareem": "2.5.0", + "mongodb": "4.12.1", + "mpath": "0.9.0", + "mquery": "4.0.3", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", + "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -580,6 +2121,64 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-geocoder": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/node-geocoder/-/node-geocoder-4.2.0.tgz", + "integrity": "sha512-ZvUiOHWHLVGlrYPvazXj+VQ9oK+EOMinh/5vWoBwOQiV0eCU7GPtEWrMRNnKvmBVOzZHb1eFe9uoKMFeyygMVA==", + "dependencies": { + "bluebird": "^3.5.2", + "node-fetch": "^2.6.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/nodemailer": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.0.tgz", + "integrity": "sha512-jFaCEGTeT3E/m/5R2MHWiyQH3pSARECRUDM+1hokOYc3lQAAG7ASuy+2jIsYVf+RVa9zePopSQwKNVFH8DKUpA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", @@ -647,6 +2246,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -666,6 +2273,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -709,6 +2324,14 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -781,6 +2404,18 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -850,6 +2485,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, "node_modules/simple-update-notifier": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", @@ -871,6 +2511,45 @@ "semver": "bin/semver.js" } }, + "node_modules/slugify": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.5.tgz", + "integrity": "sha512-8mo9bslnBO3tr5PEVFzMPIWwWnipGS0xVbYf65zxDqfNwmzYn1LpiKNrR6DlClusuvo+hDHd1zKpmfAe83NQSQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -879,6 +2558,20 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "optional": true + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -923,6 +2616,23 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "optional": true + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -957,6 +2667,15 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -964,6 +2683,44 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/xss-clean": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/xss-clean/-/xss-clean-0.1.1.tgz", + "integrity": "sha512-On99yydxoAEkHpraR7Wjg9cD6UmKfbtH2HXO1it2djid32osHL7Qr8bIu+dGYoOeKzf3ZszLfz1gwR/D7whZ2A==", + "dependencies": { + "xss-filters": "1.2.6" + } + }, + "node_modules/xss-filters": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/xss-filters/-/xss-filters-1.2.6.tgz", + "integrity": "sha512-uqgwZRpVJCDfHsRX9lDrkPyCitQYzPklmLSbajJncATZKAUd1tF1x9y2VyPNFMv8SsSWed80xorSS5qGpw3WiA==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index 46b6ec4..09ab4f7 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,24 @@ "author": "Albert Khomich", "license": "MIT", "dependencies": { + "bcryptjs": "^2.4.3", + "colors": "^1.4.0", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", "dotenv": "^16.0.3", - "express": "^4.18.2" + "express": "^4.18.2", + "express-fileupload": "^1.4.0", + "express-mongo-sanitize": "^2.2.0", + "express-rate-limit": "^6.7.0", + "helmet": "^6.0.1", + "hpp": "^0.2.3", + "jsonwebtoken": "^9.0.0", + "mongoose": "^6.8.2", + "morgan": "^1.10.0", + "node-geocoder": "^4.2.0", + "nodemailer": "^6.9.0", + "slugify": "^1.6.5", + "xss-clean": "^0.1.1" }, "devDependencies": { "nodemon": "^2.0.20" diff --git a/public/fonts/glyphicons-halflings-regular.eot b/public/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..b93a4953fff68df523aa7656497ee339d6026d64 GIT binary patch literal 20127 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 G`u_lkUM?>H literal 0 HcmV?d00001 diff --git a/public/fonts/glyphicons-halflings-regular.woff2 b/public/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 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 literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..4fe03de --- /dev/null +++ b/public/index.html @@ -0,0 +1,195 @@ +DevCamper API + |  +Backend API for the DevCamper application to manage bootcamps, courses, reviews, users and authentications

DevCamper API

Backend API for the DevCamper application to manage bootcamps, courses, reviews, users and authentications

Bootcamps +7

Bootcamps CRUD functionality

Description

Fetch all bootcamps from database. Includes pagination, filtering, etc

Description

Add new bootcamp to database. Must be authenticated and must be publisher or admin

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"name": "ModernTech Bootcamp", +"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", +"website": "https://moderntech.com", +"phone": "(222) 222-2222", +"email": "enroll@moderntech.com", +"address": "220 Pawtucket St, Lowell, MA 01854", +"careers": ["Web Development", "UI/UX", "Mobile Development"], +"housing": false, +"jobAssistance": true, +"jobGuarantee": false, +"acceptGi": true +}
Description

Update single bootcamp in database

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"careers": [ +"Web Development", +"UI/UX" +] +}
Description

Get bootcamps within a radius of a specific zipcode

Description

Rode to upload a bootcamp photo

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"careers": [ +"Web Development", +"UI/UX" +] +}

Courses +6

Create, read, update and delete courses

Description

Get all courses in Database

Description

Create a course for a specific bootcamp

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"title": "Full Stack Web Development", +"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", +"weeks": 12, +"tuition": 10000, +"minimumSkill": "intermediate", +"scholarhipsAvailable": true +}
Description

Update Course in DB

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"tuition": 13000, +"minimumSkill": "advanced" +}

Authentication +8

Routes for user authentication including register, login, reset password etc

Description

Add user to the database with encrypted password

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"name": "Albert Khomich", +"email": "albert@gmail.com", +"password": "123456", +"role": "publisher" +}
Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"email": "john@gmail.com", +"password": "123456" +}
Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Description

Generate password token and send email

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"email": "john@gmail.com" +}
Description

Reset user password using token

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"password": "012345" +}
Description

Update logged in user name and email

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"email": "john@gmail.com", +"name": "John Doe" +}
Description

Update logged in user password, send in the body currentPassword and newPassword

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"currentPassword": "123456", +"newPassword": "1234567" +}

Users +5

CRUD functionality for users only available to admins

Description

Add user to database (admin)

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"name": "Albert Khomich", +"email": "ak@gmail.com", +"password": "123456" +}
Description

Update user in database (admin)

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"name": "Albert Khomitch" +}

Reviews +6

Manage course reviews

Description

Get all reviews from database and populate with bootcamp name and description

Description

Fetch a review from database by id and populate Bootcamp name and description

Description

Insert review for a specific bootcamp

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"title": "Nice Bootcamp", +"text": "I learned a lot", +"rating": 8 +}
Description

Update review in database

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"title": "Had Fun" +}
Description

Remove review from database

Headers
KeyValueDescription
Content-Typeapplication/json

JSON Type

Body
{ +"title": "Had Fun" +}


Generated at 2023-01-21 10:54:10 by docgen
\ No newline at end of file diff --git a/public/uploads/photo_5d725a1b7b292f5f8ceff788.jpg b/public/uploads/photo_5d725a1b7b292f5f8ceff788.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ec510d9a9b32421e615f235fbc269d7e88af3103 GIT binary patch literal 59899 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 b00000000000HFWc00;pC0RcY%2Ot005yS;7 literal 0 HcmV?d00001 diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..10b0ddb --- /dev/null +++ b/readme.md @@ -0,0 +1,26 @@ +# DevCamper API + +> Backend API for DevCamper application, which is a bootcamp directory website + +## Usage + +Rename "config/config.env.env" to "config/config.env" and update the values/settings to your own + +## Install Dependencies + +``` +npm install +``` + +## Run App + +``` +# Run in dev mode +npm run dev + +# Run in prod mode +npm start +``` + +- Version 1.0.0 +- Licence MIT diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..edf847b --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,26 @@ +const express = require('express'); +const { + register, + login, + logout, + getMe, + forgotPassword, + resetPassword, + updateDetails, + updatePassword, +} = require('../controllers/auth'); + +const router = express.Router(); + +const { protect } = require('../middleware/auth'); + +router.post('/register', register); +router.post('/login', login); +router.get('/logout', logout); +router.get('/me', protect, getMe); +router.put('/updatedetails', protect, updateDetails); +router.put('/updatepassword', protect, updatePassword); +router.post('/forgotpassword', forgotPassword); +router.put('/resetpassword/:resettoken', resetPassword); + +module.exports = router; diff --git a/routes/bootcamps.js b/routes/bootcamps.js new file mode 100644 index 0000000..9b6ffff --- /dev/null +++ b/routes/bootcamps.js @@ -0,0 +1,44 @@ +const express = require('express'); +const { + getBootcamps, + getBootcamp, + createBootcamp, + updateBootcamp, + deleteBootcamp, + getBootcampsInRadius, + bootcampPhotoUpload, +} = require('../controllers/bootcamps'); + +const Bootcamp = require('../models/Bootcamp'); + +//Include other resource routers +const courseRouter = require('./courses'); +const reviewRouter = require('./reviews'); + +const router = express.Router(); + +const advancedResults = require('../middleware/advancedResults'); +const { protect, authorize } = require('../middleware/auth'); + +//Re-route into other resource routers +router.use('/:bootcampId/courses', courseRouter); +router.use('/:bootcampId/reviews', reviewRouter); + +router.route('/radius/:zipcode/:distance').get(getBootcampsInRadius); + +router + .route('/:id/photo') + .put(protect, authorize('publisher', 'admin'), bootcampPhotoUpload); + +router + .route('/') + .get(advancedResults(Bootcamp, 'courses'), getBootcamps) + .post(protect, authorize('publisher', 'admin'), createBootcamp); + +router + .route('/:id') + .get(getBootcamp) + .put(protect, authorize('publisher', 'admin'), updateBootcamp) + .delete(protect, authorize('publisher', 'admin'), deleteBootcamp); + +module.exports = router; diff --git a/routes/courses.js b/routes/courses.js new file mode 100644 index 0000000..b650b6d --- /dev/null +++ b/routes/courses.js @@ -0,0 +1,34 @@ +const express = require('express'); +const { + getCourses, + getCourse, + addCourse, + updateCourse, + deleteCourse, +} = require('../controllers/courses'); + +const Course = require('../models/Course'); + +const router = express.Router({ mergeParams: true }); + +const advancedResults = require('../middleware/advancedResults'); +const { protect, authorize } = require('../middleware/auth'); + +router + .route('/') + .get( + advancedResults(Course, { + path: 'bootcamp', + select: 'name description', + }), + getCourses + ) + .post(protect, authorize('publisher', 'admin'), addCourse); + +router + .route('/:id') + .get(getCourse) + .put(protect, authorize('publisher', 'admin'), updateCourse) + .delete(protect, authorize('publisher', 'admin'), deleteCourse); + +module.exports = router; diff --git a/routes/reviews.js b/routes/reviews.js new file mode 100644 index 0000000..1a2a67b --- /dev/null +++ b/routes/reviews.js @@ -0,0 +1,34 @@ +const express = require('express'); +const { + getReviews, + getReview, + addReview, + updateReview, + deleteReview, +} = require('../controllers/reviews'); + +const Review = require('../models/Review'); + +const router = express.Router({ mergeParams: true }); + +const advancedResults = require('../middleware/advancedResults'); +const { protect, authorize } = require('../middleware/auth'); + +router + .route('/') + .get( + advancedResults(Review, { + path: 'bootcamp', + select: 'name description', + }), + getReviews + ) + .post(protect, authorize('user', 'admin'), addReview); + +router + .route('/:id') + .get(getReview) + .put(protect, authorize('user', 'admin'), updateReview) + .delete(protect, authorize('user', 'admin'), deleteReview); + +module.exports = router; diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 0000000..886f660 --- /dev/null +++ b/routes/users.js @@ -0,0 +1,24 @@ +const express = require('express'); +const { + getUsers, + getUser, + createUser, + updateUser, + deleteUser, +} = require('../controllers/users'); + +const User = require('../models/User'); + +const router = express.Router({ mergeParams: true }); + +const advancedResults = require('../middleware/advancedResults'); +const { protect, authorize } = require('../middleware/auth'); + +router.use(protect); +router.use(authorize('admin')); + +router.route('/').get(advancedResults(User), getUsers).post(createUser); + +router.route('/:id').get(getUser).put(updateUser).delete(deleteUser); + +module.exports = router; diff --git a/seeder.js b/seeder.js new file mode 100644 index 0000000..d2e08d9 --- /dev/null +++ b/seeder.js @@ -0,0 +1,69 @@ +const fs = require('fs'); +const mongoose = require('mongoose'); +const colors = require('colors'); +const dotenv = require('dotenv'); + +// Load env vars +dotenv.config({ path: './config/config.env' }); + +//Load models +const Bootcamp = require('./models/Bootcamp'); +const Course = require('./models/Course'); +const User = require('./models/User'); +const Review = require('./models/Review'); + +//Connect to DB +mongoose.connect(process.env.MONGO_URI, { + useNewUrlParser: true, +}); + +// Read JSON files +const bootcamps = JSON.parse( + fs.readFileSync(`${__dirname}/_data/bootcamps.json`, 'utf8') +); + +const courses = JSON.parse( + fs.readFileSync(`${__dirname}/_data/courses.json`, 'utf8') +); + +const users = JSON.parse( + fs.readFileSync(`${__dirname}/_data/users.json`, 'utf8') +); + +const reviews = JSON.parse( + fs.readFileSync(`${__dirname}/_data/reviews.json`, 'utf8') +); + +// Import into DB +const importData = async () => { + try { + await Bootcamp.create(bootcamps); + await Course.create(courses); + await Review.create(reviews); + await User.create(users); + console.log('Data imported...'.green.inverse); + process.exit(); + } catch (err) { + console.error(err); + } +}; + +//Delete data +const deleteData = async () => { + try { + await Bootcamp.deleteMany(); + await Course.deleteMany(); + await User.deleteMany(); + await Review.deleteMany(); + console.log('Data destroyed...'.red.inverse); + process.exit(); + } catch (err) { + console.error(err); + } +}; + +if (process.argv[2] === '-i') { + importData(); +} else if (process.argv[2] === '-d') { + deleteData(); +} diff --git a/server.js b/server.js index 0aa4a2a..22b5f4b 100644 --- a/server.js +++ b/server.js @@ -1,14 +1,94 @@ +const path = require('path'); const express = require('express'); const dotenv = require('dotenv'); +const morgan = require('morgan'); +const colors = require('colors'); +const fileupload = require('express-fileupload'); +const cookieParser = require('cookie-parser'); +const mongoSanitize = require('express-mongo-sanitize'); +const helmet = require('helmet'); +const xss = require('xss-clean'); +const rateLimit = require('express-rate-limit'); +const hpp = require('hpp'); +const cors = require('cors'); +const errorHandler = require('./middleware/error'); +const connectDB = require('./config/db'); //Load env vars dotenv.config({ path: './config/config.env' }); +//Connect to database +connectDB(); + +//Route files +const bootcamps = require('./routes/bootcamps'); +const courses = require('./routes/courses'); +const auth = require('./routes/auth'); +const users = require('./routes/users'); +const reviews = require('./routes/reviews'); + const app = express(); +//Body parser +app.use(express.json()); + +//Cookie parser +app.use(cookieParser()); + +// Dev logging middleware +if (process.env.NODE_ENV === 'development') { + app.use(morgan('dev')); +} + +//File uploading +app.use(fileupload()); + +// Sanitize data +app.use(mongoSanitize()); + +// Set security headers +app.use(helmet()); + +// Prevent XSS attacks +app.use(xss()); + +// Rate limiting +const limiter = rateLimit({ + windowMs: 10 * 60 * 1000, + max: 100, +}); +app.use(limiter); + +// Prevent http param pollution +app.use(hpp()); + +// Enable CORS +app.use(cors()); + +//Set static folder +app.use(express.static(path.join(__dirname, 'public'))); + +//Mount routers +app.use('/api/v1/bootcamps', bootcamps); +app.use('/api/v1/courses', courses); +app.use('/api/v1/auth', auth); +app.use('/api/v1/users', users); +app.use('/api/v1/reviews', reviews); + +app.use(errorHandler); + const PORT = process.env.PORT || 5000; -app.listen( - PORT, - console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`) -); \ No newline at end of file +const server = app.listen( + PORT, + console.log( + `Server running in ${process.env.NODE_ENV} mode on port ${PORT}`.yellow.bold + ) +); + +//Handle unhandled promise rejections +process.on('unhandledRejection', (err, promise) => { + console.log(`Error: ${err.message}`.red); + //Close server & exit process + server.close(() => process.exit(1)); +}); diff --git a/utils/errorResponse.js b/utils/errorResponse.js new file mode 100644 index 0000000..29d4ac9 --- /dev/null +++ b/utils/errorResponse.js @@ -0,0 +1,8 @@ +class ErrorResponse extends Error { + constructor(message, statusCode) { + super(message); + this.statusCode = statusCode; + } +} + +module.exports = ErrorResponse; diff --git a/utils/geocoder.js b/utils/geocoder.js new file mode 100644 index 0000000..223b636 --- /dev/null +++ b/utils/geocoder.js @@ -0,0 +1,12 @@ +const NodeGeocoder = require('node-geocoder'); + +const options = { + provider: process.env.GEOCODER_PROVIDER, + httpAdapter: 'https', + apiKey: process.env.GEOCODER_API_KEY, + formatter: null, +}; + +const geocoder = NodeGeocoder(options); + +module.exports = geocoder; diff --git a/utils/sendEmail.js b/utils/sendEmail.js new file mode 100644 index 0000000..4ae0e2d --- /dev/null +++ b/utils/sendEmail.js @@ -0,0 +1,25 @@ +const nodemailer = require('nodemailer'); + +const sendEmail = async (options) => { + const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT, + auth: { + user: process.env.SMTP_EMAIL, + pass: process.env.SMTP_PASSWORD, + }, + }); + + const message = { + from: `${process.env.FROM_NAME} <${process.env.FROM_EMAIL}>`, + to: options.email, + subject: options.subject, + text: options.message, + }; + + const info = await transporter.sendMail(message); + + console.log('Message sent: %s', info.messageId); +}; + +module.exports = sendEmail;