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 0000000..b93a495 Binary files /dev/null and b/public/fonts/glyphicons-halflings-regular.eot differ diff --git a/public/fonts/glyphicons-halflings-regular.woff b/public/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..9e61285 Binary files /dev/null and b/public/fonts/glyphicons-halflings-regular.woff differ diff --git a/public/fonts/glyphicons-halflings-regular.woff2 b/public/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000..64539b5 Binary files /dev/null and b/public/fonts/glyphicons-halflings-regular.woff2 differ 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 @@ +
Backend API for the DevCamper application to manage bootcamps, courses, reviews, users and authentications
Bootcamps CRUD functionality
Fetch all bootcamps from database. Includes pagination, filtering, etc
Get single bootcamp by id
Add new bootcamp to database. Must be authenticated and must be publisher or admin
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Update single bootcamp in database
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Delete bootcamp from database
Get bootcamps within a radius of a specific zipcode
Rode to upload a bootcamp photo
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Create, read, update and delete courses
Get all courses in Database
Get the specific courses for a bootcamp
Get a single course by its ID
Create a course for a specific bootcamp
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Update Course in DB
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Delete course from DB
Routes for user authentication including register, login, reset password etc
Add user to the database with encrypted password
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Generate password token and send email
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Reset user password using token
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Update logged in user name and email
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Update logged in user password, send in the body currentPassword and newPassword
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Clear token cookie
CRUD functionality for users only available to admins
Get all users (admin)
Get single user by id (admin)
Add user to database (admin)
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Update user in database (admin)
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Delete user from database (admin)
Manage course reviews
Get all reviews from database and populate with bootcamp name and description
Fetch the reviews for a specific bootcamp
Fetch a review from database by id and populate Bootcamp name and description
Insert review for a specific bootcamp
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Update review in database
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |
Remove review from database
Key | Value | Description |
---|---|---|
Content-Type | application/json | JSON Type |