60 | ```
61 |
62 | Provided in the root of the project, a `.sample.env` for guidance.
63 |
64 |
65 | ### Folder Structure
66 |
67 | ```
68 | ├── README.md
69 | ├── backend
70 | │ ├── config
71 | │ │ └── db.js
72 | │ ├── controllers
73 | │ │ ├── noteController.js
74 | │ │ ├── ticketController.js
75 | │ │ └── userController.js
76 | │ ├── middleware
77 | │ │ ├── authMiddleware.js
78 | │ │ └── errorMiddleware.js
79 | │ ├── models
80 | │ │ ├── noteModel.js
81 | │ │ ├── ticketModel.js
82 | │ │ └── userModel.js
83 | │ ├── routes
84 | │ │ ├── noteRoutes.js
85 | │ │ ├── ticketRoutes.js
86 | │ │ └── userRoutes.js
87 | │ └── server.js
88 | ├── frontend
89 | │ ├── README.md
90 | │ ├── package-lock.json
91 | │ ├── package.json
92 | │ ├── public
93 | │ │ ├── favicon.ico
94 | │ │ ├── index.html
95 | │ │ ├── logo192.png
96 | │ │ ├── logo512.png
97 | │ │ ├── manifest.json
98 | │ │ └── robots.txt
99 | │ └── src
100 | │ ├── App.js
101 | │ ├── app
102 | │ │ └── store.js
103 | │ ├── components
104 | │ │ ├── BackButton.jsx
105 | │ │ ├── Header.jsx
106 | │ │ ├── NoteItem.jsx
107 | │ │ ├── PrivateRoute.jsx
108 | │ │ ├── Spinner.jsx
109 | │ │ └── TicketItem.jsx
110 | │ ├── features
111 | │ │ ├── auth
112 | │ │ │ ├── authService.js
113 | │ │ │ └── authSlice.js
114 | │ │ ├── notes
115 | │ │ │ ├── noteService.js
116 | │ │ │ └── noteSlice.js
117 | │ │ └── tickets
118 | │ │ ├── ticketService.js
119 | │ │ └── ticketSlice.js
120 | │ ├── hooks
121 | │ │ └── useAuthStatus.js
122 | │ ├── index.css
123 | │ ├── index.js
124 | │ ├── pages
125 | │ │ ├── Home.jsx
126 | │ │ ├── Login.jsx
127 | │ │ ├── NewTicket.jsx
128 | │ │ ├── Register.jsx
129 | │ │ ├── Ticket.jsx
130 | │ │ └── Tickets.jsx
131 | │ └── serviceWorker.js
132 | ├── node_modules
133 | ├── package-lock.json
134 | └── package.json
135 | ```
136 |
137 | ### Run backend & frontend servers concurrently
138 |
139 | In order to run the backend & frontend servers using [concurrently](https://www.npmjs.com/package/concurrently):
140 |
141 | ```sh
142 | npm run dev
143 | ```
144 |
145 | This will automatically open the local development server at [http://localhost:3000](http://localhost:3000).
146 |
147 | The page will automatically reload if you make changes to the code.
148 | You will see the build errors and lint warnings in the console.
149 |
150 | ### Routes
151 |
152 | Inside of `/backend/controllers/userController.js` are a collection of routes that involve `CRUD` functions of persistent storage.
153 |
154 | - **POST** `/api/users` - Register/create a new user using **registerUser** method in User Controller, requires a **URL-encoded** data in the **Body** containing key value of { name, email, password }, it returns the unique ID along with JWT token
155 | - **POST** `/api/users/login` - Register/create a new user using **loginUser** method in User Controller, requires a **URL-encoded** data in the **Body** containing key value of { email, password }, it returns the registered numeric ID from DB along with unique generated JWT token
156 | - **GET** `/api/users/me` - Get current user
157 |
158 | Inside of `/backend/controllers/ticketController.js` are a collection of.
159 |
160 | getTickets, [done]
161 | getTicket, [done]
162 | createTicket, [done]
163 | updateTicket
164 | deleteTicket,
165 |
166 | - **GET** `/api/tickets` - Get all the tickets of logged in user while passing the right `Authorization` `Bearer Token`.
167 | - **POST** `/api/tickets` - Create a new ticket, requires a **URL-encoded** data in the **Body** containing key value of { product, description }
168 | - **GET** `/api/tickets/:id` - Get the particular ticket detail while passing the right `Authorization` `Bearer Token`.
169 | -
170 | [Postman](https://learning.postman.com/docs/sending-requests/requests/#sending-body-data)
171 |
172 | ### Build an application
173 |
174 | In order to make a production build of your application:
175 |
176 | ```sh
177 | npm run build
178 | ```
179 |
180 | This will produce an optimized build of your application in `build` folder.
181 |
182 | ### Deploy your application
183 |
184 | In order to produce a ready-to-deploy version of your application to deploy to Heroku:
185 |
186 | ```sh
187 | npm run deploy
188 | ```
189 |
190 | This will produce a ready-to-deploy version of your application in `deploy` folder.
191 | Now you can deploy your application by running few handful commands:
192 |
193 | ```sh
194 | cd deploy
195 | heroku login
196 | git init
197 | git add *
198 | git commit -m "deploying my-app"
199 | heroku create my-app
200 | git push heroku master
201 | ```
202 | And within a few seconds, your application will be live at [https://support-desk-mern-arifmd.herokuapp.com/](https://support-desk-mern-arifmd.herokuapp.com/).
203 |
204 |
205 | ## Author(s)
206 |
207 | - **Mohammad Arif** - [mdarif](https://github.com/mdarif)
208 |
--------------------------------------------------------------------------------
/backend/config/db.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | const connectDB = async () => {
4 | try {
5 | const conn = await mongoose.connect(process.env.MONGO_URI)
6 | console.log(
7 | `MongoDB Connected: ${conn.connection.host}`.cyan.underline.bold
8 | )
9 | } catch (error) {
10 | console.log(`Error: ${error.message}`.red.underline.bold)
11 | process.exit(1) // exit process with failure
12 | }
13 | }
14 |
15 | module.exports = connectDB
16 |
--------------------------------------------------------------------------------
/backend/controllers/noteController.js:
--------------------------------------------------------------------------------
1 | const asyncHandler = require('express-async-handler')
2 |
3 | const User = require('../models/userModel')
4 | const Note = require('../models/noteModel')
5 | const Ticket = require('../models/ticketModel')
6 |
7 | // @desc Get notes for a ticket
8 | // @route GET /api/tickets/:ticketId/notes
9 | // @access Private
10 |
11 | /**
12 | * 'asyncHandler' is a simple middleware for handling exceptions
13 | * inside of async express routes and passing them to your express
14 | * error handlers.
15 | */
16 | const getNotes = asyncHandler(async (req, res) => {
17 | // Get user using the id and JWT
18 | const user = await User.findById(req.user.id)
19 |
20 | if (!user) {
21 | res.status(401)
22 | throw new Error('User not found')
23 | }
24 |
25 | const ticket = await Ticket.findById(req.params.ticketId)
26 |
27 | if (ticket.user.toString() !== req.user.id) {
28 | res.status(401)
29 | throw new Error('User not authorized')
30 | }
31 |
32 | const notes = await Note.find({ ticket: req.params.ticketId })
33 |
34 | res.status(200).json(notes)
35 | })
36 |
37 | // @desc Create ticket note
38 | // @route POST /api/tickets/:ticketId/notes
39 | // @access Private
40 |
41 | const addNote = asyncHandler(async (req, res) => {
42 | // Get user using the id and JWT
43 | const user = await User.findById(req.user.id)
44 |
45 | if (!user) {
46 | res.status(401)
47 | throw new Error('User not found')
48 | }
49 |
50 | const ticket = await Ticket.findById(req.params.ticketId)
51 |
52 | if (ticket.user.toString() !== req.user.id) {
53 | res.status(401)
54 | throw new Error('User not authorized')
55 | }
56 |
57 | const note = await Note.create({
58 | ticket: req.params.ticketId,
59 | text: req.body.text,
60 | isStaff: false,
61 | user: req.user.id
62 | })
63 |
64 | res.status(200).json(note)
65 | })
66 |
67 | module.exports = {
68 | getNotes,
69 | addNote
70 | }
71 |
--------------------------------------------------------------------------------
/backend/controllers/ticketController.js:
--------------------------------------------------------------------------------
1 | const asyncHandler = require('express-async-handler')
2 |
3 | const User = require('../models/userModel')
4 | const Ticket = require('../models/ticketModel')
5 |
6 | // @desc Get user tickets
7 | // @route GET /api/tickets
8 | // @access Private
9 |
10 | /**
11 | * 'asyncHandler' is a simple middleware for handling exceptions
12 | * inside of async express routes and passing them to your express
13 | * error handlers.
14 | */
15 | const getTickets = asyncHandler(async (req, res) => {
16 | // Get user using the id and JWT
17 | const user = await User.findById(req.user.id)
18 |
19 | if (!user) {
20 | res.status(401)
21 | throw new Error('User not found')
22 | }
23 |
24 | const tickets = await Ticket.find({ user: req.user._id })
25 |
26 | res.status(200).json(tickets)
27 | })
28 |
29 | // @desc Get user ticket
30 | // @route GET /api/tickets/:id
31 | // @access Private
32 | const getTicket = asyncHandler(async (req, res) => {
33 | // Get user using the id and JWT
34 | const user = await User.findById(req.user.id)
35 |
36 | if (!user) {
37 | res.status(401)
38 | throw new Error('User not found')
39 | }
40 |
41 | const ticket = await Ticket.findById(req.params.id)
42 |
43 | if (!ticket) {
44 | res.status(404)
45 | throw new Error('Ticket not found')
46 | }
47 |
48 | // Check if ticket belongs to user
49 | if (ticket.user.toString() !== req.user.id) {
50 | res.status(401)
51 | throw new Error('Not authorized')
52 | }
53 |
54 | res.status(200).json(ticket)
55 | })
56 |
57 | // @desc Create new ticket
58 | // @route POST /api/ticket
59 | // @access Private
60 | const createTicket = asyncHandler(async (req, res) => {
61 | const { product, description } = req.body
62 |
63 | if (!product || !description) {
64 | res.status(400) // Bad request
65 | throw new Error('Please provide a product and description')
66 | }
67 |
68 | // Get user using the id and JWT
69 | const user = await User.findById(req.user.id)
70 |
71 | if (!user) {
72 | res.status(401)
73 | throw new Error('User not found')
74 | }
75 |
76 | const ticket = await Ticket.create({
77 | product,
78 | description,
79 | user: req.user.id,
80 | status: 'new'
81 | })
82 |
83 | res.status(201).json(ticket)
84 | })
85 |
86 | // @desc Delete ticket
87 | // @route DELETE /api/tickets/:id
88 | // @access Private
89 | const deleteTicket = asyncHandler(async (req, res) => {
90 | // Get user using the id and JWT
91 | const user = await User.findById(req.user.id)
92 |
93 | if (!user) {
94 | res.status(401)
95 | throw new Error('User not found')
96 | }
97 |
98 | const ticket = await Ticket.findById(req.params.id)
99 |
100 | if (!ticket) {
101 | res.status(404)
102 | throw new Error('Ticket not found')
103 | }
104 |
105 | // Check if ticket belongs to user
106 | if (ticket.user.toString() !== req.user.id) {
107 | res.status(401)
108 | throw new Error('Not authorized')
109 | }
110 |
111 | await ticket.remove()
112 |
113 | res.status(200).json({ success: true })
114 | })
115 |
116 | // @desc Update ticket
117 | // @route PUT /api/tickets/:id
118 | // @access Private
119 | const updateTicket = asyncHandler(async (req, res) => {
120 | // Get user using the id and JWT
121 | const user = await User.findById(req.user.id)
122 |
123 | if (!user) {
124 | res.status(401)
125 | throw new Error('User not found')
126 | }
127 |
128 | const ticket = await Ticket.findById(req.params.id)
129 |
130 | if (!ticket) {
131 | res.status(404)
132 | throw new Error('Ticket not found')
133 | }
134 |
135 | // Check if ticket belongs to user
136 | if (ticket.user.toString() !== req.user.id) {
137 | res.status(401)
138 | throw new Error('Not authorized')
139 | }
140 |
141 | const updatedTicket = await Ticket.findByIdAndUpdate(
142 | req.params.id,
143 | req.body,
144 | {
145 | new: true
146 | }
147 | )
148 |
149 | res.status(200).json(updatedTicket)
150 | })
151 |
152 | module.exports = {
153 | getTickets,
154 | createTicket,
155 | getTicket,
156 | deleteTicket,
157 | updateTicket
158 | }
159 |
--------------------------------------------------------------------------------
/backend/controllers/userController.js:
--------------------------------------------------------------------------------
1 | const asyncHandler = require('express-async-handler') // Simple middleware for handling exceptions inside of async express routes and passing them to your express error handlers.
2 | const jwt = require('jsonwebtoken') // JSON Web Token for authentication and authorization
3 | const bcrypt = require('bcryptjs') // A library to help you hash passwords.
4 |
5 | const User = require('../models/userModel')
6 |
7 | // @desc Register a new user
8 | // @route /api/users
9 | // @access Public
10 |
11 | /**
12 | * 'asyncHandler' is a simple middleware for handling exceptions
13 | * inside of async express routes and passing them to your express
14 | * error handlers.
15 | */
16 | const registerUser = asyncHandler(async (req, res) => {
17 | const { name, email, password } = req.body // destructure the request body params
18 |
19 | // Validation
20 | if (!name || !email || !password) {
21 | res.status(400)
22 | throw new Error('Please provide all required fields')
23 | }
24 |
25 | // Check for existing user
26 | const userExists = await User.findOne({ email })
27 |
28 | if (userExists) {
29 | res.status(400)
30 | throw new Error('User already exists')
31 | }
32 |
33 | // Hash password
34 | const salt = await bcrypt.genSalt(10) // 10 is the number of rounds
35 | const hashedPassword = await bcrypt.hash(password, salt) // hash the password
36 |
37 | // Create user
38 | const user = await User.create({
39 | name,
40 | email,
41 | password: hashedPassword
42 | })
43 |
44 | // User is created
45 | if (user) {
46 | res.status(201).json({
47 | _id: user._id,
48 | name: user.name,
49 | email: user.email,
50 | token: generateToken(user._id)
51 | })
52 | } else {
53 | res.status(400)
54 | throw new Error('User could not be created')
55 | }
56 | })
57 |
58 | // @desc Login a user
59 | // @route /api/users/login
60 | // @access Public
61 | const loginUser = asyncHandler(async (req, res) => {
62 | const { email, password } = req.body // destructuring
63 |
64 | const user = await User.findOne({ email })
65 |
66 | // Check User and Password match
67 | if (user && (await bcrypt.compare(password, user.password))) {
68 | res.status(200).json({
69 | _id: user._id,
70 | name: user.name,
71 | email: user.email,
72 | token: generateToken(user._id)
73 | })
74 | } else {
75 | res.status(401) // Unauthorized
76 | throw new Error('Invalid credentials')
77 | }
78 | })
79 |
80 | // @desc Get current user
81 | // @route /api/users/me
82 | // @access Private
83 | const getMe = asyncHandler(async (req, res) => {
84 | const user = {
85 | id: req.user._id,
86 | email: req.user.email,
87 | name: req.user.name
88 | }
89 | res.status(200).json(user)
90 | })
91 |
92 | // Generate token
93 | generateToken = id => {
94 | return jwt.sign({ id }, process.env.JWT_SECRET, {
95 | expiresIn: '30d'
96 | })
97 | }
98 |
99 | module.exports = {
100 | registerUser,
101 | loginUser,
102 | getMe
103 | }
104 |
--------------------------------------------------------------------------------
/backend/middleware/authMiddleware.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken')
2 | const asyncHandler = require('express-async-handler')
3 | const User = require('../models/userModel')
4 |
5 | const protect = asyncHandler(async (req, res, next) => {
6 | let token
7 |
8 | // console.log(req.headers.authorization)
9 | // Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYyMWM3NTc4MzBjODJmYjc2ZWY0ZDMxNyIsImlhdCI6MTY0NjAzMjM2MSwiZXhwIjoxNjQ4NjI0MzYxfQ.ISQuHgbha-Q4kvn8GDUEwTONK0gn_QH86E6SQKQTg78
10 | if (
11 | req.headers.authorization &&
12 | req.headers.authorization.startsWith('Bearer')
13 | ) {
14 | try {
15 | // Get token from header
16 | token = req.headers.authorization.split(' ')[1]
17 | // Verify token
18 | const decoded = jwt.verify(token, process.env.JWT_SECRET)
19 | // Get user from token
20 | req.user = await User.findById(decoded.id).select('-password')
21 | // console.log(`token: ${token} decoded: ${decoded} user: ${req.user}`)
22 |
23 | next() // Continue to next middleware
24 | } catch (error) {
25 | console.log(error)
26 | res.status(401)
27 | throw new Error('Not authorized to access this route')
28 | }
29 | }
30 |
31 | if (!token) {
32 | res.status(401)
33 | throw new Error('Not authorized to access this route')
34 | }
35 | })
36 |
37 | module.exports = { protect }
38 |
--------------------------------------------------------------------------------
/backend/middleware/errorMiddleware.js:
--------------------------------------------------------------------------------
1 | const errorHandler = (err, req, res, next) => {
2 | const statusCode = res.statusCode ? res.statusCode : 500
3 | res.status(statusCode)
4 | res.json({
5 | message: err.message,
6 | stack: process.env.NODE_ENV === 'production' ? null : err.stack
7 | })
8 | }
9 |
10 | module.exports = { errorHandler }
11 |
--------------------------------------------------------------------------------
/backend/models/noteModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | const noteSchema = mongoose.Schema(
4 | {
5 | user: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | required: true,
8 | ref: 'User'
9 | },
10 | ticket: {
11 | type: mongoose.Schema.Types.ObjectId,
12 | required: true,
13 | ref: 'Ticket'
14 | },
15 | text: {
16 | type: String,
17 | required: [true, 'Please add some text']
18 | },
19 | isStaff: {
20 | type: Boolean,
21 | default: false
22 | },
23 | staffId: {
24 | type: String
25 | }
26 | },
27 | {
28 | timestamps: true
29 | }
30 | )
31 |
32 | module.exports = mongoose.model('Note', noteSchema)
33 |
--------------------------------------------------------------------------------
/backend/models/ticketModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | const ticketSchema = mongoose.Schema(
4 | {
5 | user: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | required: true,
8 | ref: 'User'
9 | },
10 | product: {
11 | type: String,
12 | required: [true, 'Please select a product'],
13 | enum: [
14 | 'iPhone',
15 | 'iPad',
16 | 'MacBook',
17 | 'Macbook Pro',
18 | 'iMac',
19 | 'iPod',
20 | 'iPod touch'
21 | ]
22 | },
23 | description: {
24 | type: String,
25 | required: [true, 'Please enter a decription of the issue']
26 | },
27 | status: {
28 | type: String,
29 | required: true,
30 | enum: ['new', 'open', 'close'],
31 | default: 'new'
32 | }
33 | },
34 | {
35 | timestamps: true
36 | }
37 | )
38 |
39 | module.exports = mongoose.model('Ticket', ticketSchema)
40 |
--------------------------------------------------------------------------------
/backend/models/userModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | const userSchema = mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: [true, 'Please add a name']
8 | },
9 | email: {
10 | type: String,
11 | required: [true, 'Please add a email'],
12 | unique: true
13 | },
14 | password: {
15 | type: String,
16 | required: [true, 'Please add a password']
17 | },
18 | isAdmin: {
19 | type: Boolean,
20 | required: true,
21 | default: false
22 | }
23 | },
24 | {
25 | timestamps: true
26 | }
27 | )
28 |
29 | module.exports = mongoose.model('User', userSchema)
30 |
--------------------------------------------------------------------------------
/backend/routes/noteRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router({ mergeParams: true })
3 | const { getNotes, addNote } = require('../controllers/noteController')
4 |
5 | const { protect } = require('../middleware/authMiddleware')
6 |
7 | router
8 | .route('/')
9 | .get(protect, getNotes)
10 | .post(protect, addNote)
11 |
12 | module.exports = router
13 |
14 | // /api/tickets/:ticketId/notes
15 |
--------------------------------------------------------------------------------
/backend/routes/ticketRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const {
4 | getTickets,
5 | getTicket,
6 | createTicket,
7 | updateTicket,
8 | deleteTicket
9 | } = require('../controllers/ticketController')
10 |
11 | const { protect } = require('../middleware/authMiddleware')
12 |
13 | // Re-route into note router
14 | const noteRouter = require('./noteRoutes')
15 | router.use('/:ticketId/notes', noteRouter)
16 |
17 | // Protected route to create the ticket
18 | router
19 | .route('/')
20 | .get(protect, getTickets)
21 | .post(protect, createTicket)
22 |
23 | router
24 | .route('/:id')
25 | .get(protect, getTicket)
26 | .delete(protect, deleteTicket)
27 | .put(protect, updateTicket)
28 |
29 | module.exports = router
30 |
--------------------------------------------------------------------------------
/backend/routes/userRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const {
4 | registerUser,
5 | loginUser,
6 | getMe
7 | } = require('../controllers/userController')
8 |
9 | const { protect } = require('../middleware/authMiddleware')
10 |
11 | router.post('/', registerUser)
12 |
13 | router.post('/login', loginUser)
14 |
15 | // Protected route (2nd argument) - protect
16 | router.get('/me', protect, getMe)
17 |
18 | module.exports = router
19 |
--------------------------------------------------------------------------------
/backend/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express') // commonjs module syntax
2 | const colors = require('colors')
3 | const dotenv = require('dotenv').config()
4 | const { errorHandler } = require('./middleware/errorMiddleware')
5 | const connectDB = require('./config/db')
6 | const path = require('path')
7 | const PORT = process.env.PORT || 3000
8 |
9 | // Connect to database
10 | connectDB()
11 |
12 | const app = express()
13 |
14 | /**
15 | * Each app.use(middleware) is called every time
16 | * a request is sent to the server
17 | */
18 |
19 | /**
20 | * This is a built-in middleware function in Express.
21 | * It parses incoming requests with JSON payloads and is based on body-parser.
22 | */
23 | app.use(express.json())
24 |
25 | /**
26 | * This is a built-in middleware function in Express.
27 | * It parses incoming requests (Object as strings or arrays) with
28 | * urlencoded payloads and is based on body-parser.
29 | */
30 | app.use(express.urlencoded({ extended: false }))
31 |
32 | // Routes endpoints
33 | app.use('/api/users', require('./routes/userRoutes'))
34 | app.use('/api/tickets', require('./routes/ticketRoutes'))
35 |
36 | // Serve Frontend
37 | if (process.env.NODE_ENV === 'production') {
38 | // Set build folder as static folder
39 | app.use(express.static(path.join(__dirname, '../frontend/build')))
40 |
41 | app.get('*', (req, res) => {
42 | res.sendFile(path.join(__dirname, '../frontend/build/index.html'))
43 | })
44 | } else {
45 | app.get('/', (req, res) => {
46 | res.status(200).json({ message: 'Welcome to the Support Desk API' })
47 | })
48 | }
49 |
50 | app.use(errorHandler)
51 |
52 | /**
53 | * app.listen()
54 | * Starts a UNIX socket and listens for connections on the given path.
55 | * This method is identical to Node’s http.Server.listen().
56 | */
57 | app.listen(PORT, () => {
58 | console.log(`Server is running on port ${PORT}`)
59 | })
60 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app), using the [Redux](https://redux.js.org/) and [Redux Toolkit](https://redux-toolkit.js.org/) template.
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "proxy": "http://localhost:5000/",
5 | "private": true,
6 | "dependencies": {
7 | "@reduxjs/toolkit": "^1.8.0",
8 | "@testing-library/jest-dom": "^4.2.4",
9 | "@testing-library/react": "^9.5.0",
10 | "@testing-library/user-event": "^7.2.1",
11 | "axios": "^0.26.0",
12 | "react": "^17.0.2",
13 | "react-dom": "^17.0.2",
14 | "react-icons": "^4.3.1",
15 | "react-modal": "^3.14.4",
16 | "react-redux": "^7.2.6",
17 | "react-router-dom": "^6.2.1",
18 | "react-scripts": "5.0.0",
19 | "react-toastify": "^8.2.0"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": {
31 | "production": [
32 | ">0.2%",
33 | "not dead",
34 | "not op_mini all"
35 | ],
36 | "development": [
37 | "last 1 chrome version",
38 | "last 1 firefox version",
39 | "last 1 safari version"
40 | ]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdarif/support-desk/82c85887884c89db437de340fe49c82aed3d2db7/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React Redux App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdarif/support-desk/82c85887884c89db437de340fe49c82aed3d2db7/frontend/public/logo192.png
--------------------------------------------------------------------------------
/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdarif/support-desk/82c85887884c89db437de340fe49c82aed3d2db7/frontend/public/logo512.png
--------------------------------------------------------------------------------
/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
2 | import { ToastContainer } from 'react-toastify'
3 | import 'react-toastify/dist/ReactToastify.css'
4 | import Header from './components/Header'
5 | import { PrivateRoute } from './components/PrivateRoute'
6 | import Home from './pages/Home'
7 | import Login from './pages/Login'
8 | import NewTicket from './pages/NewTicket'
9 | import Register from './pages/Register'
10 | import Tickets from './pages/Tickets'
11 | import Ticket from './pages/Ticket'
12 |
13 | function App () {
14 | return (
15 | <>
16 |
17 |
18 |
19 |
20 | } />
21 | } />
22 | } />
23 | }>
24 | } />
25 |
26 | }>
27 | } />
28 |
29 | }>
30 | } />
31 |
32 |
33 |
34 |
35 |
36 | >
37 | )
38 | }
39 |
40 | export default App
41 |
--------------------------------------------------------------------------------
/frontend/src/app/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit'
2 | import authReducer from '../features/auth/authSlice'
3 | import ticketReducer from '../features/tickets/ticketSlice'
4 | import noteReducer from '../features/notes/noteSlice'
5 |
6 | export const store = configureStore({
7 | reducer: {
8 | auth: authReducer,
9 | tickets: ticketReducer,
10 | notes: noteReducer
11 | }
12 | })
13 |
--------------------------------------------------------------------------------
/frontend/src/components/BackButton.jsx:
--------------------------------------------------------------------------------
1 | import { FaArrowCircleLeft } from "react-icons/fa";
2 | import { Link } from "react-router-dom";
3 |
4 | export const BackButton = ({ url }) => {
5 | return (
6 |
7 |
8 | Back
9 |
10 | );
11 | };
12 |
13 | export default BackButton;
14 |
--------------------------------------------------------------------------------
/frontend/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import { FaSignOutAlt, FaSignInAlt, FaUser } from "react-icons/fa";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { logout, reset } from "../features/auth/authSlice";
5 |
6 | function Header() {
7 | const navigate = useNavigate();
8 | const dispatch = useDispatch();
9 | const { user } = useSelector((state) => state.auth);
10 |
11 | const onLogout = () => {
12 | dispatch(logout());
13 | dispatch(reset());
14 | navigate("/");
15 | };
16 |
17 | return (
18 |
19 |
20 | Support Desk
21 |
22 |
23 | {user ? (
24 | -
25 |
29 |
30 | ) : (
31 | <>
32 | -
33 |
34 | Login
35 |
36 |
37 | -
38 |
39 | Register
40 |
41 |
42 | >
43 | )}
44 |
45 |
46 | );
47 | }
48 |
49 | export default Header;
50 |
--------------------------------------------------------------------------------
/frontend/src/components/NoteItem.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 |
3 | function NoteItem({ note }) {
4 | const { user } = useSelector((state) => state.auth);
5 |
6 | const options = {
7 | weekday: "long",
8 | year: "numeric",
9 | month: "long",
10 | day: "numeric",
11 | };
12 |
13 | return (
14 |
21 |
22 | Note from {note.isStaff ? Staff : {user.name}}
23 |
24 |
{note.text}
25 |
26 | {new Date(note.createdAt).toLocaleString("en-US", options)}
27 |
28 |
29 | );
30 | }
31 |
32 | export default NoteItem;
33 |
--------------------------------------------------------------------------------
/frontend/src/components/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import { Navigate, Outlet } from "react-router-dom";
2 | import { useAuthStatus } from "../hooks/useAuthStatus";
3 | import Spinner from "./Spinner";
4 |
5 | export const PrivateRoute = () => {
6 | const { loggedIn, checkingStatus } = useAuthStatus();
7 |
8 | if (checkingStatus) {
9 | return ;
10 | }
11 |
12 | return loggedIn ? : ;
13 |
14 | return ;
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/src/components/Spinner.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function Spinner() {
4 | return (
5 |
8 | );
9 | }
10 |
11 | export default Spinner;
12 |
--------------------------------------------------------------------------------
/frontend/src/components/TicketItem.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | function TicketItem({ ticket }) {
5 | const options = {
6 | weekday: "long",
7 | year: "numeric",
8 | month: "long",
9 | day: "numeric",
10 | };
11 | return (
12 |
13 |
{new Date(ticket.createdAt).toLocaleString("en-US", options)}
14 |
{ticket.product}
15 |
{ticket.status}
16 |
17 | View
18 |
19 |
20 | );
21 | }
22 |
23 | export default TicketItem;
24 |
--------------------------------------------------------------------------------
/frontend/src/features/auth/authService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | const API_URL = '/api/users'
4 |
5 | // Register user
6 | const register = async userData => {
7 | // userData is an object with an email and password i.e {name: 'Hina', email: 'hina@*****.com', password: '******'}
8 | const response = await axios.post(API_URL, userData)
9 |
10 | if (response.data) {
11 | // localStorage can only hold strings
12 | localStorage.setItem('user', JSON.stringify(response.data))
13 | }
14 |
15 | return response.data
16 | }
17 |
18 | // Login user
19 | const login = async userData => {
20 | // userData is an object with an email and password i.e {email: 'arif@*****.com', password: '******'}
21 | const response = await axios.post(API_URL + '/login', userData)
22 |
23 | if (response.data) {
24 | // localStorage can only hold strings
25 | // Save the user data to localStorage in a key called 'user'
26 | localStorage.setItem('user', JSON.stringify(response.data))
27 | }
28 |
29 | return response.data
30 | }
31 |
32 | // Logout user
33 | const logout = () => localStorage.removeItem('user')
34 |
35 | const authService = {
36 | register,
37 | logout,
38 | login
39 | }
40 |
41 | export default authService
42 |
--------------------------------------------------------------------------------
/frontend/src/features/auth/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
2 | import authService from './authService'
3 |
4 | // Get logged in user info from localStorage
5 | const user = JSON.parse(localStorage.getItem('user'))
6 |
7 | const initialState = {
8 | user: user ? user : null,
9 | isError: false,
10 | isLoading: false,
11 | isSuccess: false,
12 | message: ''
13 | }
14 |
15 | // Register new user
16 | /**
17 | * createAsyncThunk: A function that accepts a Redux action type string and
18 | * a callback function that should return a promise.
19 | * It generates promise lifecycle action types based
20 | * on the action type prefix that you pass in,
21 | * and returns a thunk action creator that will
22 | * run the promise callback and dispatch the
23 | * lifecycle actions based on the returned promise.
24 | * This abstracts the standard recommended approach for handling async request lifecycles.
25 | */
26 |
27 | /**
28 | * 'auth/register' is the action type string in this case.
29 | * Whenever this function is dispatched from a component within our application,
30 | * 'createAsyncThunk' generates promise lifecycle action types using this
31 | * string as a prefix.
32 | * pending: auth/register/pending
33 | * fulfilled: auth/register/fulfilled
34 | * rejected: auth/register/rejected
35 | */
36 |
37 | /**
38 | * On its initial call, 'createAsyncThunk' dispatches the auth/register/pending
39 | * lifecycle action type. The payloadCreator then executes to return either a
40 | * result or an error.
41 | *
42 | * In the event of an error, auth/register/rejected is dispatched and
43 | * 'createAsyncThunk' should either return a rejected promise containing
44 | * an Error instance, a plain descriptive message,
45 | * or a resolved promise with a RejectWithValue
46 | * argument as returned by the thunkAPI.rejectWithValue function.
47 | *
48 | * If our data fetch is successful, the posts/getPosts/fulfilled action
49 | * type gets dispatched.
50 | *
51 | */
52 |
53 | export const register = createAsyncThunk(
54 | //action type string
55 | 'auth/register',
56 |
57 | // callback function
58 | async (user, thunkAPI) => {
59 | try {
60 | return await authService.register(user)
61 | } catch (error) {
62 | /**
63 | * Remember that when your 'payloadCreator' returns a rejected promise,
64 | * the 'rejected' action is dispatched (with 'action.payload' as 'undefined').
65 | */
66 | const message =
67 | (error.response &&
68 | error.response.data &&
69 | error.response.data.message) ||
70 | error.message ||
71 | error.toString()
72 | /**
73 | * By using 'thunkAPI', you can return a resolved promise to the reducer,
74 | * which has 'action.payload' set to a custom value of your choice.
75 | * 'thunkAPI' uses its 'rejectWithValue' property to perform this.
76 | */
77 | return thunkAPI.rejectWithValue(message)
78 | }
79 | }
80 | )
81 |
82 | // Login user
83 | export const login = createAsyncThunk('auth/login', async (user, thunkAPI) => {
84 | try {
85 | return await authService.login(user)
86 | } catch (error) {
87 | const message =
88 | (error.response && error.response.data && error.response.data.message) ||
89 | error.message ||
90 | error.toString()
91 |
92 | return thunkAPI.rejectWithValue(message)
93 | }
94 | })
95 |
96 | // Logout user
97 | export const logout = createAsyncThunk('auth/logout', async () => {
98 | await authService.logout()
99 | })
100 |
101 | /**
102 | * A Slice 'createSlice' function that accepts an initial state, an object of reducer functions,
103 | * and a "slice name", and automatically generates action creators and
104 | * action types that correspond to the reducers and state.
105 | */
106 | /**
107 | * Within 'createSlice', synchronous requests made to the store are handled
108 | * in the reducers object while extraReducers handles asynchronous requests
109 | */
110 | export const authSlice = createSlice({
111 | // A name, used in action types
112 | name: 'auth',
113 | // The initial state for the reducer
114 | initialState,
115 | // An object of "case reducers". Key names will be used to generate actions.
116 | reducers: {
117 | reset: state => {
118 | state.isError = false
119 | state.isLoading = false
120 | state.isSuccess = false
121 | state.message = ''
122 | }
123 | },
124 | /**
125 | *
126 | * extraReducers: One of the key concepts of Redux is that each slice reducer "owns"
127 | * its slice of state, and that many slice reducers can independently
128 | * respond to the same action type. extraReducers allows createSlice
129 | * to respond to other action types besides the types it has generated.
130 | *
131 | */
132 |
133 | /**
134 | *
135 | * The three lifecycle action types mentioned earlier can then be evaluated
136 | * in extraReducers, where we make our desired changes to the store.
137 | * In this case
138 | */
139 | extraReducers: builder => {
140 | builder
141 | .addCase(register.pending, state => {
142 | state.isLoading = true
143 | })
144 | .addCase(register.fulfilled, (state, action) => {
145 | state.isLoading = false
146 | state.isSuccess = true
147 | state.user = action.payload
148 | })
149 | .addCase(register.rejected, (state, action) => {
150 | state.isLoading = false
151 | state.isError = true
152 | state.message = action.payload
153 | state.user = null
154 | })
155 | .addCase(login.pending, state => {
156 | state.isLoading = true
157 | })
158 | .addCase(login.fulfilled, (state, action) => {
159 | state.isLoading = false
160 | state.isSuccess = true
161 | state.user = action.payload
162 | })
163 | .addCase(login.rejected, (state, action) => {
164 | state.isLoading = false
165 | state.isError = true
166 | state.message = action.payload
167 | state.user = null
168 | })
169 | .addCase(logout.fulfilled, state => {
170 | state.user = null
171 | })
172 | }
173 | })
174 |
175 | export const { reset } = authSlice.actions
176 | /**
177 | * Every slice you create must be added to your Redux store (src/app/store.js)
178 | * so you can gain access to its contents.
179 | */
180 | export default authSlice.reducer
181 |
--------------------------------------------------------------------------------
/frontend/src/features/notes/noteService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | const API_URL = '/api/tickets/'
4 |
5 | // Get ticket notes
6 | const getNotes = async (ticketId, token) => {
7 | const config = {
8 | headers: {
9 | Authorization: `Bearer ${token}`
10 | }
11 | }
12 |
13 | const response = await axios.get(API_URL + ticketId + '/notes', config)
14 |
15 | return response.data
16 | }
17 |
18 | // Create ticket note
19 | const createNote = async (noteText, ticketId, token) => {
20 | const config = {
21 | headers: {
22 | Authorization: `Bearer ${token}`
23 | }
24 | }
25 |
26 | const response = await axios.post(
27 | API_URL + ticketId + '/notes',
28 | {
29 | text: noteText
30 | },
31 | config
32 | )
33 |
34 | return response.data
35 | }
36 |
37 | const noteService = {
38 | getNotes,
39 | createNote
40 | }
41 |
42 | export default noteService
43 |
--------------------------------------------------------------------------------
/frontend/src/features/notes/noteSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
2 | import noteService from './noteService'
3 |
4 | const initialState = {
5 | notes: [],
6 | isError: false,
7 | isSuccess: false,
8 | isLoading: false,
9 | message: ''
10 | }
11 |
12 | // Get ticket notes
13 | /**
14 | * createAsyncThunk: A function that accepts a Redux action type string and
15 | * a callback function that should return a promise.
16 | * It generates promise lifecycle action types based
17 | * on the action type prefix that you pass in,
18 | * and returns a thunk action creator that will
19 | * run the promise callback and dispatch the
20 | * lifecycle actions based on the returned promise.
21 | * This abstracts the standard recommended approach for handling async request lifecycles.
22 | */
23 | export const getNotes = createAsyncThunk(
24 | 'tickets/getAll',
25 | async (ticketId, thunkAPI) => {
26 | /**
27 | * thunkAPI: an object containing all of the parameters
28 | * that are normally passed to a Redux thunk function,
29 | * as well as additional options: https://redux-toolkit.js.org/api/createAsyncThunk
30 | */
31 | try {
32 | // Token is required for authentication
33 | const token = thunkAPI.getState().auth.user.token
34 | return await noteService.getNotes(ticketId, token)
35 | } catch (error) {
36 | const message =
37 | (error.response &&
38 | error.response.data &&
39 | error.response.data.message) ||
40 | error.message ||
41 | error.toString()
42 |
43 | return thunkAPI.rejectWithValue(message)
44 | }
45 | }
46 | )
47 |
48 | // Creata ticket note
49 | /**
50 | * createAsyncThunk: A function that accepts a Redux action type string and
51 | * a callback function that should return a promise.
52 | * It generates promise lifecycle action types based
53 | * on the action type prefix that you pass in,
54 | * and returns a thunk action creator that will
55 | * run the promise callback and dispatch the
56 | * lifecycle actions based on the returned promise.
57 | * This abstracts the standard recommended approach for handling async request lifecycles.
58 | */
59 | export const createNote = createAsyncThunk(
60 | 'notes/create',
61 | async ({ noteText, ticketId }, thunkAPI) => {
62 | /**
63 | * thunkAPI: an object containing all of the parameters
64 | * that are normally passed to a Redux thunk function,
65 | * as well as additional options: https://redux-toolkit.js.org/api/createAsyncThunk
66 | */
67 | try {
68 | // Token is required for authentication
69 | const token = thunkAPI.getState().auth.user.token
70 | return await noteService.createNote(noteText, ticketId, token)
71 | } catch (error) {
72 | const message =
73 | (error.response &&
74 | error.response.data &&
75 | error.response.data.message) ||
76 | error.message ||
77 | error.toString()
78 |
79 | return thunkAPI.rejectWithValue(message)
80 | }
81 | }
82 | )
83 |
84 | export const noteSlice = createSlice({
85 | name: 'note',
86 | initialState,
87 | reducers: {
88 | reset: state => initialState
89 | },
90 | extraReducers: builder => {
91 | builder
92 | .addCase(getNotes.pending, state => {
93 | state.isLoading = true
94 | })
95 | .addCase(getNotes.fulfilled, (state, action) => {
96 | state.isLoading = false
97 | state.isSuccess = true
98 | state.notes = action.payload
99 | })
100 | .addCase(getNotes.rejected, (state, action) => {
101 | state.isLoading = false
102 | state.isError = true
103 | state.message = action.payload
104 | })
105 | .addCase(createNote.pending, state => {
106 | state.isLoading = true
107 | })
108 | .addCase(createNote.fulfilled, (state, action) => {
109 | state.isLoading = false
110 | state.isSuccess = true
111 | state.notes.push(action.payload)
112 | })
113 | .addCase(createNote.rejected, (state, action) => {
114 | state.isLoading = false
115 | state.isError = true
116 | state.message = action.payload
117 | })
118 | }
119 | })
120 |
121 | export const { reset } = noteSlice.actions
122 | export default noteSlice.reducer
123 |
--------------------------------------------------------------------------------
/frontend/src/features/tickets/ticketService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | const API_URL = '/api/tickets/'
4 |
5 | // Create new ticket
6 | const createTicket = async (ticketData, token) => {
7 | const config = {
8 | headers: {
9 | Authorization: `Bearer ${token}`
10 | }
11 | }
12 |
13 | const response = await axios.post(API_URL, ticketData, config)
14 |
15 | return response.data
16 | }
17 |
18 | // Get user tickets
19 | const getTickets = async token => {
20 | const config = {
21 | headers: {
22 | Authorization: `Bearer ${token}`
23 | }
24 | }
25 |
26 | const response = await axios.get(API_URL, config)
27 |
28 | return response.data
29 | }
30 |
31 | // Get user ticket
32 | const getTicket = async (ticketId, token) => {
33 | const config = {
34 | headers: {
35 | Authorization: `Bearer ${token}`
36 | }
37 | }
38 |
39 | const response = await axios.get(API_URL + ticketId, config)
40 |
41 | return response.data
42 | }
43 |
44 | // Close ticket
45 | const closeTicket = async (ticketId, token) => {
46 | const config = {
47 | headers: {
48 | Authorization: `Bearer ${token}`
49 | }
50 | }
51 |
52 | const response = await axios.put(
53 | API_URL + ticketId,
54 | { status: 'close' },
55 | config
56 | )
57 |
58 | return response.data
59 | }
60 |
61 | const ticketService = {
62 | createTicket,
63 | getTickets,
64 | getTicket,
65 | closeTicket
66 | }
67 |
68 | export default ticketService
69 |
--------------------------------------------------------------------------------
/frontend/src/features/tickets/ticketSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
2 | import ticketService from './ticketService'
3 |
4 | const initialState = {
5 | tickets: [],
6 | ticket: {},
7 | isError: false,
8 | isSuccess: false,
9 | isLoading: false,
10 | message: ''
11 | }
12 |
13 | // Create new ticket
14 | /**
15 | * createAsyncThunk: A function that accepts a Redux action type string and
16 | * a callback function that should return a promise.
17 | * It generates promise lifecycle action types based
18 | * on the action type prefix that you pass in,
19 | * and returns a thunk action creator that will
20 | * run the promise callback and dispatch the
21 | * lifecycle actions based on the returned promise.
22 | * This abstracts the standard recommended approach for handling async request lifecycles.
23 | */
24 | export const createTicket = createAsyncThunk(
25 | 'tickets/create',
26 | async (ticketData, thunkAPI) => {
27 | /**
28 | * thunkAPI: an object containing all of the parameters
29 | * that are normally passed to a Redux thunk function,
30 | * as well as additional options: https://redux-toolkit.js.org/api/createAsyncThunk
31 | */
32 | try {
33 | // Token is required for authentication
34 | const token = thunkAPI.getState().auth.user.token
35 | return await ticketService.createTicket(ticketData, token)
36 | } catch (error) {
37 | const message =
38 | (error.response &&
39 | error.response.data &&
40 | error.response.data.message) ||
41 | error.message ||
42 | error.toString()
43 |
44 | return thunkAPI.rejectWithValue(message)
45 | }
46 | }
47 | )
48 |
49 | // Get user tickets
50 | export const getTickets = createAsyncThunk(
51 | 'tickets/getAll',
52 | async (_, thunkAPI) => {
53 | /**
54 | * thunkAPI: an object containing all of the parameters
55 | * that are normally passed to a Redux thunk function,
56 | * as well as additional options: https://redux-toolkit.js.org/api/createAsyncThunk
57 | */
58 | try {
59 | // Token is required for authentication
60 | const token = thunkAPI.getState().auth.user.token
61 | return await ticketService.getTickets(token)
62 | } catch (error) {
63 | const message =
64 | (error.response &&
65 | error.response.data &&
66 | error.response.data.message) ||
67 | error.message ||
68 | error.toString()
69 |
70 | return thunkAPI.rejectWithValue(message)
71 | }
72 | }
73 | )
74 |
75 | // Get user ticket
76 | export const getTicket = createAsyncThunk(
77 | 'tickets/get',
78 | async (ticketId, thunkAPI) => {
79 | /**
80 | * thunkAPI: an object containing all of the parameters
81 | * that are normally passed to a Redux thunk function,
82 | * as well as additional options: https://redux-toolkit.js.org/api/createAsyncThunk
83 | */
84 | try {
85 | // Token is required for authentication
86 | const token = thunkAPI.getState().auth.user.token
87 | return await ticketService.getTicket(ticketId, token)
88 | } catch (error) {
89 | const message =
90 | (error.response &&
91 | error.response.data &&
92 | error.response.data.message) ||
93 | error.message ||
94 | error.toString()
95 |
96 | return thunkAPI.rejectWithValue(message)
97 | }
98 | }
99 | )
100 |
101 | // Close ticket
102 | export const closeTicket = createAsyncThunk(
103 | 'tickets/close',
104 | async (ticketId, thunkAPI) => {
105 | /**
106 | * thunkAPI: an object containing all of the parameters
107 | * that are normally passed to a Redux thunk function,
108 | * as well as additional options: https://redux-toolkit.js.org/api/createAsyncThunk
109 | */
110 | try {
111 | // Token is required for authentication
112 | const token = thunkAPI.getState().auth.user.token
113 | return await ticketService.closeTicket(ticketId, token) // Service function
114 | } catch (error) {
115 | const message =
116 | (error.response &&
117 | error.response.data &&
118 | error.response.data.message) ||
119 | error.message ||
120 | error.toString()
121 |
122 | return thunkAPI.rejectWithValue(message)
123 | }
124 | }
125 | )
126 |
127 | export const ticketSlice = createSlice({
128 | name: 'ticket',
129 | initialState,
130 | reducers: {
131 | reset: state => initialState
132 | },
133 | extraReducers: builder => {
134 | builder
135 | .addCase(createTicket.pending, state => {
136 | state.isLoading = true
137 | })
138 | .addCase(createTicket.fulfilled, state => {
139 | state.isLoading = false
140 | state.isSuccess = true
141 | })
142 | .addCase(createTicket.rejected, (state, action) => {
143 | state.isLoading = false
144 | state.isError = true
145 | state.message = action.payload
146 | })
147 | .addCase(getTickets.pending, state => {
148 | state.isLoading = true
149 | })
150 | .addCase(getTickets.fulfilled, (state, action) => {
151 | state.isLoading = false
152 | state.isSuccess = true
153 | state.tickets = action.payload
154 | })
155 | .addCase(getTickets.rejected, (state, action) => {
156 | state.isLoading = false
157 | state.isError = true
158 | state.message = action.payload
159 | })
160 | .addCase(getTicket.pending, state => {
161 | state.isLoading = true
162 | })
163 | .addCase(getTicket.fulfilled, (state, action) => {
164 | state.isLoading = false
165 | state.isSuccess = true
166 | state.ticket = action.payload
167 | })
168 | .addCase(getTicket.rejected, (state, action) => {
169 | state.isLoading = false
170 | state.isError = true
171 | state.message = action.payload
172 | })
173 | .addCase(closeTicket.fulfilled, (state, action) => {
174 | state.isLoading = false
175 | state.tickets.map(ticket =>
176 | ticket._id === action.payload._id ? (ticket.status = 'close') : ticket
177 | )
178 | })
179 | }
180 | })
181 |
182 | export const { reset } = ticketSlice.actions
183 | export default ticketSlice.reducer
184 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useAuthStatus.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { useSelector } from 'react-redux'
3 |
4 | export const useAuthStatus = () => {
5 | const [loggedIn, setLoggedIn] = useState(false)
6 | const [checkingStatus, setCheckingStatus] = useState(true)
7 |
8 | const { user } = useSelector(state => state.auth)
9 |
10 | useEffect(() => {
11 | if (user) {
12 | setLoggedIn(true)
13 | } else {
14 | setLoggedIn(false)
15 | }
16 | setCheckingStatus(false)
17 | }, [user])
18 | return { loggedIn, checkingStatus }
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap');
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | }
8 |
9 | body {
10 | font-family: 'Poppins', sans-serif;
11 | height: 100vh;
12 | }
13 |
14 | a {
15 | text-decoration: none;
16 | color: #000;
17 | }
18 |
19 | p {
20 | line-height: 1.7;
21 | }
22 |
23 | ul {
24 | list-style: none;
25 | }
26 |
27 | li {
28 | line-height: 2.2;
29 | }
30 |
31 | h1,
32 | h2,
33 | h3 {
34 | font-weight: 600;
35 | margin-bottom: 10px;
36 | }
37 |
38 | .container {
39 | width: 100%;
40 | max-width: 960px;
41 | margin: 0 auto;
42 | padding: 0 20px;
43 | text-align: center;
44 | }
45 |
46 | .header {
47 | display: flex;
48 | justify-content: space-between;
49 | align-items: center;
50 | padding: 20px 0;
51 | border-bottom: 1px solid #e6e6e6;
52 | margin-bottom: 60px;
53 | }
54 |
55 | .header ul {
56 | display: flex;
57 | align-items: center;
58 | justify-content: space-between;
59 | }
60 |
61 | .header ul li {
62 | margin-left: 20px;
63 | }
64 |
65 | .header ul li a {
66 | display: flex;
67 | align-items: center;
68 | }
69 |
70 | .header ul li a:hover {
71 | color: #777;
72 | }
73 |
74 | .header ul li a svg {
75 | margin-right: 5px;
76 | }
77 |
78 | .heading {
79 | font-size: 2rem;
80 | font-weight: 700;
81 | margin-bottom: 50px;
82 | padding: 0 20px;
83 | }
84 |
85 | .heading p {
86 | color: #828282;
87 | }
88 |
89 | .boxes {
90 | display: grid;
91 | grid-template-columns: repeat(3, 1fr);
92 | gap: 20px;
93 | justify-content: space-between;
94 | align-items: center;
95 | text-align: center;
96 | margin-bottom: 30px;
97 | }
98 |
99 | .boxes div {
100 | padding: 30px;
101 | border: 1px solid #e6e6e6;
102 | border-radius: 10px;
103 | }
104 |
105 | .boxes h2 {
106 | margin-top: 20px;
107 | }
108 |
109 | .boxes a:hover {
110 | color: #777;
111 | }
112 |
113 | .form {
114 | width: 70%;
115 | margin: 0 auto;
116 | }
117 |
118 | .form-group {
119 | margin-bottom: 10px;
120 | }
121 |
122 | .form-group input,
123 | .form-group textarea,
124 | .form-group select {
125 | width: 100%;
126 | padding: 10px;
127 | border: 1px solid #e6e6e6;
128 | border-radius: 5px;
129 | margin-bottom: 10px;
130 | font-family: inherit;
131 | }
132 |
133 | .form-group label {
134 | text-align: left;
135 | display: block;
136 | margin: 0 0 5px 3px;
137 | }
138 |
139 | .btn {
140 | padding: 10px 20px;
141 | border: 1px solid #000;
142 | border-radius: 5px;
143 | background: #000;
144 | color: #fff;
145 | font-size: 16px;
146 | font-weight: 700;
147 | cursor: pointer;
148 | text-align: center;
149 | appearance: button;
150 | display: flex;
151 | align-items: center;
152 | justify-content: center;
153 | }
154 |
155 | .btn svg {
156 | margin-right: 8px;
157 | }
158 |
159 | .btn-reverse {
160 | background: #fff;
161 | color: #000;
162 | }
163 |
164 | .btn-block {
165 | width: 100%;
166 | margin-bottom: 20px;
167 | }
168 |
169 | .btn-sm {
170 | padding: 5px 15px;
171 | font-size: 13px;
172 | }
173 |
174 | .btn-danger {
175 | background: darkred;
176 | border: none;
177 | }
178 |
179 | .btn-back {
180 | width: 150px;
181 | margin-bottom: 20px;
182 | }
183 |
184 | .btn:hover {
185 | transform: scale(0.98);
186 | }
187 |
188 | .ticket-created {
189 | border: 1px solid #e6e6e6;
190 | border-radius: 5px;
191 | padding: 50px;
192 | }
193 |
194 | .ticket-number {
195 | margin-bottom: 30px;
196 | }
197 |
198 | .ticket-number h2 {
199 | font-size: 2.3rem;
200 | margin-bottom: 10px;
201 | }
202 |
203 | .ticket-number p {
204 | font-size: 1.3rem;
205 | }
206 |
207 | .ticket-info {
208 | font-size: 1.3rem;
209 | }
210 |
211 | .ticket,
212 | .ticket-headings {
213 | display: grid;
214 | grid-template-columns: repeat(4, 1fr);
215 | gap: 20px;
216 | justify-content: space-between;
217 | align-items: center;
218 | margin-bottom: 20px;
219 | background: #f4f4f4;
220 | padding: 10px 15px;
221 | border-radius: 5px;
222 | text-align: center;
223 | }
224 |
225 | .ticket-headings {
226 | font-weight: 700;
227 | }
228 |
229 | .status {
230 | background-color: #333;
231 | color: #fff;
232 | width: 100px;
233 | padding: 0 20px;
234 | justify-self: center;
235 | border-radius: 10px;
236 | font-size: 16px;
237 | text-align: center;
238 | }
239 |
240 | .status-new {
241 | background-color: green;
242 | color: #fff;
243 | border-radius: 10px;
244 | }
245 |
246 | .status-open {
247 | background-color: steelblue;
248 | color: #fff;
249 | border-radius: 10px;
250 | }
251 |
252 | .status-close {
253 | background-color: darkred;
254 | color: #fff;
255 | border-radius: 10px;
256 | }
257 |
258 | .ticket-page {
259 | position: relative;
260 | text-align: left;
261 | }
262 |
263 | .ticket-page h2 {
264 | display: flex;
265 | align-items: center;
266 | justify-content: space-between;
267 | }
268 |
269 | .ticket-page .btn {
270 | margin-bottom: 30px;
271 | }
272 |
273 | .ticket-page .btn-block {
274 | width: 100%;
275 | margin-top: 30px;
276 | }
277 |
278 | .ticket-desc {
279 | margin: 20px 0;
280 | font-size: 17px;
281 | background-color: #f4f4f4;
282 | border: 1px #ccc solid;
283 | padding: 10px 15px;
284 | border-radius: 5px;
285 | }
286 |
287 | .note {
288 | border: 1px solid #e6e6e6;
289 | border-radius: 5px;
290 | text-align: left;
291 | padding: 20px;
292 | margin-bottom: 20px;
293 | position: relative;
294 | }
295 |
296 | .note-head {
297 | background: #f4f4f4;
298 | padding: 5px 20px;
299 | display: flex;
300 | justify-content: space-between;
301 | align-items: center;
302 | margin-bottom: 20px;
303 | }
304 |
305 | .note-date {
306 | position: absolute;
307 | top: 15px;
308 | right: 10px;
309 | font-size: 14px;
310 | }
311 |
312 | .delete-note {
313 | color: red;
314 | cursor: pointer;
315 | position: absolute;
316 | bottom: 10px;
317 | right: 20px;
318 | }
319 |
320 | .btn-close {
321 | background: none;
322 | border: none;
323 | color: #000;
324 | position: absolute;
325 | top: 5px;
326 | right: 5px;
327 | font-size: 16px;
328 | cursor: pointer;
329 | }
330 |
331 | .btn-close:hover {
332 | color: red;
333 | transform: scale(0.98);
334 | }
335 |
336 | p.status-in-progress {
337 | color: orangered;
338 | }
339 |
340 | p.status-waiting {
341 | color: red;
342 | }
343 |
344 | p.status-ready {
345 | color: steelblue;
346 | }
347 |
348 | p.status-complete {
349 | color: green;
350 | }
351 |
352 | footer {
353 | position: sticky;
354 | top: 95vh;
355 | text-align: center;
356 | }
357 |
358 | .loadingSpinnerContainer {
359 | position: fixed;
360 | top: 0;
361 | right: 0;
362 | bottom: 0;
363 | left: 0;
364 | background-color: rgba(0, 0, 0, 0.5);
365 | z-index: 5000;
366 | display: flex;
367 | justify-content: center;
368 | align-items: center;
369 | }
370 |
371 | .loadingSpinner {
372 | width: 64px;
373 | height: 64px;
374 | border: 8px solid;
375 | border-color: #000 transparent #555 transparent;
376 | border-radius: 50%;
377 | animation: spin 1.2s linear infinite;
378 | }
379 |
380 | @keyframes spin {
381 | 0% {
382 | transform: rotate(0deg);
383 | }
384 | 100% {
385 | transform: rotate(360deg);
386 | }
387 | }
388 |
389 | @media (max-width: 600px) {
390 | .boxes {
391 | grid-template-columns: 1fr;
392 | }
393 |
394 | .form {
395 | width: 90%;
396 | }
397 |
398 | .ticket-created h2,
399 | .heading h1 {
400 | font-size: 2rem;
401 | }
402 |
403 | .heading p {
404 | font-size: 1.5rem;
405 | }
406 | }
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import { store } from './app/store';
6 | import { Provider } from 'react-redux';
7 | import * as serviceWorker from './serviceWorker';
8 |
9 | ReactDOM.render(
10 |
11 |
12 |
13 |
14 | ,
15 | document.getElementById('root')
16 | );
17 |
18 | // If you want your app to work offline and load faster, you can change
19 | // unregister() to register() below. Note this comes with some pitfalls.
20 | // Learn more about service workers: https://bit.ly/CRA-PWA
21 | serviceWorker.unregister();
22 |
--------------------------------------------------------------------------------
/frontend/src/pages/Home.jsx:
--------------------------------------------------------------------------------
1 | import { FaQuestionCircle, FaTicketAlt } from "react-icons/fa";
2 | import { Link } from "react-router-dom";
3 |
4 | function Home() {
5 | return (
6 | <>
7 |
8 | What do you need help with?
9 | Please choose from an option below
10 |
11 |
12 |
13 | Create New Ticket
14 |
15 |
16 |
17 | View My Tickets
18 |
19 | >
20 | );
21 | }
22 |
23 | export default Home;
24 |
--------------------------------------------------------------------------------
/frontend/src/pages/Login.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { toast } from "react-toastify";
3 | import { useNavigate } from "react-router-dom";
4 | import { FaSignInAlt } from "react-icons/fa";
5 | import { useSelector, useDispatch } from "react-redux";
6 | import { login, reset } from "../features/auth/authSlice";
7 | import Spinner from "../components/Spinner";
8 |
9 | function Login() {
10 | const [formData, setFormData] = useState({
11 | email: "",
12 | password: "",
13 | });
14 |
15 | const { email, password } = formData; // destructuring
16 |
17 | /**
18 | * useDispatch() returns a reference to the dispatch function
19 | * from the Redux store. You may use it to dispatch actions
20 | * as needed.
21 | */
22 | const dispatch = useDispatch();
23 | const navigate = useNavigate();
24 |
25 | /**
26 | * useSelector() allows you to extract data from the Redux store state,
27 | * using a selector function.
28 | */
29 |
30 | const { user, isLoading, isError, isSuccess, message } = useSelector(
31 | (state) => state.auth
32 | );
33 |
34 | useEffect(() => {
35 | if (isError) {
36 | toast.error(message);
37 | }
38 |
39 | // Redirect when logged in
40 | if (isSuccess || user) {
41 | navigate("/");
42 | }
43 |
44 | dispatch(reset());
45 | }, [isError, isSuccess, user, navigate, message, dispatch]);
46 |
47 | const onChange = (e) => {
48 | setFormData((prevState) => ({
49 | ...prevState,
50 | [e.target.name]: e.target.value,
51 | }));
52 | };
53 |
54 | const onSubmit = (e) => {
55 | e.preventDefault();
56 |
57 | const userData = {
58 | email,
59 | password,
60 | };
61 |
62 | dispatch(login(userData));
63 | };
64 |
65 | if (isLoading) {
66 | return ;
67 | }
68 |
69 | return (
70 | <>
71 |
72 |
73 | Login
74 |
75 | Please log in to get support
76 |
77 |
78 |
109 | >
110 | );
111 | }
112 |
113 | export default Login;
114 |
--------------------------------------------------------------------------------
/frontend/src/pages/NewTicket.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import { useNavigate } from "react-router-dom";
4 | import { toast } from "react-toastify";
5 | import { createTicket, reset } from "../features/tickets/ticketSlice";
6 | import Spinner from "../components/Spinner";
7 | import BackButton from "../components/BackButton";
8 |
9 | function NewTicket() {
10 | const { user } = useSelector((state) => state.auth);
11 | const { isLoading, isError, isSuccess, message } = useSelector(
12 | (state) => state.tickets
13 | );
14 |
15 | const [name] = useState(user.name);
16 | const [email] = useState(user.email);
17 | const [product, setProduct] = useState("iPhone");
18 | const [description, setDescription] = useState("");
19 |
20 | const dispatch = useDispatch();
21 | const navigate = useNavigate();
22 |
23 | useEffect(() => {
24 | if (isError) {
25 | toast.error(message);
26 | }
27 |
28 | if (isSuccess) {
29 | dispatch(reset());
30 | navigate("/tickets");
31 | }
32 | }, [dispatch, isError, isSuccess, navigate, message]);
33 |
34 | const onSubmit = (e) => {
35 | e.preventDefault();
36 | dispatch(createTicket({ product, description }));
37 | };
38 |
39 | if (isLoading) return ;
40 |
41 | return (
42 | <>
43 |
44 |
45 | Create New Ticket
46 | Please fill out the form below
47 |
48 |
49 |
55 |
56 |
92 | >
93 | );
94 | }
95 |
96 | export default NewTicket;
97 |
--------------------------------------------------------------------------------
/frontend/src/pages/Register.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { useNavigate } from "react-router-dom";
3 | import { toast } from "react-toastify";
4 | import { FaUser } from "react-icons/fa";
5 | import { useSelector, useDispatch } from "react-redux";
6 | import { register, reset } from "../features/auth/authSlice";
7 | import Spinner from "../components/Spinner";
8 |
9 | function Register() {
10 | const [formData, setFormData] = useState({
11 | name: "",
12 | email: "",
13 | password: "",
14 | password2: "",
15 | });
16 |
17 | const { name, email, password, password2 } = formData; // destructuring
18 |
19 | /**
20 | * useDispatch() returns a reference to the dispatch function
21 | * from the Redux store. You may use it to dispatch actions
22 | * as needed.
23 | */
24 |
25 | /**
26 | * By using 'useSelector' and 'useDispatch' from 'react-redux',
27 | * we can read state from a Redux store and dispatch any
28 | * action from a component, respectively.
29 | */
30 | const dispatch = useDispatch();
31 |
32 | const navigate = useNavigate();
33 |
34 | /**
35 | * useSelector() allows you to extract data from the Redux store state,
36 | * using a selector function.
37 | */
38 | const { user, isLoading, isError, isSuccess, message } = useSelector(
39 | (state) => state.auth
40 | );
41 |
42 | useEffect(() => {
43 | if (isError) {
44 | toast.error(message);
45 | }
46 |
47 | // Redirect when logged in
48 | if (isSuccess || user) {
49 | navigate("/");
50 | }
51 |
52 | dispatch(reset());
53 | }, [isError, isSuccess, user, navigate, message, dispatch]);
54 |
55 | const onChange = (e) => {
56 | setFormData((prevState) => ({
57 | ...prevState,
58 | [e.target.name]: e.target.value,
59 | }));
60 | };
61 |
62 | const onSubmit = (e) => {
63 | e.preventDefault();
64 |
65 | if (password !== password2) {
66 | toast.error("Passwords do not match");
67 | } else {
68 | const userData = {
69 | name,
70 | email,
71 | password,
72 | };
73 |
74 | dispatch(register(userData));
75 | }
76 | };
77 |
78 | if (isLoading) {
79 | return ;
80 | }
81 |
82 | return (
83 | <>
84 |
85 |
86 | Register
87 |
88 | Please create an account
89 |
90 |
91 |
146 | >
147 | );
148 | }
149 |
150 | export default Register;
151 |
--------------------------------------------------------------------------------
/frontend/src/pages/Ticket.jsx:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from "react-redux";
2 | import BackButton from "../components/BackButton";
3 | import { getTicket, closeTicket } from "../features/tickets/ticketSlice";
4 | import {
5 | getNotes,
6 | createNote,
7 | reset as notesReset,
8 | } from "../features/notes/noteSlice";
9 | import Spinner from "../components/Spinner";
10 | import { useParams, useNavigate } from "react-router-dom";
11 | import { useEffect, useState } from "react";
12 | import { toast } from "react-toastify";
13 | import NoteItem from "../components/NoteItem";
14 | import Modal from "react-modal";
15 | import { FaPlus } from "react-icons/fa";
16 |
17 | const customStyles = {
18 | content: {
19 | width: "600px",
20 | top: "50%",
21 | left: "50%",
22 | right: "auto",
23 | bottom: "auto",
24 | marginRight: "-50%",
25 | transform: "translate(-50%, -50%)",
26 | position: "relative",
27 | },
28 | };
29 |
30 | Modal.setAppElement("#root");
31 |
32 | function Ticket() {
33 | const options = {
34 | weekday: "long",
35 | year: "numeric",
36 | month: "long",
37 | day: "numeric",
38 | };
39 | const [modalIsOpen, setModalIsOpen] = useState(false);
40 | const [noteText, setNoteText] = useState("");
41 |
42 | const { ticket, isLoading, isError, message } = useSelector(
43 | (state) => state.tickets
44 | );
45 |
46 | const { notes, isLoading: notesIsLoading } = useSelector(
47 | (state) => state.notes
48 | );
49 |
50 | const { ticketId } = useParams();
51 | const dispatch = useDispatch();
52 | const navigate = useNavigate();
53 |
54 | useEffect(() => {
55 | if (isError) {
56 | toast.error(message);
57 | }
58 |
59 | dispatch(getTicket(ticketId));
60 | dispatch(getNotes(ticketId));
61 | // eslint-disable-next-line
62 | }, [isError, message, ticketId]);
63 |
64 | if (isLoading || notesIsLoading) return ;
65 |
66 | if (isError) {
67 | return Something went wrong
;
68 | }
69 |
70 | // Close ticket
71 | const onTicketClose = () => {
72 | dispatch(closeTicket(ticketId));
73 | toast.success("Ticket Closed");
74 | navigate("/tickets");
75 | };
76 |
77 | // Open/Close Modal
78 | const openModal = () => {
79 | setModalIsOpen(true);
80 | };
81 | const closeModal = () => {
82 | setModalIsOpen(false);
83 | };
84 |
85 | // Create Note Submit
86 | const onNoteSubmit = (e) => {
87 | e.preventDefault();
88 | dispatch(createNote({ ticketId, noteText }));
89 | closeModal();
90 | };
91 |
92 | return (
93 |
94 |
95 |
96 |
97 | Ticket ID: {ticket._id}
98 |
99 | {ticket.status}
100 |
101 |
102 |
103 | Date Submitted:{" "}
104 | {new Date(ticket.createdAt).toLocaleString("en-US", options)}
105 |
106 | Product: {ticket.product}
107 |
108 |
109 |
Description of Issue
110 |
{ticket.description}
111 |
112 | Notes
113 |
114 |
115 | {ticket.status !== "close" && (
116 |
119 | )}
120 |
121 |
127 | Add Note
128 |
131 |
148 |
149 |
150 | {notes.map((note) => (
151 |
152 | ))}
153 |
154 | {ticket.status !== "close" && (
155 |
158 | )}
159 |
160 | );
161 | }
162 |
163 | export default Ticket;
164 |
--------------------------------------------------------------------------------
/frontend/src/pages/Tickets.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import Spinner from "../components/Spinner";
4 | import BackButton from "../components/BackButton";
5 | import { getTickets, reset } from "../features/tickets/ticketSlice";
6 | import TicketItem from "../components/TicketItem";
7 |
8 | function Tickets() {
9 | const { tickets, isLoading, isSuccess } = useSelector(
10 | (state) => state.tickets
11 | );
12 |
13 | const dispatch = useDispatch();
14 |
15 | useEffect(() => {
16 | // Unmounting
17 | return () => {
18 | if (isSuccess) {
19 | dispatch(reset());
20 | }
21 | };
22 | }, [dispatch, isSuccess]);
23 |
24 | useEffect(() => {
25 | dispatch(getTickets());
26 | }, [dispatch]);
27 |
28 | if (isLoading) return ;
29 |
30 | return (
31 | <>
32 |
33 | Tickets
34 |
35 |
36 |
Date
37 |
Product
38 |
Status
39 |
40 |
41 | {tickets.map((ticket) => (
42 |
43 | ))}
44 |
45 | >
46 | );
47 | }
48 |
49 | export default Tickets;
50 |
--------------------------------------------------------------------------------
/frontend/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then((registration) => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch((error) => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then((response) => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then((registration) => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready.then((registration) => {
134 | registration.unregister();
135 | });
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "support-desk",
3 | "version": "1.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "support-desk",
9 | "version": "1.0.0",
10 | "license": "MIT",
11 | "dependencies": {
12 | "bcryptjs": "^2.4.3",
13 | "colors": "^1.4.0",
14 | "concurrently": "^7.0.0",
15 | "dotenv": "^16.0.0",
16 | "express": "^4.17.3",
17 | "jsonwebtoken": "^8.5.1",
18 | "mongoose": "^6.2.3"
19 | },
20 | "devDependencies": {
21 | "express-async-handler": "^1.2.0",
22 | "nodeman": "^1.1.2"
23 | }
24 | },
25 | "node_modules/@types/node": {
26 | "version": "17.0.21",
27 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
28 | "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ=="
29 | },
30 | "node_modules/@types/webidl-conversions": {
31 | "version": "6.1.1",
32 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz",
33 | "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
34 | },
35 | "node_modules/@types/whatwg-url": {
36 | "version": "8.2.1",
37 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
38 | "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
39 | "dependencies": {
40 | "@types/node": "*",
41 | "@types/webidl-conversions": "*"
42 | }
43 | },
44 | "node_modules/accepts": {
45 | "version": "1.3.8",
46 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
47 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
48 | "dependencies": {
49 | "mime-types": "~2.1.34",
50 | "negotiator": "0.6.3"
51 | },
52 | "engines": {
53 | "node": ">= 0.6"
54 | }
55 | },
56 | "node_modules/ansi-regex": {
57 | "version": "5.0.1",
58 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
59 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
60 | "engines": {
61 | "node": ">=8"
62 | }
63 | },
64 | "node_modules/ansi-styles": {
65 | "version": "4.3.0",
66 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
67 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
68 | "dependencies": {
69 | "color-convert": "^2.0.1"
70 | },
71 | "engines": {
72 | "node": ">=8"
73 | },
74 | "funding": {
75 | "url": "https://github.com/chalk/ansi-styles?sponsor=1"
76 | }
77 | },
78 | "node_modules/array-flatten": {
79 | "version": "1.1.1",
80 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
81 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
82 | },
83 | "node_modules/base64-js": {
84 | "version": "1.5.1",
85 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
86 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
87 | "funding": [
88 | {
89 | "type": "github",
90 | "url": "https://github.com/sponsors/feross"
91 | },
92 | {
93 | "type": "patreon",
94 | "url": "https://www.patreon.com/feross"
95 | },
96 | {
97 | "type": "consulting",
98 | "url": "https://feross.org/support"
99 | }
100 | ]
101 | },
102 | "node_modules/bcryptjs": {
103 | "version": "2.4.3",
104 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
105 | "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
106 | },
107 | "node_modules/body-parser": {
108 | "version": "1.19.2",
109 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz",
110 | "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==",
111 | "dependencies": {
112 | "bytes": "3.1.2",
113 | "content-type": "~1.0.4",
114 | "debug": "2.6.9",
115 | "depd": "~1.1.2",
116 | "http-errors": "1.8.1",
117 | "iconv-lite": "0.4.24",
118 | "on-finished": "~2.3.0",
119 | "qs": "6.9.7",
120 | "raw-body": "2.4.3",
121 | "type-is": "~1.6.18"
122 | },
123 | "engines": {
124 | "node": ">= 0.8"
125 | }
126 | },
127 | "node_modules/bson": {
128 | "version": "4.6.1",
129 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz",
130 | "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==",
131 | "dependencies": {
132 | "buffer": "^5.6.0"
133 | },
134 | "engines": {
135 | "node": ">=6.9.0"
136 | }
137 | },
138 | "node_modules/buffer": {
139 | "version": "5.7.1",
140 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
141 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
142 | "funding": [
143 | {
144 | "type": "github",
145 | "url": "https://github.com/sponsors/feross"
146 | },
147 | {
148 | "type": "patreon",
149 | "url": "https://www.patreon.com/feross"
150 | },
151 | {
152 | "type": "consulting",
153 | "url": "https://feross.org/support"
154 | }
155 | ],
156 | "dependencies": {
157 | "base64-js": "^1.3.1",
158 | "ieee754": "^1.1.13"
159 | }
160 | },
161 | "node_modules/buffer-equal-constant-time": {
162 | "version": "1.0.1",
163 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
164 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
165 | },
166 | "node_modules/bytes": {
167 | "version": "3.1.2",
168 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
169 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
170 | "engines": {
171 | "node": ">= 0.8"
172 | }
173 | },
174 | "node_modules/chalk": {
175 | "version": "4.1.2",
176 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
177 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
178 | "dependencies": {
179 | "ansi-styles": "^4.1.0",
180 | "supports-color": "^7.1.0"
181 | },
182 | "engines": {
183 | "node": ">=10"
184 | },
185 | "funding": {
186 | "url": "https://github.com/chalk/chalk?sponsor=1"
187 | }
188 | },
189 | "node_modules/chalk/node_modules/supports-color": {
190 | "version": "7.2.0",
191 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
192 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
193 | "dependencies": {
194 | "has-flag": "^4.0.0"
195 | },
196 | "engines": {
197 | "node": ">=8"
198 | }
199 | },
200 | "node_modules/cliui": {
201 | "version": "7.0.4",
202 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
203 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
204 | "dependencies": {
205 | "string-width": "^4.2.0",
206 | "strip-ansi": "^6.0.0",
207 | "wrap-ansi": "^7.0.0"
208 | }
209 | },
210 | "node_modules/color-convert": {
211 | "version": "2.0.1",
212 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
213 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
214 | "dependencies": {
215 | "color-name": "~1.1.4"
216 | },
217 | "engines": {
218 | "node": ">=7.0.0"
219 | }
220 | },
221 | "node_modules/color-name": {
222 | "version": "1.1.4",
223 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
224 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
225 | },
226 | "node_modules/colors": {
227 | "version": "1.4.0",
228 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
229 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
230 | "engines": {
231 | "node": ">=0.1.90"
232 | }
233 | },
234 | "node_modules/concurrently": {
235 | "version": "7.0.0",
236 | "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.0.0.tgz",
237 | "integrity": "sha512-WKM7PUsI8wyXpF80H+zjHP32fsgsHNQfPLw/e70Z5dYkV7hF+rf8q3D+ScWJIEr57CpkO3OWBko6hwhQLPR8Pw==",
238 | "dependencies": {
239 | "chalk": "^4.1.0",
240 | "date-fns": "^2.16.1",
241 | "lodash": "^4.17.21",
242 | "rxjs": "^6.6.3",
243 | "spawn-command": "^0.0.2-1",
244 | "supports-color": "^8.1.0",
245 | "tree-kill": "^1.2.2",
246 | "yargs": "^16.2.0"
247 | },
248 | "bin": {
249 | "concurrently": "dist/bin/concurrently.js"
250 | },
251 | "engines": {
252 | "node": "^12.20.0 || ^14.13.0 || >=16.0.0"
253 | }
254 | },
255 | "node_modules/content-disposition": {
256 | "version": "0.5.4",
257 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
258 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
259 | "dependencies": {
260 | "safe-buffer": "5.2.1"
261 | },
262 | "engines": {
263 | "node": ">= 0.6"
264 | }
265 | },
266 | "node_modules/content-type": {
267 | "version": "1.0.4",
268 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
269 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
270 | "engines": {
271 | "node": ">= 0.6"
272 | }
273 | },
274 | "node_modules/cookie": {
275 | "version": "0.4.2",
276 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
277 | "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
278 | "engines": {
279 | "node": ">= 0.6"
280 | }
281 | },
282 | "node_modules/cookie-signature": {
283 | "version": "1.0.6",
284 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
285 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
286 | },
287 | "node_modules/date-fns": {
288 | "version": "2.28.0",
289 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
290 | "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==",
291 | "engines": {
292 | "node": ">=0.11"
293 | },
294 | "funding": {
295 | "type": "opencollective",
296 | "url": "https://opencollective.com/date-fns"
297 | }
298 | },
299 | "node_modules/debug": {
300 | "version": "2.6.9",
301 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
302 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
303 | "dependencies": {
304 | "ms": "2.0.0"
305 | }
306 | },
307 | "node_modules/denque": {
308 | "version": "2.0.1",
309 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
310 | "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==",
311 | "engines": {
312 | "node": ">=0.10"
313 | }
314 | },
315 | "node_modules/depd": {
316 | "version": "1.1.2",
317 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
318 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
319 | "engines": {
320 | "node": ">= 0.6"
321 | }
322 | },
323 | "node_modules/destroy": {
324 | "version": "1.0.4",
325 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
326 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
327 | },
328 | "node_modules/dotenv": {
329 | "version": "16.0.0",
330 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz",
331 | "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==",
332 | "engines": {
333 | "node": ">=12"
334 | }
335 | },
336 | "node_modules/ecdsa-sig-formatter": {
337 | "version": "1.0.11",
338 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
339 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
340 | "dependencies": {
341 | "safe-buffer": "^5.0.1"
342 | }
343 | },
344 | "node_modules/ee-first": {
345 | "version": "1.1.1",
346 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
347 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
348 | },
349 | "node_modules/emoji-regex": {
350 | "version": "8.0.0",
351 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
352 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
353 | },
354 | "node_modules/encodeurl": {
355 | "version": "1.0.2",
356 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
357 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
358 | "engines": {
359 | "node": ">= 0.8"
360 | }
361 | },
362 | "node_modules/escalade": {
363 | "version": "3.1.1",
364 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
365 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
366 | "engines": {
367 | "node": ">=6"
368 | }
369 | },
370 | "node_modules/escape-html": {
371 | "version": "1.0.3",
372 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
373 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
374 | },
375 | "node_modules/etag": {
376 | "version": "1.8.1",
377 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
378 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
379 | "engines": {
380 | "node": ">= 0.6"
381 | }
382 | },
383 | "node_modules/express": {
384 | "version": "4.17.3",
385 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz",
386 | "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==",
387 | "dependencies": {
388 | "accepts": "~1.3.8",
389 | "array-flatten": "1.1.1",
390 | "body-parser": "1.19.2",
391 | "content-disposition": "0.5.4",
392 | "content-type": "~1.0.4",
393 | "cookie": "0.4.2",
394 | "cookie-signature": "1.0.6",
395 | "debug": "2.6.9",
396 | "depd": "~1.1.2",
397 | "encodeurl": "~1.0.2",
398 | "escape-html": "~1.0.3",
399 | "etag": "~1.8.1",
400 | "finalhandler": "~1.1.2",
401 | "fresh": "0.5.2",
402 | "merge-descriptors": "1.0.1",
403 | "methods": "~1.1.2",
404 | "on-finished": "~2.3.0",
405 | "parseurl": "~1.3.3",
406 | "path-to-regexp": "0.1.7",
407 | "proxy-addr": "~2.0.7",
408 | "qs": "6.9.7",
409 | "range-parser": "~1.2.1",
410 | "safe-buffer": "5.2.1",
411 | "send": "0.17.2",
412 | "serve-static": "1.14.2",
413 | "setprototypeof": "1.2.0",
414 | "statuses": "~1.5.0",
415 | "type-is": "~1.6.18",
416 | "utils-merge": "1.0.1",
417 | "vary": "~1.1.2"
418 | },
419 | "engines": {
420 | "node": ">= 0.10.0"
421 | }
422 | },
423 | "node_modules/express-async-handler": {
424 | "version": "1.2.0",
425 | "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz",
426 | "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==",
427 | "dev": true
428 | },
429 | "node_modules/finalhandler": {
430 | "version": "1.1.2",
431 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
432 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
433 | "dependencies": {
434 | "debug": "2.6.9",
435 | "encodeurl": "~1.0.2",
436 | "escape-html": "~1.0.3",
437 | "on-finished": "~2.3.0",
438 | "parseurl": "~1.3.3",
439 | "statuses": "~1.5.0",
440 | "unpipe": "~1.0.0"
441 | },
442 | "engines": {
443 | "node": ">= 0.8"
444 | }
445 | },
446 | "node_modules/forwarded": {
447 | "version": "0.2.0",
448 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
449 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
450 | "engines": {
451 | "node": ">= 0.6"
452 | }
453 | },
454 | "node_modules/fresh": {
455 | "version": "0.5.2",
456 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
457 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
458 | "engines": {
459 | "node": ">= 0.6"
460 | }
461 | },
462 | "node_modules/get-caller-file": {
463 | "version": "2.0.5",
464 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
465 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
466 | "engines": {
467 | "node": "6.* || 8.* || >= 10.*"
468 | }
469 | },
470 | "node_modules/has-flag": {
471 | "version": "4.0.0",
472 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
473 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
474 | "engines": {
475 | "node": ">=8"
476 | }
477 | },
478 | "node_modules/http-errors": {
479 | "version": "1.8.1",
480 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
481 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
482 | "dependencies": {
483 | "depd": "~1.1.2",
484 | "inherits": "2.0.4",
485 | "setprototypeof": "1.2.0",
486 | "statuses": ">= 1.5.0 < 2",
487 | "toidentifier": "1.0.1"
488 | },
489 | "engines": {
490 | "node": ">= 0.6"
491 | }
492 | },
493 | "node_modules/iconv-lite": {
494 | "version": "0.4.24",
495 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
496 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
497 | "dependencies": {
498 | "safer-buffer": ">= 2.1.2 < 3"
499 | },
500 | "engines": {
501 | "node": ">=0.10.0"
502 | }
503 | },
504 | "node_modules/ieee754": {
505 | "version": "1.2.1",
506 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
507 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
508 | "funding": [
509 | {
510 | "type": "github",
511 | "url": "https://github.com/sponsors/feross"
512 | },
513 | {
514 | "type": "patreon",
515 | "url": "https://www.patreon.com/feross"
516 | },
517 | {
518 | "type": "consulting",
519 | "url": "https://feross.org/support"
520 | }
521 | ]
522 | },
523 | "node_modules/inherits": {
524 | "version": "2.0.4",
525 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
526 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
527 | },
528 | "node_modules/ip": {
529 | "version": "1.1.5",
530 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
531 | "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
532 | },
533 | "node_modules/ipaddr.js": {
534 | "version": "1.9.1",
535 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
536 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
537 | "engines": {
538 | "node": ">= 0.10"
539 | }
540 | },
541 | "node_modules/is-fullwidth-code-point": {
542 | "version": "3.0.0",
543 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
544 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
545 | "engines": {
546 | "node": ">=8"
547 | }
548 | },
549 | "node_modules/jsonwebtoken": {
550 | "version": "8.5.1",
551 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
552 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
553 | "dependencies": {
554 | "jws": "^3.2.2",
555 | "lodash.includes": "^4.3.0",
556 | "lodash.isboolean": "^3.0.3",
557 | "lodash.isinteger": "^4.0.4",
558 | "lodash.isnumber": "^3.0.3",
559 | "lodash.isplainobject": "^4.0.6",
560 | "lodash.isstring": "^4.0.1",
561 | "lodash.once": "^4.0.0",
562 | "ms": "^2.1.1",
563 | "semver": "^5.6.0"
564 | },
565 | "engines": {
566 | "node": ">=4",
567 | "npm": ">=1.4.28"
568 | }
569 | },
570 | "node_modules/jsonwebtoken/node_modules/ms": {
571 | "version": "2.1.3",
572 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
573 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
574 | },
575 | "node_modules/jwa": {
576 | "version": "1.4.1",
577 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
578 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
579 | "dependencies": {
580 | "buffer-equal-constant-time": "1.0.1",
581 | "ecdsa-sig-formatter": "1.0.11",
582 | "safe-buffer": "^5.0.1"
583 | }
584 | },
585 | "node_modules/jws": {
586 | "version": "3.2.2",
587 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
588 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
589 | "dependencies": {
590 | "jwa": "^1.4.1",
591 | "safe-buffer": "^5.0.1"
592 | }
593 | },
594 | "node_modules/kareem": {
595 | "version": "2.3.4",
596 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.4.tgz",
597 | "integrity": "sha512-Vcrt8lcpVl0s8ePx634BxwRqmFo+5DcOhlmNadehxreMTIQi/9hOL/B3hZQQbK5DgMS7Lem3xABXV7/S3jy+7g=="
598 | },
599 | "node_modules/lodash": {
600 | "version": "4.17.21",
601 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
602 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
603 | },
604 | "node_modules/lodash.includes": {
605 | "version": "4.3.0",
606 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
607 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
608 | },
609 | "node_modules/lodash.isboolean": {
610 | "version": "3.0.3",
611 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
612 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
613 | },
614 | "node_modules/lodash.isinteger": {
615 | "version": "4.0.4",
616 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
617 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
618 | },
619 | "node_modules/lodash.isnumber": {
620 | "version": "3.0.3",
621 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
622 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
623 | },
624 | "node_modules/lodash.isplainobject": {
625 | "version": "4.0.6",
626 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
627 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
628 | },
629 | "node_modules/lodash.isstring": {
630 | "version": "4.0.1",
631 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
632 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
633 | },
634 | "node_modules/lodash.once": {
635 | "version": "4.1.1",
636 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
637 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
638 | },
639 | "node_modules/media-typer": {
640 | "version": "0.3.0",
641 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
642 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
643 | "engines": {
644 | "node": ">= 0.6"
645 | }
646 | },
647 | "node_modules/memory-pager": {
648 | "version": "1.5.0",
649 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
650 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
651 | "optional": true
652 | },
653 | "node_modules/merge-descriptors": {
654 | "version": "1.0.1",
655 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
656 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
657 | },
658 | "node_modules/methods": {
659 | "version": "1.1.2",
660 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
661 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
662 | "engines": {
663 | "node": ">= 0.6"
664 | }
665 | },
666 | "node_modules/mime": {
667 | "version": "1.6.0",
668 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
669 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
670 | "bin": {
671 | "mime": "cli.js"
672 | },
673 | "engines": {
674 | "node": ">=4"
675 | }
676 | },
677 | "node_modules/mime-db": {
678 | "version": "1.51.0",
679 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
680 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
681 | "engines": {
682 | "node": ">= 0.6"
683 | }
684 | },
685 | "node_modules/mime-types": {
686 | "version": "2.1.34",
687 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
688 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
689 | "dependencies": {
690 | "mime-db": "1.51.0"
691 | },
692 | "engines": {
693 | "node": ">= 0.6"
694 | }
695 | },
696 | "node_modules/mongodb": {
697 | "version": "4.3.1",
698 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.1.tgz",
699 | "integrity": "sha512-sNa8APSIk+r4x31ZwctKjuPSaeKuvUeNb/fu/3B6dRM02HpEgig7hTHM8A/PJQTlxuC/KFWlDlQjhsk/S43tBg==",
700 | "dependencies": {
701 | "bson": "^4.6.1",
702 | "denque": "^2.0.1",
703 | "mongodb-connection-string-url": "^2.4.1",
704 | "socks": "^2.6.1"
705 | },
706 | "engines": {
707 | "node": ">=12.9.0"
708 | },
709 | "optionalDependencies": {
710 | "saslprep": "^1.0.3"
711 | }
712 | },
713 | "node_modules/mongodb-connection-string-url": {
714 | "version": "2.5.2",
715 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz",
716 | "integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==",
717 | "dependencies": {
718 | "@types/whatwg-url": "^8.2.1",
719 | "whatwg-url": "^11.0.0"
720 | }
721 | },
722 | "node_modules/mongoose": {
723 | "version": "6.2.3",
724 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.2.3.tgz",
725 | "integrity": "sha512-FxF2D0MGGIw9bAJ57nSyM4Hs4tDHbu6dn9gQwT1J/lxmRB8jfaWWJ3FSJXTmeYlQ6BpyKeIaT8fj6SAX0YMNBA==",
726 | "dependencies": {
727 | "bson": "^4.2.2",
728 | "kareem": "2.3.4",
729 | "mongodb": "4.3.1",
730 | "mpath": "0.8.4",
731 | "mquery": "4.0.2",
732 | "ms": "2.1.3",
733 | "sift": "16.0.0"
734 | },
735 | "engines": {
736 | "node": ">=12.0.0"
737 | },
738 | "funding": {
739 | "type": "opencollective",
740 | "url": "https://opencollective.com/mongoose"
741 | }
742 | },
743 | "node_modules/mongoose/node_modules/ms": {
744 | "version": "2.1.3",
745 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
746 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
747 | },
748 | "node_modules/mpath": {
749 | "version": "0.8.4",
750 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
751 | "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==",
752 | "engines": {
753 | "node": ">=4.0.0"
754 | }
755 | },
756 | "node_modules/mquery": {
757 | "version": "4.0.2",
758 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.2.tgz",
759 | "integrity": "sha512-oAVF0Nil1mT3rxty6Zln4YiD6x6QsUWYz927jZzjMxOK2aqmhEz5JQ7xmrKK7xRFA2dwV+YaOpKU/S+vfNqKxA==",
760 | "dependencies": {
761 | "debug": "4.x"
762 | },
763 | "engines": {
764 | "node": ">=12.0.0"
765 | }
766 | },
767 | "node_modules/mquery/node_modules/debug": {
768 | "version": "4.3.3",
769 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
770 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
771 | "dependencies": {
772 | "ms": "2.1.2"
773 | },
774 | "engines": {
775 | "node": ">=6.0"
776 | },
777 | "peerDependenciesMeta": {
778 | "supports-color": {
779 | "optional": true
780 | }
781 | }
782 | },
783 | "node_modules/mquery/node_modules/ms": {
784 | "version": "2.1.2",
785 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
786 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
787 | },
788 | "node_modules/ms": {
789 | "version": "2.0.0",
790 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
791 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
792 | },
793 | "node_modules/negotiator": {
794 | "version": "0.6.3",
795 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
796 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
797 | "engines": {
798 | "node": ">= 0.6"
799 | }
800 | },
801 | "node_modules/nodeman": {
802 | "version": "1.1.2",
803 | "resolved": "https://registry.npmjs.org/nodeman/-/nodeman-1.1.2.tgz",
804 | "integrity": "sha1-eBKFCPdUDbv/v38QkyIuCdWOG2Q=",
805 | "dev": true,
806 | "dependencies": {
807 | "colors": "*"
808 | },
809 | "bin": {
810 | "_nodeman": "bin/_nodeman",
811 | "nodeman": "bin/nodeman"
812 | },
813 | "engines": {
814 | "node": "*"
815 | }
816 | },
817 | "node_modules/on-finished": {
818 | "version": "2.3.0",
819 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
820 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
821 | "dependencies": {
822 | "ee-first": "1.1.1"
823 | },
824 | "engines": {
825 | "node": ">= 0.8"
826 | }
827 | },
828 | "node_modules/parseurl": {
829 | "version": "1.3.3",
830 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
831 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
832 | "engines": {
833 | "node": ">= 0.8"
834 | }
835 | },
836 | "node_modules/path-to-regexp": {
837 | "version": "0.1.7",
838 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
839 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
840 | },
841 | "node_modules/proxy-addr": {
842 | "version": "2.0.7",
843 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
844 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
845 | "dependencies": {
846 | "forwarded": "0.2.0",
847 | "ipaddr.js": "1.9.1"
848 | },
849 | "engines": {
850 | "node": ">= 0.10"
851 | }
852 | },
853 | "node_modules/punycode": {
854 | "version": "2.1.1",
855 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
856 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
857 | "engines": {
858 | "node": ">=6"
859 | }
860 | },
861 | "node_modules/qs": {
862 | "version": "6.9.7",
863 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
864 | "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
865 | "engines": {
866 | "node": ">=0.6"
867 | },
868 | "funding": {
869 | "url": "https://github.com/sponsors/ljharb"
870 | }
871 | },
872 | "node_modules/range-parser": {
873 | "version": "1.2.1",
874 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
875 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
876 | "engines": {
877 | "node": ">= 0.6"
878 | }
879 | },
880 | "node_modules/raw-body": {
881 | "version": "2.4.3",
882 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz",
883 | "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==",
884 | "dependencies": {
885 | "bytes": "3.1.2",
886 | "http-errors": "1.8.1",
887 | "iconv-lite": "0.4.24",
888 | "unpipe": "1.0.0"
889 | },
890 | "engines": {
891 | "node": ">= 0.8"
892 | }
893 | },
894 | "node_modules/require-directory": {
895 | "version": "2.1.1",
896 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
897 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
898 | "engines": {
899 | "node": ">=0.10.0"
900 | }
901 | },
902 | "node_modules/rxjs": {
903 | "version": "6.6.7",
904 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
905 | "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
906 | "dependencies": {
907 | "tslib": "^1.9.0"
908 | },
909 | "engines": {
910 | "npm": ">=2.0.0"
911 | }
912 | },
913 | "node_modules/safe-buffer": {
914 | "version": "5.2.1",
915 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
916 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
917 | "funding": [
918 | {
919 | "type": "github",
920 | "url": "https://github.com/sponsors/feross"
921 | },
922 | {
923 | "type": "patreon",
924 | "url": "https://www.patreon.com/feross"
925 | },
926 | {
927 | "type": "consulting",
928 | "url": "https://feross.org/support"
929 | }
930 | ]
931 | },
932 | "node_modules/safer-buffer": {
933 | "version": "2.1.2",
934 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
935 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
936 | },
937 | "node_modules/saslprep": {
938 | "version": "1.0.3",
939 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
940 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
941 | "optional": true,
942 | "dependencies": {
943 | "sparse-bitfield": "^3.0.3"
944 | },
945 | "engines": {
946 | "node": ">=6"
947 | }
948 | },
949 | "node_modules/semver": {
950 | "version": "5.7.1",
951 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
952 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
953 | "bin": {
954 | "semver": "bin/semver"
955 | }
956 | },
957 | "node_modules/send": {
958 | "version": "0.17.2",
959 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
960 | "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
961 | "dependencies": {
962 | "debug": "2.6.9",
963 | "depd": "~1.1.2",
964 | "destroy": "~1.0.4",
965 | "encodeurl": "~1.0.2",
966 | "escape-html": "~1.0.3",
967 | "etag": "~1.8.1",
968 | "fresh": "0.5.2",
969 | "http-errors": "1.8.1",
970 | "mime": "1.6.0",
971 | "ms": "2.1.3",
972 | "on-finished": "~2.3.0",
973 | "range-parser": "~1.2.1",
974 | "statuses": "~1.5.0"
975 | },
976 | "engines": {
977 | "node": ">= 0.8.0"
978 | }
979 | },
980 | "node_modules/send/node_modules/ms": {
981 | "version": "2.1.3",
982 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
983 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
984 | },
985 | "node_modules/serve-static": {
986 | "version": "1.14.2",
987 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
988 | "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
989 | "dependencies": {
990 | "encodeurl": "~1.0.2",
991 | "escape-html": "~1.0.3",
992 | "parseurl": "~1.3.3",
993 | "send": "0.17.2"
994 | },
995 | "engines": {
996 | "node": ">= 0.8.0"
997 | }
998 | },
999 | "node_modules/setprototypeof": {
1000 | "version": "1.2.0",
1001 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1002 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1003 | },
1004 | "node_modules/sift": {
1005 | "version": "16.0.0",
1006 | "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
1007 | "integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
1008 | },
1009 | "node_modules/smart-buffer": {
1010 | "version": "4.2.0",
1011 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
1012 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
1013 | "engines": {
1014 | "node": ">= 6.0.0",
1015 | "npm": ">= 3.0.0"
1016 | }
1017 | },
1018 | "node_modules/socks": {
1019 | "version": "2.6.2",
1020 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz",
1021 | "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==",
1022 | "dependencies": {
1023 | "ip": "^1.1.5",
1024 | "smart-buffer": "^4.2.0"
1025 | },
1026 | "engines": {
1027 | "node": ">= 10.13.0",
1028 | "npm": ">= 3.0.0"
1029 | }
1030 | },
1031 | "node_modules/sparse-bitfield": {
1032 | "version": "3.0.3",
1033 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1034 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
1035 | "optional": true,
1036 | "dependencies": {
1037 | "memory-pager": "^1.0.2"
1038 | }
1039 | },
1040 | "node_modules/spawn-command": {
1041 | "version": "0.0.2-1",
1042 | "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
1043 | "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A="
1044 | },
1045 | "node_modules/statuses": {
1046 | "version": "1.5.0",
1047 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
1048 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
1049 | "engines": {
1050 | "node": ">= 0.6"
1051 | }
1052 | },
1053 | "node_modules/string-width": {
1054 | "version": "4.2.3",
1055 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
1056 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
1057 | "dependencies": {
1058 | "emoji-regex": "^8.0.0",
1059 | "is-fullwidth-code-point": "^3.0.0",
1060 | "strip-ansi": "^6.0.1"
1061 | },
1062 | "engines": {
1063 | "node": ">=8"
1064 | }
1065 | },
1066 | "node_modules/strip-ansi": {
1067 | "version": "6.0.1",
1068 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
1069 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
1070 | "dependencies": {
1071 | "ansi-regex": "^5.0.1"
1072 | },
1073 | "engines": {
1074 | "node": ">=8"
1075 | }
1076 | },
1077 | "node_modules/supports-color": {
1078 | "version": "8.1.1",
1079 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
1080 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
1081 | "dependencies": {
1082 | "has-flag": "^4.0.0"
1083 | },
1084 | "engines": {
1085 | "node": ">=10"
1086 | },
1087 | "funding": {
1088 | "url": "https://github.com/chalk/supports-color?sponsor=1"
1089 | }
1090 | },
1091 | "node_modules/toidentifier": {
1092 | "version": "1.0.1",
1093 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1094 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1095 | "engines": {
1096 | "node": ">=0.6"
1097 | }
1098 | },
1099 | "node_modules/tr46": {
1100 | "version": "3.0.0",
1101 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
1102 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
1103 | "dependencies": {
1104 | "punycode": "^2.1.1"
1105 | },
1106 | "engines": {
1107 | "node": ">=12"
1108 | }
1109 | },
1110 | "node_modules/tree-kill": {
1111 | "version": "1.2.2",
1112 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
1113 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
1114 | "bin": {
1115 | "tree-kill": "cli.js"
1116 | }
1117 | },
1118 | "node_modules/tslib": {
1119 | "version": "1.14.1",
1120 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
1121 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
1122 | },
1123 | "node_modules/type-is": {
1124 | "version": "1.6.18",
1125 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1126 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1127 | "dependencies": {
1128 | "media-typer": "0.3.0",
1129 | "mime-types": "~2.1.24"
1130 | },
1131 | "engines": {
1132 | "node": ">= 0.6"
1133 | }
1134 | },
1135 | "node_modules/unpipe": {
1136 | "version": "1.0.0",
1137 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1138 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
1139 | "engines": {
1140 | "node": ">= 0.8"
1141 | }
1142 | },
1143 | "node_modules/utils-merge": {
1144 | "version": "1.0.1",
1145 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1146 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
1147 | "engines": {
1148 | "node": ">= 0.4.0"
1149 | }
1150 | },
1151 | "node_modules/vary": {
1152 | "version": "1.1.2",
1153 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1154 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
1155 | "engines": {
1156 | "node": ">= 0.8"
1157 | }
1158 | },
1159 | "node_modules/webidl-conversions": {
1160 | "version": "7.0.0",
1161 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1162 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1163 | "engines": {
1164 | "node": ">=12"
1165 | }
1166 | },
1167 | "node_modules/whatwg-url": {
1168 | "version": "11.0.0",
1169 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
1170 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
1171 | "dependencies": {
1172 | "tr46": "^3.0.0",
1173 | "webidl-conversions": "^7.0.0"
1174 | },
1175 | "engines": {
1176 | "node": ">=12"
1177 | }
1178 | },
1179 | "node_modules/wrap-ansi": {
1180 | "version": "7.0.0",
1181 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
1182 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
1183 | "dependencies": {
1184 | "ansi-styles": "^4.0.0",
1185 | "string-width": "^4.1.0",
1186 | "strip-ansi": "^6.0.0"
1187 | },
1188 | "engines": {
1189 | "node": ">=10"
1190 | },
1191 | "funding": {
1192 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
1193 | }
1194 | },
1195 | "node_modules/y18n": {
1196 | "version": "5.0.8",
1197 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
1198 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
1199 | "engines": {
1200 | "node": ">=10"
1201 | }
1202 | },
1203 | "node_modules/yargs": {
1204 | "version": "16.2.0",
1205 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
1206 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
1207 | "dependencies": {
1208 | "cliui": "^7.0.2",
1209 | "escalade": "^3.1.1",
1210 | "get-caller-file": "^2.0.5",
1211 | "require-directory": "^2.1.1",
1212 | "string-width": "^4.2.0",
1213 | "y18n": "^5.0.5",
1214 | "yargs-parser": "^20.2.2"
1215 | },
1216 | "engines": {
1217 | "node": ">=10"
1218 | }
1219 | },
1220 | "node_modules/yargs-parser": {
1221 | "version": "20.2.9",
1222 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
1223 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
1224 | "engines": {
1225 | "node": ">=10"
1226 | }
1227 | }
1228 | },
1229 | "dependencies": {
1230 | "@types/node": {
1231 | "version": "17.0.21",
1232 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
1233 | "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ=="
1234 | },
1235 | "@types/webidl-conversions": {
1236 | "version": "6.1.1",
1237 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz",
1238 | "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
1239 | },
1240 | "@types/whatwg-url": {
1241 | "version": "8.2.1",
1242 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
1243 | "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
1244 | "requires": {
1245 | "@types/node": "*",
1246 | "@types/webidl-conversions": "*"
1247 | }
1248 | },
1249 | "accepts": {
1250 | "version": "1.3.8",
1251 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
1252 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
1253 | "requires": {
1254 | "mime-types": "~2.1.34",
1255 | "negotiator": "0.6.3"
1256 | }
1257 | },
1258 | "ansi-regex": {
1259 | "version": "5.0.1",
1260 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
1261 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
1262 | },
1263 | "ansi-styles": {
1264 | "version": "4.3.0",
1265 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
1266 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
1267 | "requires": {
1268 | "color-convert": "^2.0.1"
1269 | }
1270 | },
1271 | "array-flatten": {
1272 | "version": "1.1.1",
1273 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
1274 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
1275 | },
1276 | "base64-js": {
1277 | "version": "1.5.1",
1278 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
1279 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
1280 | },
1281 | "bcryptjs": {
1282 | "version": "2.4.3",
1283 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
1284 | "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
1285 | },
1286 | "body-parser": {
1287 | "version": "1.19.2",
1288 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz",
1289 | "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==",
1290 | "requires": {
1291 | "bytes": "3.1.2",
1292 | "content-type": "~1.0.4",
1293 | "debug": "2.6.9",
1294 | "depd": "~1.1.2",
1295 | "http-errors": "1.8.1",
1296 | "iconv-lite": "0.4.24",
1297 | "on-finished": "~2.3.0",
1298 | "qs": "6.9.7",
1299 | "raw-body": "2.4.3",
1300 | "type-is": "~1.6.18"
1301 | }
1302 | },
1303 | "bson": {
1304 | "version": "4.6.1",
1305 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz",
1306 | "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==",
1307 | "requires": {
1308 | "buffer": "^5.6.0"
1309 | }
1310 | },
1311 | "buffer": {
1312 | "version": "5.7.1",
1313 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
1314 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
1315 | "requires": {
1316 | "base64-js": "^1.3.1",
1317 | "ieee754": "^1.1.13"
1318 | }
1319 | },
1320 | "buffer-equal-constant-time": {
1321 | "version": "1.0.1",
1322 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
1323 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
1324 | },
1325 | "bytes": {
1326 | "version": "3.1.2",
1327 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
1328 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
1329 | },
1330 | "chalk": {
1331 | "version": "4.1.2",
1332 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
1333 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
1334 | "requires": {
1335 | "ansi-styles": "^4.1.0",
1336 | "supports-color": "^7.1.0"
1337 | },
1338 | "dependencies": {
1339 | "supports-color": {
1340 | "version": "7.2.0",
1341 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
1342 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
1343 | "requires": {
1344 | "has-flag": "^4.0.0"
1345 | }
1346 | }
1347 | }
1348 | },
1349 | "cliui": {
1350 | "version": "7.0.4",
1351 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
1352 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
1353 | "requires": {
1354 | "string-width": "^4.2.0",
1355 | "strip-ansi": "^6.0.0",
1356 | "wrap-ansi": "^7.0.0"
1357 | }
1358 | },
1359 | "color-convert": {
1360 | "version": "2.0.1",
1361 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
1362 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
1363 | "requires": {
1364 | "color-name": "~1.1.4"
1365 | }
1366 | },
1367 | "color-name": {
1368 | "version": "1.1.4",
1369 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
1370 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
1371 | },
1372 | "colors": {
1373 | "version": "1.4.0",
1374 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
1375 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
1376 | },
1377 | "concurrently": {
1378 | "version": "7.0.0",
1379 | "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.0.0.tgz",
1380 | "integrity": "sha512-WKM7PUsI8wyXpF80H+zjHP32fsgsHNQfPLw/e70Z5dYkV7hF+rf8q3D+ScWJIEr57CpkO3OWBko6hwhQLPR8Pw==",
1381 | "requires": {
1382 | "chalk": "^4.1.0",
1383 | "date-fns": "^2.16.1",
1384 | "lodash": "^4.17.21",
1385 | "rxjs": "^6.6.3",
1386 | "spawn-command": "^0.0.2-1",
1387 | "supports-color": "^8.1.0",
1388 | "tree-kill": "^1.2.2",
1389 | "yargs": "^16.2.0"
1390 | }
1391 | },
1392 | "content-disposition": {
1393 | "version": "0.5.4",
1394 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
1395 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
1396 | "requires": {
1397 | "safe-buffer": "5.2.1"
1398 | }
1399 | },
1400 | "content-type": {
1401 | "version": "1.0.4",
1402 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
1403 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
1404 | },
1405 | "cookie": {
1406 | "version": "0.4.2",
1407 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
1408 | "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
1409 | },
1410 | "cookie-signature": {
1411 | "version": "1.0.6",
1412 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
1413 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
1414 | },
1415 | "date-fns": {
1416 | "version": "2.28.0",
1417 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
1418 | "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw=="
1419 | },
1420 | "debug": {
1421 | "version": "2.6.9",
1422 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
1423 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
1424 | "requires": {
1425 | "ms": "2.0.0"
1426 | }
1427 | },
1428 | "denque": {
1429 | "version": "2.0.1",
1430 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
1431 | "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ=="
1432 | },
1433 | "depd": {
1434 | "version": "1.1.2",
1435 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
1436 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
1437 | },
1438 | "destroy": {
1439 | "version": "1.0.4",
1440 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
1441 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
1442 | },
1443 | "dotenv": {
1444 | "version": "16.0.0",
1445 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz",
1446 | "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q=="
1447 | },
1448 | "ecdsa-sig-formatter": {
1449 | "version": "1.0.11",
1450 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
1451 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
1452 | "requires": {
1453 | "safe-buffer": "^5.0.1"
1454 | }
1455 | },
1456 | "ee-first": {
1457 | "version": "1.1.1",
1458 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
1459 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
1460 | },
1461 | "emoji-regex": {
1462 | "version": "8.0.0",
1463 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
1464 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
1465 | },
1466 | "encodeurl": {
1467 | "version": "1.0.2",
1468 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
1469 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
1470 | },
1471 | "escalade": {
1472 | "version": "3.1.1",
1473 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
1474 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
1475 | },
1476 | "escape-html": {
1477 | "version": "1.0.3",
1478 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
1479 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
1480 | },
1481 | "etag": {
1482 | "version": "1.8.1",
1483 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
1484 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
1485 | },
1486 | "express": {
1487 | "version": "4.17.3",
1488 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz",
1489 | "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==",
1490 | "requires": {
1491 | "accepts": "~1.3.8",
1492 | "array-flatten": "1.1.1",
1493 | "body-parser": "1.19.2",
1494 | "content-disposition": "0.5.4",
1495 | "content-type": "~1.0.4",
1496 | "cookie": "0.4.2",
1497 | "cookie-signature": "1.0.6",
1498 | "debug": "2.6.9",
1499 | "depd": "~1.1.2",
1500 | "encodeurl": "~1.0.2",
1501 | "escape-html": "~1.0.3",
1502 | "etag": "~1.8.1",
1503 | "finalhandler": "~1.1.2",
1504 | "fresh": "0.5.2",
1505 | "merge-descriptors": "1.0.1",
1506 | "methods": "~1.1.2",
1507 | "on-finished": "~2.3.0",
1508 | "parseurl": "~1.3.3",
1509 | "path-to-regexp": "0.1.7",
1510 | "proxy-addr": "~2.0.7",
1511 | "qs": "6.9.7",
1512 | "range-parser": "~1.2.1",
1513 | "safe-buffer": "5.2.1",
1514 | "send": "0.17.2",
1515 | "serve-static": "1.14.2",
1516 | "setprototypeof": "1.2.0",
1517 | "statuses": "~1.5.0",
1518 | "type-is": "~1.6.18",
1519 | "utils-merge": "1.0.1",
1520 | "vary": "~1.1.2"
1521 | }
1522 | },
1523 | "express-async-handler": {
1524 | "version": "1.2.0",
1525 | "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz",
1526 | "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==",
1527 | "dev": true
1528 | },
1529 | "finalhandler": {
1530 | "version": "1.1.2",
1531 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
1532 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
1533 | "requires": {
1534 | "debug": "2.6.9",
1535 | "encodeurl": "~1.0.2",
1536 | "escape-html": "~1.0.3",
1537 | "on-finished": "~2.3.0",
1538 | "parseurl": "~1.3.3",
1539 | "statuses": "~1.5.0",
1540 | "unpipe": "~1.0.0"
1541 | }
1542 | },
1543 | "forwarded": {
1544 | "version": "0.2.0",
1545 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
1546 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
1547 | },
1548 | "fresh": {
1549 | "version": "0.5.2",
1550 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
1551 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
1552 | },
1553 | "get-caller-file": {
1554 | "version": "2.0.5",
1555 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
1556 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
1557 | },
1558 | "has-flag": {
1559 | "version": "4.0.0",
1560 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
1561 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
1562 | },
1563 | "http-errors": {
1564 | "version": "1.8.1",
1565 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
1566 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
1567 | "requires": {
1568 | "depd": "~1.1.2",
1569 | "inherits": "2.0.4",
1570 | "setprototypeof": "1.2.0",
1571 | "statuses": ">= 1.5.0 < 2",
1572 | "toidentifier": "1.0.1"
1573 | }
1574 | },
1575 | "iconv-lite": {
1576 | "version": "0.4.24",
1577 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
1578 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
1579 | "requires": {
1580 | "safer-buffer": ">= 2.1.2 < 3"
1581 | }
1582 | },
1583 | "ieee754": {
1584 | "version": "1.2.1",
1585 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
1586 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
1587 | },
1588 | "inherits": {
1589 | "version": "2.0.4",
1590 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1591 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
1592 | },
1593 | "ip": {
1594 | "version": "1.1.5",
1595 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
1596 | "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
1597 | },
1598 | "ipaddr.js": {
1599 | "version": "1.9.1",
1600 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
1601 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
1602 | },
1603 | "is-fullwidth-code-point": {
1604 | "version": "3.0.0",
1605 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
1606 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
1607 | },
1608 | "jsonwebtoken": {
1609 | "version": "8.5.1",
1610 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
1611 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
1612 | "requires": {
1613 | "jws": "^3.2.2",
1614 | "lodash.includes": "^4.3.0",
1615 | "lodash.isboolean": "^3.0.3",
1616 | "lodash.isinteger": "^4.0.4",
1617 | "lodash.isnumber": "^3.0.3",
1618 | "lodash.isplainobject": "^4.0.6",
1619 | "lodash.isstring": "^4.0.1",
1620 | "lodash.once": "^4.0.0",
1621 | "ms": "^2.1.1",
1622 | "semver": "^5.6.0"
1623 | },
1624 | "dependencies": {
1625 | "ms": {
1626 | "version": "2.1.3",
1627 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1628 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1629 | }
1630 | }
1631 | },
1632 | "jwa": {
1633 | "version": "1.4.1",
1634 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
1635 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
1636 | "requires": {
1637 | "buffer-equal-constant-time": "1.0.1",
1638 | "ecdsa-sig-formatter": "1.0.11",
1639 | "safe-buffer": "^5.0.1"
1640 | }
1641 | },
1642 | "jws": {
1643 | "version": "3.2.2",
1644 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
1645 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
1646 | "requires": {
1647 | "jwa": "^1.4.1",
1648 | "safe-buffer": "^5.0.1"
1649 | }
1650 | },
1651 | "kareem": {
1652 | "version": "2.3.4",
1653 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.4.tgz",
1654 | "integrity": "sha512-Vcrt8lcpVl0s8ePx634BxwRqmFo+5DcOhlmNadehxreMTIQi/9hOL/B3hZQQbK5DgMS7Lem3xABXV7/S3jy+7g=="
1655 | },
1656 | "lodash": {
1657 | "version": "4.17.21",
1658 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
1659 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
1660 | },
1661 | "lodash.includes": {
1662 | "version": "4.3.0",
1663 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
1664 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
1665 | },
1666 | "lodash.isboolean": {
1667 | "version": "3.0.3",
1668 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
1669 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
1670 | },
1671 | "lodash.isinteger": {
1672 | "version": "4.0.4",
1673 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
1674 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
1675 | },
1676 | "lodash.isnumber": {
1677 | "version": "3.0.3",
1678 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
1679 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
1680 | },
1681 | "lodash.isplainobject": {
1682 | "version": "4.0.6",
1683 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
1684 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
1685 | },
1686 | "lodash.isstring": {
1687 | "version": "4.0.1",
1688 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
1689 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
1690 | },
1691 | "lodash.once": {
1692 | "version": "4.1.1",
1693 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
1694 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
1695 | },
1696 | "media-typer": {
1697 | "version": "0.3.0",
1698 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
1699 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
1700 | },
1701 | "memory-pager": {
1702 | "version": "1.5.0",
1703 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
1704 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
1705 | "optional": true
1706 | },
1707 | "merge-descriptors": {
1708 | "version": "1.0.1",
1709 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
1710 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
1711 | },
1712 | "methods": {
1713 | "version": "1.1.2",
1714 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
1715 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
1716 | },
1717 | "mime": {
1718 | "version": "1.6.0",
1719 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
1720 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
1721 | },
1722 | "mime-db": {
1723 | "version": "1.51.0",
1724 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
1725 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
1726 | },
1727 | "mime-types": {
1728 | "version": "2.1.34",
1729 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
1730 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
1731 | "requires": {
1732 | "mime-db": "1.51.0"
1733 | }
1734 | },
1735 | "mongodb": {
1736 | "version": "4.3.1",
1737 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.1.tgz",
1738 | "integrity": "sha512-sNa8APSIk+r4x31ZwctKjuPSaeKuvUeNb/fu/3B6dRM02HpEgig7hTHM8A/PJQTlxuC/KFWlDlQjhsk/S43tBg==",
1739 | "requires": {
1740 | "bson": "^4.6.1",
1741 | "denque": "^2.0.1",
1742 | "mongodb-connection-string-url": "^2.4.1",
1743 | "saslprep": "^1.0.3",
1744 | "socks": "^2.6.1"
1745 | }
1746 | },
1747 | "mongodb-connection-string-url": {
1748 | "version": "2.5.2",
1749 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz",
1750 | "integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==",
1751 | "requires": {
1752 | "@types/whatwg-url": "^8.2.1",
1753 | "whatwg-url": "^11.0.0"
1754 | }
1755 | },
1756 | "mongoose": {
1757 | "version": "6.2.3",
1758 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.2.3.tgz",
1759 | "integrity": "sha512-FxF2D0MGGIw9bAJ57nSyM4Hs4tDHbu6dn9gQwT1J/lxmRB8jfaWWJ3FSJXTmeYlQ6BpyKeIaT8fj6SAX0YMNBA==",
1760 | "requires": {
1761 | "bson": "^4.2.2",
1762 | "kareem": "2.3.4",
1763 | "mongodb": "4.3.1",
1764 | "mpath": "0.8.4",
1765 | "mquery": "4.0.2",
1766 | "ms": "2.1.3",
1767 | "sift": "16.0.0"
1768 | },
1769 | "dependencies": {
1770 | "ms": {
1771 | "version": "2.1.3",
1772 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1773 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1774 | }
1775 | }
1776 | },
1777 | "mpath": {
1778 | "version": "0.8.4",
1779 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
1780 | "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g=="
1781 | },
1782 | "mquery": {
1783 | "version": "4.0.2",
1784 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.2.tgz",
1785 | "integrity": "sha512-oAVF0Nil1mT3rxty6Zln4YiD6x6QsUWYz927jZzjMxOK2aqmhEz5JQ7xmrKK7xRFA2dwV+YaOpKU/S+vfNqKxA==",
1786 | "requires": {
1787 | "debug": "4.x"
1788 | },
1789 | "dependencies": {
1790 | "debug": {
1791 | "version": "4.3.3",
1792 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
1793 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
1794 | "requires": {
1795 | "ms": "2.1.2"
1796 | }
1797 | },
1798 | "ms": {
1799 | "version": "2.1.2",
1800 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1801 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
1802 | }
1803 | }
1804 | },
1805 | "ms": {
1806 | "version": "2.0.0",
1807 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1808 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
1809 | },
1810 | "negotiator": {
1811 | "version": "0.6.3",
1812 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
1813 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
1814 | },
1815 | "nodeman": {
1816 | "version": "1.1.2",
1817 | "resolved": "https://registry.npmjs.org/nodeman/-/nodeman-1.1.2.tgz",
1818 | "integrity": "sha1-eBKFCPdUDbv/v38QkyIuCdWOG2Q=",
1819 | "dev": true,
1820 | "requires": {
1821 | "colors": "*"
1822 | }
1823 | },
1824 | "on-finished": {
1825 | "version": "2.3.0",
1826 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
1827 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
1828 | "requires": {
1829 | "ee-first": "1.1.1"
1830 | }
1831 | },
1832 | "parseurl": {
1833 | "version": "1.3.3",
1834 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1835 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
1836 | },
1837 | "path-to-regexp": {
1838 | "version": "0.1.7",
1839 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
1840 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
1841 | },
1842 | "proxy-addr": {
1843 | "version": "2.0.7",
1844 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1845 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1846 | "requires": {
1847 | "forwarded": "0.2.0",
1848 | "ipaddr.js": "1.9.1"
1849 | }
1850 | },
1851 | "punycode": {
1852 | "version": "2.1.1",
1853 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
1854 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
1855 | },
1856 | "qs": {
1857 | "version": "6.9.7",
1858 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
1859 | "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw=="
1860 | },
1861 | "range-parser": {
1862 | "version": "1.2.1",
1863 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1864 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
1865 | },
1866 | "raw-body": {
1867 | "version": "2.4.3",
1868 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz",
1869 | "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==",
1870 | "requires": {
1871 | "bytes": "3.1.2",
1872 | "http-errors": "1.8.1",
1873 | "iconv-lite": "0.4.24",
1874 | "unpipe": "1.0.0"
1875 | }
1876 | },
1877 | "require-directory": {
1878 | "version": "2.1.1",
1879 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
1880 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
1881 | },
1882 | "rxjs": {
1883 | "version": "6.6.7",
1884 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
1885 | "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
1886 | "requires": {
1887 | "tslib": "^1.9.0"
1888 | }
1889 | },
1890 | "safe-buffer": {
1891 | "version": "5.2.1",
1892 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1893 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
1894 | },
1895 | "safer-buffer": {
1896 | "version": "2.1.2",
1897 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1898 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
1899 | },
1900 | "saslprep": {
1901 | "version": "1.0.3",
1902 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
1903 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
1904 | "optional": true,
1905 | "requires": {
1906 | "sparse-bitfield": "^3.0.3"
1907 | }
1908 | },
1909 | "semver": {
1910 | "version": "5.7.1",
1911 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
1912 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
1913 | },
1914 | "send": {
1915 | "version": "0.17.2",
1916 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
1917 | "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
1918 | "requires": {
1919 | "debug": "2.6.9",
1920 | "depd": "~1.1.2",
1921 | "destroy": "~1.0.4",
1922 | "encodeurl": "~1.0.2",
1923 | "escape-html": "~1.0.3",
1924 | "etag": "~1.8.1",
1925 | "fresh": "0.5.2",
1926 | "http-errors": "1.8.1",
1927 | "mime": "1.6.0",
1928 | "ms": "2.1.3",
1929 | "on-finished": "~2.3.0",
1930 | "range-parser": "~1.2.1",
1931 | "statuses": "~1.5.0"
1932 | },
1933 | "dependencies": {
1934 | "ms": {
1935 | "version": "2.1.3",
1936 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1937 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1938 | }
1939 | }
1940 | },
1941 | "serve-static": {
1942 | "version": "1.14.2",
1943 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
1944 | "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
1945 | "requires": {
1946 | "encodeurl": "~1.0.2",
1947 | "escape-html": "~1.0.3",
1948 | "parseurl": "~1.3.3",
1949 | "send": "0.17.2"
1950 | }
1951 | },
1952 | "setprototypeof": {
1953 | "version": "1.2.0",
1954 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1955 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1956 | },
1957 | "sift": {
1958 | "version": "16.0.0",
1959 | "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
1960 | "integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
1961 | },
1962 | "smart-buffer": {
1963 | "version": "4.2.0",
1964 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
1965 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
1966 | },
1967 | "socks": {
1968 | "version": "2.6.2",
1969 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz",
1970 | "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==",
1971 | "requires": {
1972 | "ip": "^1.1.5",
1973 | "smart-buffer": "^4.2.0"
1974 | }
1975 | },
1976 | "sparse-bitfield": {
1977 | "version": "3.0.3",
1978 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1979 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
1980 | "optional": true,
1981 | "requires": {
1982 | "memory-pager": "^1.0.2"
1983 | }
1984 | },
1985 | "spawn-command": {
1986 | "version": "0.0.2-1",
1987 | "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
1988 | "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A="
1989 | },
1990 | "statuses": {
1991 | "version": "1.5.0",
1992 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
1993 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
1994 | },
1995 | "string-width": {
1996 | "version": "4.2.3",
1997 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
1998 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
1999 | "requires": {
2000 | "emoji-regex": "^8.0.0",
2001 | "is-fullwidth-code-point": "^3.0.0",
2002 | "strip-ansi": "^6.0.1"
2003 | }
2004 | },
2005 | "strip-ansi": {
2006 | "version": "6.0.1",
2007 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
2008 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
2009 | "requires": {
2010 | "ansi-regex": "^5.0.1"
2011 | }
2012 | },
2013 | "supports-color": {
2014 | "version": "8.1.1",
2015 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
2016 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
2017 | "requires": {
2018 | "has-flag": "^4.0.0"
2019 | }
2020 | },
2021 | "toidentifier": {
2022 | "version": "1.0.1",
2023 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
2024 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
2025 | },
2026 | "tr46": {
2027 | "version": "3.0.0",
2028 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
2029 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
2030 | "requires": {
2031 | "punycode": "^2.1.1"
2032 | }
2033 | },
2034 | "tree-kill": {
2035 | "version": "1.2.2",
2036 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
2037 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="
2038 | },
2039 | "tslib": {
2040 | "version": "1.14.1",
2041 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
2042 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
2043 | },
2044 | "type-is": {
2045 | "version": "1.6.18",
2046 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
2047 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
2048 | "requires": {
2049 | "media-typer": "0.3.0",
2050 | "mime-types": "~2.1.24"
2051 | }
2052 | },
2053 | "unpipe": {
2054 | "version": "1.0.0",
2055 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
2056 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
2057 | },
2058 | "utils-merge": {
2059 | "version": "1.0.1",
2060 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
2061 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
2062 | },
2063 | "vary": {
2064 | "version": "1.1.2",
2065 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
2066 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
2067 | },
2068 | "webidl-conversions": {
2069 | "version": "7.0.0",
2070 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
2071 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
2072 | },
2073 | "whatwg-url": {
2074 | "version": "11.0.0",
2075 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
2076 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
2077 | "requires": {
2078 | "tr46": "^3.0.0",
2079 | "webidl-conversions": "^7.0.0"
2080 | }
2081 | },
2082 | "wrap-ansi": {
2083 | "version": "7.0.0",
2084 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
2085 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
2086 | "requires": {
2087 | "ansi-styles": "^4.0.0",
2088 | "string-width": "^4.1.0",
2089 | "strip-ansi": "^6.0.0"
2090 | }
2091 | },
2092 | "y18n": {
2093 | "version": "5.0.8",
2094 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
2095 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
2096 | },
2097 | "yargs": {
2098 | "version": "16.2.0",
2099 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
2100 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
2101 | "requires": {
2102 | "cliui": "^7.0.2",
2103 | "escalade": "^3.1.1",
2104 | "get-caller-file": "^2.0.5",
2105 | "require-directory": "^2.1.1",
2106 | "string-width": "^4.2.0",
2107 | "y18n": "^5.0.5",
2108 | "yargs-parser": "^20.2.2"
2109 | }
2110 | },
2111 | "yargs-parser": {
2112 | "version": "20.2.9",
2113 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
2114 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="
2115 | }
2116 | }
2117 | }
2118 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "support-desk",
3 | "version": "1.0.0",
4 | "description": "Support Ticket App",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "node backend/server.js",
8 | "server": "nodemon backend/server.js --ignore client",
9 | "client": "npm start --prefix frontend",
10 | "dev": "concurrently \"npm run server\" \"npm run client\"",
11 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix frontend && npm run build --prefix frontend"
12 | },
13 | "author": "Mohammad Arif",
14 | "license": "MIT",
15 | "dependencies": {
16 | "bcryptjs": "^2.4.3",
17 | "colors": "^1.4.0",
18 | "concurrently": "^7.0.0",
19 | "dotenv": "^16.0.0",
20 | "express": "^4.17.3",
21 | "jsonwebtoken": "^8.5.1",
22 | "express-async-handler": "^1.2.0",
23 | "mongoose": "^6.2.3"
24 | },
25 | "devDependencies": {
26 | "nodeman": "^1.1.2"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------