├── .gitignore ├── server ├── .gitignore ├── models │ └── dbModel.js ├── package.json ├── controllers │ ├── sessionController.js │ ├── userController.js │ └── eventController.js ├── server.js ├── routes │ └── api.js └── package-lock.json ├── .DS_Store ├── docs └── schema.jpg ├── client ├── src │ ├── components │ │ ├── logo.gif │ │ ├── UserContext.js │ │ ├── Login.jsx │ │ ├── App.jsx │ │ ├── MarkerCreator.jsx │ │ ├── MarkerUpdator.jsx │ │ └── Map.jsx │ ├── main.jsx │ └── stylesheets │ │ ├── index.css │ │ └── App.css ├── vite.config.js ├── .gitignore ├── package.json └── index.html ├── .eslintrc.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | coverage/ 4 | .env -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poppin-events/Poppin-Events/HEAD/.DS_Store -------------------------------------------------------------------------------- /docs/schema.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poppin-events/Poppin-Events/HEAD/docs/schema.jpg -------------------------------------------------------------------------------- /client/src/components/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poppin-events/Poppin-Events/HEAD/client/src/components/logo.gif -------------------------------------------------------------------------------- /client/src/components/UserContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const UserContext = React.createContext(null); 4 | 5 | export { UserContext }; -------------------------------------------------------------------------------- /client/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | server: { 7 | port: 3000, 8 | proxy: { 9 | '/api': 'http://localhost:5000/', 10 | }, 11 | }, 12 | plugins: [react()], 13 | }); 14 | -------------------------------------------------------------------------------- /client/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './stylesheets/index.css'; 4 | import App from './components/App'; 5 | import { BrowserRouter } from "react-router-dom"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root'), 12 | ); 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | 'plugin:react/recommended', 8 | 'airbnb', 9 | ], 10 | overrides: [ 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest', 14 | sourceType: 'module', 15 | }, 16 | plugins: [ 17 | 'react', 18 | ], 19 | rules: { 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | pnpm-debug.log* 10 | lerna-debug.log* 11 | 12 | node_modules 13 | dist 14 | dist-ssr 15 | *.local 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /client/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "concurrently": "^7.6.0" 4 | }, 5 | "scripts": { 6 | "start": "concurrently \"cd ./server && npm run server\" \"cd ./client && npm run dev\"" 7 | }, 8 | "devDependencies": { 9 | "eslint": "^8.35.0", 10 | "eslint-config-airbnb": "^19.0.4", 11 | "eslint-plugin-import": "^2.27.5", 12 | "eslint-plugin-jsx-a11y": "^6.7.1", 13 | "eslint-plugin-react": "^7.32.2", 14 | "eslint-plugin-react-hooks": "^4.6.0", 15 | "nodemon": "^2.0.21" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /server/models/dbModel.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | require('dotenv').config(); 3 | 4 | // put your PG_URI here 5 | const PG_URI = process.env.PG_URI; 6 | // 'postgres://leekwldv:85zQjHG1-BdoK3wOIfWOGl63DL85OW9c@mahmud.db.elephantsql.com/leekwldv' 7 | // create new pool 8 | const pool = new Pool({ 9 | connectionString: PG_URI, 10 | }); 11 | 12 | module.exports = { 13 | query: (text, params, callback) => { 14 | console.log('executing query', text, 'with params: ', params); 15 | return pool.query(text, params, callback); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js", 9 | "server": "nodemon server.js" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "cookie-parser": "^1.4.6", 16 | "express": "^4.18.2", 17 | "express-session": "^1.17.3", 18 | "google-auth-library": "^8.7.0", 19 | "jwt-decode": "^3.1.2", 20 | "pg": "^8.9.0" 21 | }, 22 | "devDependencies": { 23 | "dotenv": "^16.0.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@react-google-maps/api": "^2.18.1", 12 | "@react-oauth/google": "^0.8.0", 13 | "axios": "^1.3.4", 14 | "jwt-decode": "^3.1.2", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-dotenv": "^0.1.3", 18 | "react-google-autocomplete": "^2.7.3", 19 | "react-icons": "^4.8.0", 20 | "react-router-dom": "^6.8.2", 21 | "react-scripts": "^5.0.1" 22 | }, 23 | "devDependencies": { 24 | "@vitejs/plugin-react": "^3.1.0", 25 | "vite": "^4.1.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/controllers/sessionController.js: -------------------------------------------------------------------------------- 1 | const sessionController = {}; 2 | 3 | sessionController.validateSession = (req, res, next) => { 4 | console.log('validating session...'); 5 | console.log('req.session is currently: ', req.session); 6 | if (req.session.loggedIn) { 7 | res.locals = { 8 | name: req.session.name, 9 | email: req.session.email, 10 | picture: req.session.picture, 11 | loggedIn: req.session.loggedIn, 12 | id: req.session.userID, 13 | }; 14 | } else { 15 | res.locals.loggedIn = false; 16 | } 17 | next(); 18 | }; 19 | sessionController.deleteSession = (req, res, next) => { 20 | console.log('deleting session...'); 21 | req.session.destroy(e => console.log('LOLLLLLLLLLLLLLLLLLLLLLL', e)); 22 | next(); 23 | }; 24 | 25 | module.exports = sessionController; 26 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | Vite App 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | // require in dependencies 2 | const path = require('path'); 3 | const express = require('express'); 4 | const session = require('express-session'); 5 | const apiRouter = require('./routes/api'); 6 | 7 | // require in routes 8 | 9 | const PORT = 5000; 10 | const app = express(); 11 | 12 | // request parsing (if needed) 13 | app.use(express.json()); 14 | app.use(express.urlencoded({ extended: true })); 15 | app.use(session({ 16 | secret: 'af168f987f1gh78fhg91f', 17 | name: 'ssid', 18 | saveUninitialized: false, 19 | })); 20 | // handle requests for static files 21 | app.use(express.static(path.resolve(__dirname, '../dist'))); 22 | // route handlers 23 | 24 | app.use('/api', apiRouter); 25 | 26 | // catch-all 404 route handler 27 | app.use((_, res) => res.status(404).send('Page Not Found')); 28 | 29 | // error handler 30 | app.use((err, req, res, next) => { 31 | const defaultErr = { 32 | log: 'Express error handler caught unknown middleware error', 33 | status: 500, 34 | message: { err: 'An error occurred' }, 35 | }; 36 | const errorObj = { ...defaultErr, ...err }; 37 | console.log(errorObj.log); 38 | return res.status(errorObj.status).json(errorObj.message).redirect('/'); 39 | }); 40 | 41 | // start server 42 | app.listen(PORT, () => console.log(`start listening on port : ${PORT}`)); 43 | 44 | module.exports = app; 45 | -------------------------------------------------------------------------------- /server/routes/api.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const userController = require('../controllers/userController'); 3 | const eventController = require('../controllers/eventController'); 4 | const sessionController = require('../controllers/sessionController'); 5 | 6 | const router = express.Router(); 7 | 8 | // Responds with user info (location + events) when passed in the correct google id 9 | // router.get( 10 | // '/user/:id', 11 | // userController.getUser, 12 | // (req, res) => res.status(200).json(res.locals.user), 13 | // ); 14 | 15 | // Log in or a new user in the database 16 | router.post( 17 | '/users', 18 | userController.login, 19 | (req, res) => res.status(200).json(res.locals.id), 20 | ); 21 | 22 | // Responds with all events in the database (Name, Location, Date, Description, Created By) 23 | router.get( 24 | '/events', 25 | eventController.getEvents, 26 | (req, res) => res.status(200).json(res.locals.events), 27 | ); 28 | 29 | // Create an event in the database 30 | router.post( 31 | '/events', 32 | eventController.createEvent, 33 | (req, res) => res.status(200).json(res.locals.id), 34 | ); 35 | 36 | // Update an event in the database 37 | router.put( 38 | '/events', 39 | eventController.updateEvent, 40 | (req, res) => res.sendStatus(200), 41 | ); 42 | 43 | // Delete an event in the database 44 | router.delete( 45 | '/events', 46 | eventController.deleteEvent, 47 | (req, res) => res.sendStatus(200), 48 | ); 49 | 50 | // Checks for active sessions 51 | router.get( 52 | '/sessions', 53 | sessionController.validateSession, 54 | (req, res) => res.status(200).send(res.locals), 55 | ); 56 | router.delete( 57 | '/sessions', 58 | sessionController.deleteSession, 59 | (req, res) => res.sendStatus(200), 60 | ); 61 | 62 | module.exports = router; 63 | -------------------------------------------------------------------------------- /client/src/components/Login.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/function-component-definition */ 2 | import React, {useState, useContext} from 'react'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import { FcGoogle } from 'react-icons/fc'; 5 | import { GoogleOAuthProvider } from '@react-oauth/google'; 6 | import { GoogleLogin } from '@react-oauth/google'; 7 | import jwt_decode from "jwt-decode"; 8 | import env from 'react-dotenv'; 9 | import { UserContext } from './UserContext'; 10 | import axios from 'axios'; 11 | 12 | const Login = (props) => { 13 | const navigate = useNavigate(); 14 | 15 | const responseGoogle = async (response) => { 16 | // the google oauth (identity services) api responds with a JWT with all user info 17 | const userObject = jwt_decode(response.credential); 18 | // destructure that info for our purposes 19 | const { name, email, picture } = userObject; 20 | try { 21 | const res = await axios.post('/api/users', { 22 | name, email, picture, 23 | }); 24 | // reroute to map 25 | if (res.status === 200){ 26 | props.setUser({ name, email, picture, id: res.data}); 27 | navigate('/map'); 28 | } 29 | } catch (e) { 30 | console.log('error in post: ', e.message); 31 | } 32 | }; 33 | 34 | return ( 35 |
36 | 37 | ( 39 | 47 | )} 48 | onSuccess={responseGoogle} 49 | onFailure={responseGoogle} 50 | cookiePolicy="single_host_origin" 51 | size="medium" 52 | /> 53 | 54 |
55 | ) 56 | } 57 | 58 | export default Login -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | // import the user model 2 | const jwt_decode = require('jwt-decode'); // ended up only using jwt_decode on front end 3 | const db = require('../models/dbModel'); 4 | 5 | const userController = {}; 6 | 7 | // get user info from database 8 | userController.login = async (req, res, next) => { 9 | try { 10 | // console.log('req body in userController.login', req.body); 11 | const { name, email, picture } = req.body; 12 | const query = `SELECT * FROM users WHERE email = '${email}'`; 13 | const user = await db.query(query); 14 | // if the user does not exist in the database, create them 15 | const userVals = [name, email, picture]; 16 | if (user.rows.length) res.locals.id = user.rows[0].id; 17 | if (user.rows.length === 0) { 18 | const createUsr = 'INSERT INTO users (name, email, picture) VALUES ($1, $2, $3) RETURNING id'; 19 | const newUser = await db.query(createUsr, userVals); 20 | res.locals.id = newUser.rows[0]; 21 | } else if (user.rows[0].picture !== picture) { 22 | const updatePic = 'UPDATE users SET picture = $2 WHERE email = $1;'; 23 | await db.query(updatePic, userVals.slice(1)); 24 | } 25 | req.session.loggedIn = true; 26 | req.session.email = email; 27 | req.session.name = name; 28 | req.session.picture = picture; 29 | req.session.userID = res.locals.id; 30 | return next(); 31 | } catch (e) { 32 | return next({ 33 | log: 'Error in userController.login', 34 | status: 500, 35 | message: { error: e.message }, 36 | }); 37 | } 38 | }; 39 | 40 | // get user info from database 41 | userController.getUser = async (req, res, next) => { 42 | const { email } = req.params; 43 | const query = `SELECT * FROM users WHERE email = ${email}`; 44 | const value = [id]; 45 | const user = await db.query(query, value); 46 | // use array destructuring to get the first element of the array 47 | [res.locals.user] = user.rows; 48 | return next(); 49 | }; 50 | 51 | // create a new user in the database 52 | // userController.createUser = async (req, res, next) => { 53 | 54 | // } 55 | 56 | module.exports = userController; 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Poppin-Events 2 | An app that allows users to view events in their area, create new events, and edit/delete their own events from the map 3 | 4 | # To future archaeologists: 5 | We used Vite! Enjoy (here is the starter video for structure: https://www.youtube.com/watch?v=PPjpHaLkV7A) 6 | 7 | Note: You'll have to npm i for the root directory, the client, and the server separately 8 | 9 | Run 'npm start' in root directory to concurrently run server(5000) and client(3000) 10 | 11 | Npm install your frontend dependencies in client folder, backend dependencies in server folder 12 | 13 | If your server is running on 3001, it won't work, so shut it down and killall node before starting again 14 | 15 | Some important considerations - things you'll have to create and set up for yourself: 16 | 17 | 1 - GoogleAPI key for OAuth and Maps (make a .env file in the client directory with key value pairs that correspond with constants that are imported like import.meta.env.VITE_GOOGLE_OATH_CLIENT_ID or import.meta.env.VITE_GOOGLE_OATH_CLIENT_ID) 18 | 19 | - You will need to sign up via Google Cloud Platform to create your own application to get your own Client ID and Secret for Oauth, and same thing for Google Maps 20 | 21 | 2 - SQL server URI (make a .env file in the server directory with PG_URI='') 22 | 23 | SQL server Schema: 24 | ![pg_schema](/docs/schema.jpg) 25 | 26 | Must swap in your own via .evs: 27 | 28 | server/.env => swap out PG_URI (call with "process.env.PG_URI") 29 | 30 | client/.env => swap out GOOGLE_API_KEY (call with "import.meta.env.VITE_GOOGLE_OATH_CLIENT_ID") 31 | 32 | client/.env => swap out GOOGLE_OATH_CLIENT_ID (call with "import.meta.env.VITE_GOOGLE_OATH_CLIENT_ID") 33 | 34 | Unimplemented features with frameworks: 35 | 36 | 1 - display pictures next to the organizer's name in the event display: Currently, we get, store, and update user picture urls, but they are unused 37 | 38 | 2 - have an RSVP button in event display instead of organizer email that creates a new entry on the attendees table in the SQL database, linking the user to the respective event. We have a schema for using Attendees as an association table, but it is unused currently. Maybe also add an attendee list to the event info panel from there. 39 | 40 | Possible Refactors: 41 | 42 | 1 - Possibly display event boxes on map if you want, instead of to the side 43 | 44 | 2 - Refactor use of .id for both events and users to be userID / eventID on the front-end 45 | 46 | Bugs to squash: 47 | 48 | 1 - Newly created events don't have their time populate correctly on edit without a full page reload - likely, the date isn't being saved in state in a way the datetime-local input wants to receive 49 | -------------------------------------------------------------------------------- /client/src/components/App.jsx: -------------------------------------------------------------------------------- 1 | import '../stylesheets/App.css'; 2 | import axios from 'axios'; 3 | import Map from './Map'; 4 | import React, { useState, useEffect } from 'react'; 5 | import jwt_decode from 'jwt-decode'; 6 | import Login from './Login'; 7 | import { UserContext } from './UserContext'; 8 | import {Routes, Route, useNavigate} from 'react-router-dom'; 9 | 10 | 11 | function App(props) { 12 | const navigate = useNavigate(); 13 | const [user, setUser] = useState(null); 14 | console.log('in APP, user is: ', user); 15 | 16 | useEffect(() => { 17 | console.log('in useEffect, and user is: ', user); 18 | const checkSession = async () => { 19 | try { 20 | const userInfo = await axios.get('/api/sessions'); 21 | console.log('user info is: ', userInfo); 22 | if (userInfo.data.loggedIn === true) { 23 | setUser({ 24 | name: userInfo.data.name, 25 | email: userInfo.data.email, 26 | picture: userInfo.data.picture, 27 | // for later: refactor to be userID instead of id 28 | id: userInfo.data.id, 29 | }); 30 | navigate('/map'); 31 | } 32 | else navigate('login'); 33 | } catch (e) { 34 | console.log('Error in checkSession: ', e.message); 35 | } 36 | }; 37 | if (user === null) { 38 | console.log('user is null'); 39 | checkSession(); 40 | } 41 | else { 42 | console.log('user is not null'); 43 | navigate('/map'); 44 | } 45 | }, [user]); 46 | 47 | const logout = async () => { 48 | // make server request to logout / destroy session + cookie 49 | try { 50 | const response = await axios.delete('/api/sessions'); 51 | console.log('successful logout'); 52 | setUser(null); 53 | } catch (e) { 54 | console.log('error logging out: ', e.message); 55 | } 56 | } 57 | 58 | 59 | return ( 60 | <> 61 | 73 | 74 | 75 | 76 | setUser(u)} 79 | setUserJWT={(jwt) => setUserJWT(jwt)}>} 80 | /> 81 | you are on path= /

} 84 | /> 85 | } 88 | /> 89 |
90 |
91 | 92 | ); 93 | } 94 | 95 | export default App; 96 | -------------------------------------------------------------------------------- /server/controllers/eventController.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | // import the event model 3 | const db = require('../models/dbModel'); 4 | 5 | const eventController = {}; 6 | 7 | // get all events from database 8 | eventController.getEvents = async (req, res, next) => { 9 | try { 10 | // select event information, using jsonb_agg to create a json object out of lat and lng by declaring key/value pairs 11 | const query = await db.query('SELECT e.id, e.name, e.description, e.date, e.loc_name AS locName, e.address, jsonb_agg(json_build_object(\'lat\', e.lat, \'lng\', e.lng)) AS location, u.name AS organizer, u.email, u.picture FROM events e LEFT OUTER JOIN users u ON e.organizer_id = u.id group by e.id, u.name, u.email, u.picture'); 12 | res.locals.events = query.rows; 13 | // query shape: {something: x, rows:[{data}, {data2}], blah: y, ....} 14 | return next(); 15 | } catch (error) { 16 | return next({ 17 | log: 'eventController.getEvents error', 18 | message: { err: 'Error getting events from database' }, 19 | }); 20 | } 21 | }; 22 | 23 | // create a new event in the database 24 | eventController.createEvent = async (req, res, next) => { 25 | try { 26 | console.log('in event creator with req: ', req.body); 27 | const { name, description, date, locName, address, userID } = req.body; 28 | const { lat, lng } = req.body.location[0]; 29 | // insert the event into the database using a subquery for the organizer id 30 | const addEventQuery = 'INSERT INTO events (name, description, date, loc_name, address, lat, lng, organizer_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id'; 31 | const newEventVals = [name, description, date, locName, address, lat, lng, userID]; 32 | const newEvent = await db.query(addEventQuery, newEventVals); 33 | // **note - that rows[0] will actually be an OBJECT containing {id: } ** ! 34 | res.locals.id = newEvent.rows[0]; 35 | return next(); 36 | } catch (error) { 37 | return next({ 38 | log: 'eventController.createEvent error', 39 | message: { err: 'Error creating event in database' }, 40 | }); 41 | } 42 | }; 43 | 44 | // update an event in the database 45 | eventController.updateEvent = async (req, res, next) => { 46 | const { 47 | name, description, date, locName, address, userID, eventID, 48 | } = req.body; 49 | const { lat, lng } = req.body.location[0]; 50 | const values = [name, description, date, locName, address, lat, lng, userID, eventID]; 51 | const text = 'UPDATE events SET name = $1, description = $2, date = $3, loc_name = $4, address = $5, lat = $6, lng = $7 WHERE organizer_id = $8 AND id = $9;'; 52 | try { 53 | await db.query(text, values); 54 | return next(); 55 | } catch (error) { 56 | return next({ 57 | log: 'eventController.updateEvent error', 58 | message: { err: 'Error updating event in database' }, 59 | }); 60 | } 61 | }; 62 | 63 | // delete an event from the database 64 | eventController.deleteEvent = async (req, res, next) => { 65 | const { eventID, userID } = req.body.deleteReq; 66 | const values = [eventID, userID]; 67 | const text = 'DELETE FROM events WHERE id = $1 AND organizer_id = $2'; 68 | try { 69 | await db.query(text, values); 70 | return next(); 71 | } catch (error) { 72 | return next({ 73 | log: 'eventController.deleteEvent error', 74 | message: { err: 'Error deleting event from database' }, 75 | }); 76 | } 77 | }; 78 | 79 | module.exports = eventController; 80 | -------------------------------------------------------------------------------- /client/src/components/MarkerCreator.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useRef} from 'react'; 2 | import axios from 'axios'; 3 | import { useJsApiLoader } from '@react-google-maps/api'; 4 | import { UserContext } from './UserContext'; 5 | import Autocomplete from 'react-google-autocomplete'; 6 | 7 | const libraries = ['places']; 8 | export default function MarkerCreator(props) { 9 | const { isLoaded } = useJsApiLoader({ 10 | googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY, 11 | libraries, 12 | }); 13 | 14 | const { user } = useContext(UserContext); 15 | // state for controlled inputs 16 | const [name, setName] = useState(''); 17 | const [address, setAddress] = useState(''); 18 | const [date, setDate] = useState(''); 19 | const [description, setDescription] = useState(''); 20 | const [locName, setLocName] = useState(''); 21 | let autocomplete = null; 22 | 23 | // submit handler 24 | const handleSubmit = async (e) => { 25 | e.preventDefault(); 26 | try { 27 | console.log('in MARKER CREATOR user is: ', user.id); 28 | const { id, email, name: username } = user; 29 | // new event object for database 30 | const event = { 31 | name, 32 | address, 33 | locName, 34 | date, 35 | description, 36 | userID: id, 37 | }; 38 | // encode the address 39 | const encoded = address.replaceAll(' ', '+'); 40 | // geocode the address (https://developers.google.com/maps/documentation/geocoding/requests-geocoding) 41 | const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encoded}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY}`; 42 | const response = await axios.get(url); 43 | const data = response.data.results[0]; 44 | event.location = [{ 45 | lat: data.geometry.location.lat, 46 | lng: data.geometry.location.lng, 47 | }]; 48 | // send the post request to the server 49 | const eventID = await axios.post('/api/events', event); 50 | // add other pairs to the event object for the front-end to read 51 | event.id = eventID.data.id; 52 | event.email = email; 53 | event.organizer = username; 54 | // add the new event into state (from parent component) to rerender the map + markers 55 | props.setMarkerData(prevMarkerData => { 56 | return [...prevMarkerData, event]; 57 | }); 58 | } catch (err) { 59 | console.log('error in post: ', err.message); 60 | } 61 | }; 62 | 63 | // autocomplete onLoad 64 | function onLoad(ac) { 65 | console.log('here in ONLOAD, ac is: ', ac); 66 | autocomplete = ac; 67 | } 68 | 69 | // autocomplete change handler 70 | function handleChange() { 71 | console.log('autocomplete is currently: ', autocomplete); 72 | if(autocomplete !== null) console.log('autocomplete place is: ', autocomplete.getPlace()); 73 | } 74 | 75 | // component imported from @react-google-maps/api to have autocomplete address 76 | return ( 77 |
78 |

Create an Event

79 |
80 | 83 | setName(e.target.value)} value={name} required /> 84 | 87 | setDescription(e.target.value)} value={description} required /> 88 | 91 | setLocName(e.target.value)} value={locName} required /> 92 | 95 | { 99 | console.log('PLACE in autocomplete IS: ', place); 100 | setAddress(place.formatted_address); 101 | }} 102 | /> 103 | 106 | setDate(e.target.value)} value={date} required /> 107 | 108 | 109 |
110 | ); 111 | } 112 | -------------------------------------------------------------------------------- /client/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-family: 'Poppins', sans-serif; 6 | padding-top: 80px; 7 | background-image: url('https://img.freepik.com/free-vector/hand-painted-watercolor-pastel-sky-background_23-2148902028.jpg?w=1800&t=st=1678152616~exp=1678153216~hmac=74aded69431c66906454ac1c5b72d32320d4b96356014ce1c1940c26d3ac63e0'); 8 | background-size: cover; 9 | } 10 | 11 | button, input { 12 | font-family: 'Poppins', sans-serif; 13 | } 14 | 15 | button { 16 | cursor: pointer; 17 | } 18 | .edit-button, .delete-button { 19 | width: 100px; 20 | height: 30px; 21 | } 22 | .edit-button { 23 | margin-right: 10px; 24 | } 25 | #root { 26 | width: 100%; 27 | display: flex; 28 | flex-direction: column; 29 | align-items: center; 30 | } 31 | 32 | * { 33 | /* outline: 1px solid lime; */ 34 | } 35 | 36 | .info-list { 37 | list-style: none; 38 | } 39 | 40 | .info-container { 41 | /* outline: 2px solid black; */ 42 | display: flex; 43 | flex-direction: column; 44 | align-items: center; 45 | height: min-content; 46 | text-align: center; 47 | padding: 20px; 48 | min-width: 300px; 49 | margin-top: 20px; 50 | } 51 | .event-title { 52 | margin-top: -4px; 53 | } 54 | .event-description { 55 | font-style: italic; 56 | margin: -3px 0; 57 | } 58 | .map-container { 59 | 60 | min-width: 700px; 61 | min-height: 700px; 62 | margin: 0 30px; 63 | } 64 | 65 | .box-shadow-1 { 66 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), -1px 6px 9px rgba(0, 0, 0, 0.24); 67 | transition: all 0.3s cubic-bezier(.25, .8, .25, 1); 68 | } 69 | 70 | .box-shadow-1:hover { 71 | box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22); 72 | } 73 | 74 | .create-event-container { 75 | text-align: center; 76 | /* outline: 1px solid gray; */ 77 | height: min-content; 78 | padding: 20px; 79 | padding-top: 10px; 80 | border-radius: 5px; 81 | width: 300px; 82 | 83 | } 84 | 85 | .button-primary { 86 | padding: 12px 0; 87 | width: 50%; 88 | margin-top: 5px; 89 | } 90 | 91 | .create-form { 92 | display: flex; 93 | flex-direction: column; 94 | /* width: 300px; */ 95 | /* outline: 1px solid gray; */ 96 | align-items: center; 97 | } 98 | 99 | .create-form input { 100 | border: none; 101 | outline: none; 102 | border-bottom: 1px solid rgb(126, 126, 126); 103 | text-align: center; 104 | margin-bottom: 18px; 105 | font-size: 15px; 106 | width: 85%; 107 | background: none; 108 | } 109 | 110 | 111 | 112 | .brand-container { 113 | display: flex; 114 | justify-content: center; 115 | align-items: center; 116 | } 117 | 118 | .brand-logo { 119 | width: 40px; 120 | } 121 | 122 | /* .autocomplete-container { 123 | display: inline-block; 124 | width: 100%; 125 | } */ 126 | .autocomplete-container { 127 | /* outline: 2px solid red; */ 128 | /* background: blue; */ 129 | width: 100%; 130 | } 131 | .autocomplete-container .autocomplete-input { 132 | /* background: red; */ 133 | /* width: 98%; 134 | display: inline-block; */ 135 | 136 | } 137 | 138 | 139 | 140 | .screen-reader-text { 141 | border: 0; 142 | clip: rect(1px, 1px, 1px, 1px); 143 | clip-path: inset(50%); 144 | height: 1px; 145 | margin: -1px; 146 | width: 1px; 147 | overflow: hidden; 148 | position: absolute !important; 149 | word-wrap: normal !important; 150 | } 151 | 152 | 153 | 154 | .navbar { 155 | background: rgb(234, 248, 255); 156 | opacity: .5; 157 | position: fixed; 158 | top: 0; 159 | width: 100%; 160 | display: flex; 161 | justify-content: space-between; 162 | align-items: center; 163 | z-index: 200; 164 | height: 60px; 165 | } 166 | 167 | .nav-list { 168 | display: flex; 169 | list-style: none; 170 | } 171 | .nav-list li{ 172 | margin-right: 20px; 173 | } 174 | 175 | a { 176 | cursor: pointer; 177 | } 178 | 179 | a:hover { 180 | text-decoration: underline; 181 | } 182 | 183 | .brand-heading { 184 | margin-left: 20px; 185 | display: inline-block; 186 | font-style: italic; 187 | /* text-decoration: underline; */ 188 | font-weight: 800; 189 | font-size: 28px; 190 | color: rgb(136, 54, 244); 191 | } 192 | 193 | .map-section { 194 | /* outline: 2px solid gold; */ 195 | display: flex; 196 | width: 80%; 197 | 198 | } 199 | 200 | 201 | 202 | .current-location-button { 203 | position: absolute; 204 | top: 9px; 205 | left: 50%; 206 | transform: translateX(-50%); 207 | padding: 10px; 208 | } 209 | 210 | button { 211 | background: none; 212 | border-radius: 3px; 213 | border: 1px solid rgb(126, 126, 126); 214 | transition: .15s ease; 215 | } 216 | button:hover { 217 | background: rgba(246, 255, 238, 0.853); 218 | } 219 | 220 | .delete-button:hover, .cancel-button:hover{ 221 | background: rgba(255, 209, 220, 0.453); 222 | } -------------------------------------------------------------------------------- /client/src/components/MarkerUpdator.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import axios from 'axios'; 3 | import { useJsApiLoader } from '@react-google-maps/api'; 4 | import { UserContext } from './UserContext'; 5 | import Autocomplete from 'react-google-autocomplete'; 6 | 7 | const libraries = ['places']; 8 | export default function MarkerUpdator(props) { 9 | const { isLoaded } = useJsApiLoader({ 10 | googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY, 11 | libraries, 12 | }); 13 | 14 | const { user } = useContext(UserContext); 15 | 16 | const [name, setName] = useState(props.eventData.name); 17 | const [address, setAddress] = useState(props.eventData.address); 18 | const [date, setDate] = useState(props.eventData.date.slice(0,props.eventData.date.length-8)); 19 | const [description, setDescription] = useState(props.eventData.description); 20 | const [locName, setLocName] = useState(props.eventData.locName ? props.eventData.locName : props.eventData.locname); 21 | let autocomplete = null; 22 | console.log(props.eventData); 23 | 24 | // "2023-03-22T20:21:00.000Z" database format 25 | // 2023-03-22T20:21 date format 26 | // Wed, 22 Mar 2023 20:21:00 GMT 27 | // props.eventData.date.slice(0,props.eventData.date.length-8) 28 | // graveyard of broken souls 29 | 30 | // "023-03-22T20:21" 31 | // cancel handler 32 | const cancelHandler = () => { 33 | props.setUpdating(false); 34 | } 35 | 36 | // submit handler 37 | const handleSubmit = async (e) => { 38 | e.preventDefault(); 39 | try { 40 | console.log('in MARKER CREATOR user is: ', user.id); 41 | const { id, email, name: username } = user; 42 | // event object for the database 43 | const event = { 44 | name, 45 | address, 46 | locName, 47 | date, 48 | description, 49 | userID: id, 50 | eventID: props.eventData.id 51 | }; 52 | // encode the address and geocode it 53 | const encoded = address.replaceAll(' ', '+'); 54 | const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encoded}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY}`; 55 | const response = await axios.get(url); 56 | const data = response.data.results[0]; 57 | event.location = [{ 58 | lat: data.geometry.location.lat, 59 | lng: data.geometry.location.lng, 60 | }]; 61 | // send the update request to the database 62 | const eventID = await axios.put('/api/events', event); 63 | event.eventID = eventID.data; 64 | event.email = email; 65 | event.organizer = username; 66 | // replace the MarkerData in state with the updated array 67 | props.setMarkerData(prevMarkerData => { 68 | // remove the edited event 69 | // could make more performant with map instead of filter 70 | const updatedMarkers = prevMarkerData.filter(event => { 71 | return event.id !== event.eventID; 72 | }); 73 | // spread in the filtered old events with the new event added in 74 | return [...updatedMarkers, event]; 75 | }); 76 | // update window closes and is replaced with add event 77 | props.setUpdating(false); 78 | //console.log('most recent marker is: ', markerData[markerData.length - 1]); 79 | // email from context and organizer from context 80 | // get event id to store in state 81 | } catch (err) { 82 | console.log('error in post: ', err.message); 83 | } 84 | }; 85 | 86 | // autocomplete onLoad 87 | function onLoad(ac) { 88 | console.log('here in ONLOAD, ac is: ', ac); 89 | autocomplete = ac; 90 | } 91 | 92 | // autocomplete change handler 93 | function handleChange() { 94 | console.log('autocomplete is currently: ', autocomplete); 95 | if(autocomplete !== null) console.log('autocomplete place is: ', autocomplete.getPlace()); 96 | } 97 | 98 | return ( 99 |
100 |

Edit your Event

101 |
102 | 105 | setName(e.target.value)} value={name} required /> 106 | 109 | setDescription(e.target.value)} value={description} required /> 110 | 113 | setLocName(e.target.value)} value={locName} required /> 114 | 117 | { 121 | console.log('PLACE in autocomplete IS: ', place); 122 | setAddress(place.formatted_address); 123 | }} 124 | value={address} 125 | /> 126 | 129 | setDate(e.target.value)} value={date} required /> 130 | 131 | 132 | 133 |
134 | ); 135 | } 136 | -------------------------------------------------------------------------------- /client/src/components/Map.jsx: -------------------------------------------------------------------------------- 1 | import '../stylesheets/App.css'; 2 | import React, { useState, useEffect, useContext} from 'react'; 3 | import { GoogleMap, useJsApiLoader, MarkerF } from '@react-google-maps/api'; 4 | 5 | import axios from 'axios'; 6 | import MarkerCreator from './MarkerCreator'; 7 | import MarkerUpdator from './MarkerUpdator'; 8 | import { UserContext } from './UserContext'; 9 | 10 | function Map() { 11 | // state for map center positioning 12 | const [mapPos, setMapPos] = useState({ lat: 0.37766641e2, lng: -0.123098308e3 }); 13 | 14 | // state for the data for marker from the database 15 | const [markerData, setMarkerData] = useState([]); 16 | 17 | // state to display the event data to the page after clicking a marker 18 | const [eventData, setEventData] = useState(null); 19 | 20 | // get the userID from the context 21 | // userID is used to determine if the user is the creator of the event 22 | const { user } = useContext(UserContext); 23 | const [updating, setUpdating] = useState(false); 24 | // in-the-works refactor to clarify userID vs eventID from .id 25 | const userID = user === null ? null : user.id; 26 | 27 | // Load the script for google maps API 28 | const { isLoaded } = useJsApiLoader({ 29 | googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY, 30 | // we don't think this is actually used, but removing it breaks EVERYTHING?! 31 | libraries: ['places'], 32 | }); 33 | 34 | // get all marker data from database on mount 35 | useEffect(() => { 36 | try { 37 | const getEvents = async () => { 38 | const response = await axios.get('/api/events'); 39 | const { data } = response; 40 | setMarkerData(data); 41 | }; 42 | getEvents(); 43 | // get current user location and set the center of the map to that location 44 | if (navigator.geolocation) { // native browser geolocation functionality 45 | navigator.geolocation.getCurrentPosition( 46 | (position) => { 47 | const pos = { 48 | lat: position.coords.latitude, 49 | lng: position.coords.longitude, 50 | }; 51 | // change map center positioning state 52 | setMapPos(pos); 53 | }, 54 | ); 55 | } 56 | } catch (e) { 57 | console.log('error in getEvents: ', e.message); 58 | } 59 | }, []); 60 | 61 | // change google map position to current user location on button click 62 | const currPosition = () => { 63 | if (navigator.geolocation) { 64 | navigator.geolocation.getCurrentPosition( 65 | (position) => { 66 | const pos = { 67 | lat: position.coords.latitude, 68 | lng: position.coords.longitude, 69 | }; 70 | // change map center positioning state 71 | setMapPos(pos); 72 | }, 73 | ); 74 | } 75 | }; 76 | 77 | // handle click on update button 78 | const handleUpdate = () => { 79 | setUpdating(true); 80 | }; 81 | 82 | // handle click on delete button 83 | const handleDelete = async (eID, uID) => { 84 | // create the object for the db query on backend 85 | const deleteReq = { 86 | eventID: eID, 87 | userID: uID, 88 | }; 89 | // send object to the server to delete the event 90 | const response = await axios.delete('/api/events/', { // yeah, not restful, oh well sowee >.< 91 | data: { deleteReq } }); 92 | // filter the removed event from the marker data array 93 | setMarkerData(prevMarkerData => { 94 | return prevMarkerData.filter(event => { 95 | return event.id !== eID; 96 | }); 97 | }); 98 | setEventData(null); 99 | }; 100 | 101 | // ensures that a div exists for the map even when the map API key is not loaded successfully. DO NOT DELETE 102 | if (!isLoaded) return
Loading... 🥺
; 103 | // component imported from @react-google-maps/api used to render google maps 104 | // https://react-google-maps-api-docs.netlify.app/#googlemap 105 | 106 | 107 | // yes ... we know that this could be refactored into multiple components but .... time 108 | return ( 109 |
110 | 115 | 116 | {/* If markerData is changed, places corresponding Markers in the map */} 117 | {/* component imported from @react-google-maps/api renders markers on the map */} 118 | {markerData.length > 0 && markerData.map((event) => ( 119 | setEventData(event)} 124 | /> 125 | ))} 126 | 127 | {/* If a Marker is being added, call MarkerCreator and if updated, call MarkerUpdator */} 128 |
129 | {!updating && } 130 | {updating 131 | && ( 132 | 138 | )} 139 | {/* If eventData and user are not null, display the event data */} 140 | { 141 | eventData && user && 142 | ( 143 |
144 |

{eventData.name}

145 |

{eventData.description}

146 |
    147 |
  • Organizer: {eventData.organizer}
  • 148 |
  • Location: {eventData.address}
  • 149 |
  • Date: {(new Date(eventData.date)).toLocaleString()}
  • 150 |
  • RSVP: {eventData.email}
  • 151 |
152 | {/* If the user is the creator of the event, display the edit and delete buttons */} 153 | { 154 | eventData.email === user.email && ( 155 |
156 | 157 | 158 |
159 | ) 160 | } 161 |
162 | ) 163 | } 164 |
165 |
166 | ); 167 | } 168 | 169 | export default Map; 170 | -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "server", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "cookie-parser": "^1.4.6", 13 | "express": "^4.18.2", 14 | "express-session": "^1.17.3", 15 | "google-auth-library": "^8.7.0", 16 | "jwt-decode": "^3.1.2", 17 | "pg": "^8.9.0" 18 | }, 19 | "devDependencies": { 20 | "dotenv": "^16.0.3" 21 | } 22 | }, 23 | "node_modules/accepts": { 24 | "version": "1.3.8", 25 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 26 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 27 | "dependencies": { 28 | "mime-types": "~2.1.34", 29 | "negotiator": "0.6.3" 30 | }, 31 | "engines": { 32 | "node": ">= 0.6" 33 | } 34 | }, 35 | "node_modules/agent-base": { 36 | "version": "6.0.2", 37 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 38 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 39 | "dependencies": { 40 | "debug": "4" 41 | }, 42 | "engines": { 43 | "node": ">= 6.0.0" 44 | } 45 | }, 46 | "node_modules/agent-base/node_modules/debug": { 47 | "version": "4.3.4", 48 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 49 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 50 | "dependencies": { 51 | "ms": "2.1.2" 52 | }, 53 | "engines": { 54 | "node": ">=6.0" 55 | }, 56 | "peerDependenciesMeta": { 57 | "supports-color": { 58 | "optional": true 59 | } 60 | } 61 | }, 62 | "node_modules/agent-base/node_modules/ms": { 63 | "version": "2.1.2", 64 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 65 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 66 | }, 67 | "node_modules/array-flatten": { 68 | "version": "1.1.1", 69 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 70 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 71 | }, 72 | "node_modules/arrify": { 73 | "version": "2.0.1", 74 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", 75 | "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", 76 | "engines": { 77 | "node": ">=8" 78 | } 79 | }, 80 | "node_modules/base64-js": { 81 | "version": "1.5.1", 82 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 83 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 84 | "funding": [ 85 | { 86 | "type": "github", 87 | "url": "https://github.com/sponsors/feross" 88 | }, 89 | { 90 | "type": "patreon", 91 | "url": "https://www.patreon.com/feross" 92 | }, 93 | { 94 | "type": "consulting", 95 | "url": "https://feross.org/support" 96 | } 97 | ] 98 | }, 99 | "node_modules/bignumber.js": { 100 | "version": "9.1.1", 101 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", 102 | "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", 103 | "engines": { 104 | "node": "*" 105 | } 106 | }, 107 | "node_modules/body-parser": { 108 | "version": "1.20.1", 109 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 110 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 111 | "dependencies": { 112 | "bytes": "3.1.2", 113 | "content-type": "~1.0.4", 114 | "debug": "2.6.9", 115 | "depd": "2.0.0", 116 | "destroy": "1.2.0", 117 | "http-errors": "2.0.0", 118 | "iconv-lite": "0.4.24", 119 | "on-finished": "2.4.1", 120 | "qs": "6.11.0", 121 | "raw-body": "2.5.1", 122 | "type-is": "~1.6.18", 123 | "unpipe": "1.0.0" 124 | }, 125 | "engines": { 126 | "node": ">= 0.8", 127 | "npm": "1.2.8000 || >= 1.4.16" 128 | } 129 | }, 130 | "node_modules/buffer-equal-constant-time": { 131 | "version": "1.0.1", 132 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 133 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" 134 | }, 135 | "node_modules/buffer-writer": { 136 | "version": "2.0.0", 137 | "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", 138 | "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", 139 | "engines": { 140 | "node": ">=4" 141 | } 142 | }, 143 | "node_modules/bytes": { 144 | "version": "3.1.2", 145 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 146 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 147 | "engines": { 148 | "node": ">= 0.8" 149 | } 150 | }, 151 | "node_modules/call-bind": { 152 | "version": "1.0.2", 153 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 154 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 155 | "dependencies": { 156 | "function-bind": "^1.1.1", 157 | "get-intrinsic": "^1.0.2" 158 | }, 159 | "funding": { 160 | "url": "https://github.com/sponsors/ljharb" 161 | } 162 | }, 163 | "node_modules/content-disposition": { 164 | "version": "0.5.4", 165 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 166 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 167 | "dependencies": { 168 | "safe-buffer": "5.2.1" 169 | }, 170 | "engines": { 171 | "node": ">= 0.6" 172 | } 173 | }, 174 | "node_modules/content-type": { 175 | "version": "1.0.5", 176 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 177 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 178 | "engines": { 179 | "node": ">= 0.6" 180 | } 181 | }, 182 | "node_modules/cookie": { 183 | "version": "0.5.0", 184 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 185 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 186 | "engines": { 187 | "node": ">= 0.6" 188 | } 189 | }, 190 | "node_modules/cookie-parser": { 191 | "version": "1.4.6", 192 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", 193 | "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", 194 | "dependencies": { 195 | "cookie": "0.4.1", 196 | "cookie-signature": "1.0.6" 197 | }, 198 | "engines": { 199 | "node": ">= 0.8.0" 200 | } 201 | }, 202 | "node_modules/cookie-parser/node_modules/cookie": { 203 | "version": "0.4.1", 204 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 205 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", 206 | "engines": { 207 | "node": ">= 0.6" 208 | } 209 | }, 210 | "node_modules/cookie-signature": { 211 | "version": "1.0.6", 212 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 213 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 214 | }, 215 | "node_modules/debug": { 216 | "version": "2.6.9", 217 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 218 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 219 | "dependencies": { 220 | "ms": "2.0.0" 221 | } 222 | }, 223 | "node_modules/depd": { 224 | "version": "2.0.0", 225 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 226 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 227 | "engines": { 228 | "node": ">= 0.8" 229 | } 230 | }, 231 | "node_modules/destroy": { 232 | "version": "1.2.0", 233 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 234 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 235 | "engines": { 236 | "node": ">= 0.8", 237 | "npm": "1.2.8000 || >= 1.4.16" 238 | } 239 | }, 240 | "node_modules/dotenv": { 241 | "version": "16.0.3", 242 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", 243 | "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", 244 | "dev": true, 245 | "engines": { 246 | "node": ">=12" 247 | } 248 | }, 249 | "node_modules/ecdsa-sig-formatter": { 250 | "version": "1.0.11", 251 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 252 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 253 | "dependencies": { 254 | "safe-buffer": "^5.0.1" 255 | } 256 | }, 257 | "node_modules/ee-first": { 258 | "version": "1.1.1", 259 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 260 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 261 | }, 262 | "node_modules/encodeurl": { 263 | "version": "1.0.2", 264 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 265 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 266 | "engines": { 267 | "node": ">= 0.8" 268 | } 269 | }, 270 | "node_modules/escape-html": { 271 | "version": "1.0.3", 272 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 273 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 274 | }, 275 | "node_modules/etag": { 276 | "version": "1.8.1", 277 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 278 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 279 | "engines": { 280 | "node": ">= 0.6" 281 | } 282 | }, 283 | "node_modules/express": { 284 | "version": "4.18.2", 285 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", 286 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", 287 | "dependencies": { 288 | "accepts": "~1.3.8", 289 | "array-flatten": "1.1.1", 290 | "body-parser": "1.20.1", 291 | "content-disposition": "0.5.4", 292 | "content-type": "~1.0.4", 293 | "cookie": "0.5.0", 294 | "cookie-signature": "1.0.6", 295 | "debug": "2.6.9", 296 | "depd": "2.0.0", 297 | "encodeurl": "~1.0.2", 298 | "escape-html": "~1.0.3", 299 | "etag": "~1.8.1", 300 | "finalhandler": "1.2.0", 301 | "fresh": "0.5.2", 302 | "http-errors": "2.0.0", 303 | "merge-descriptors": "1.0.1", 304 | "methods": "~1.1.2", 305 | "on-finished": "2.4.1", 306 | "parseurl": "~1.3.3", 307 | "path-to-regexp": "0.1.7", 308 | "proxy-addr": "~2.0.7", 309 | "qs": "6.11.0", 310 | "range-parser": "~1.2.1", 311 | "safe-buffer": "5.2.1", 312 | "send": "0.18.0", 313 | "serve-static": "1.15.0", 314 | "setprototypeof": "1.2.0", 315 | "statuses": "2.0.1", 316 | "type-is": "~1.6.18", 317 | "utils-merge": "1.0.1", 318 | "vary": "~1.1.2" 319 | }, 320 | "engines": { 321 | "node": ">= 0.10.0" 322 | } 323 | }, 324 | "node_modules/express-session": { 325 | "version": "1.17.3", 326 | "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", 327 | "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", 328 | "dependencies": { 329 | "cookie": "0.4.2", 330 | "cookie-signature": "1.0.6", 331 | "debug": "2.6.9", 332 | "depd": "~2.0.0", 333 | "on-headers": "~1.0.2", 334 | "parseurl": "~1.3.3", 335 | "safe-buffer": "5.2.1", 336 | "uid-safe": "~2.1.5" 337 | }, 338 | "engines": { 339 | "node": ">= 0.8.0" 340 | } 341 | }, 342 | "node_modules/express-session/node_modules/cookie": { 343 | "version": "0.4.2", 344 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", 345 | "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", 346 | "engines": { 347 | "node": ">= 0.6" 348 | } 349 | }, 350 | "node_modules/extend": { 351 | "version": "3.0.2", 352 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 353 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 354 | }, 355 | "node_modules/fast-text-encoding": { 356 | "version": "1.0.6", 357 | "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", 358 | "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" 359 | }, 360 | "node_modules/finalhandler": { 361 | "version": "1.2.0", 362 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 363 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 364 | "dependencies": { 365 | "debug": "2.6.9", 366 | "encodeurl": "~1.0.2", 367 | "escape-html": "~1.0.3", 368 | "on-finished": "2.4.1", 369 | "parseurl": "~1.3.3", 370 | "statuses": "2.0.1", 371 | "unpipe": "~1.0.0" 372 | }, 373 | "engines": { 374 | "node": ">= 0.8" 375 | } 376 | }, 377 | "node_modules/forwarded": { 378 | "version": "0.2.0", 379 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 380 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 381 | "engines": { 382 | "node": ">= 0.6" 383 | } 384 | }, 385 | "node_modules/fresh": { 386 | "version": "0.5.2", 387 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 388 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 389 | "engines": { 390 | "node": ">= 0.6" 391 | } 392 | }, 393 | "node_modules/function-bind": { 394 | "version": "1.1.1", 395 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 396 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 397 | }, 398 | "node_modules/gaxios": { 399 | "version": "5.0.2", 400 | "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", 401 | "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", 402 | "dependencies": { 403 | "extend": "^3.0.2", 404 | "https-proxy-agent": "^5.0.0", 405 | "is-stream": "^2.0.0", 406 | "node-fetch": "^2.6.7" 407 | }, 408 | "engines": { 409 | "node": ">=12" 410 | } 411 | }, 412 | "node_modules/gcp-metadata": { 413 | "version": "5.2.0", 414 | "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", 415 | "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", 416 | "dependencies": { 417 | "gaxios": "^5.0.0", 418 | "json-bigint": "^1.0.0" 419 | }, 420 | "engines": { 421 | "node": ">=12" 422 | } 423 | }, 424 | "node_modules/get-intrinsic": { 425 | "version": "1.2.0", 426 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", 427 | "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", 428 | "dependencies": { 429 | "function-bind": "^1.1.1", 430 | "has": "^1.0.3", 431 | "has-symbols": "^1.0.3" 432 | }, 433 | "funding": { 434 | "url": "https://github.com/sponsors/ljharb" 435 | } 436 | }, 437 | "node_modules/google-auth-library": { 438 | "version": "8.7.0", 439 | "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", 440 | "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", 441 | "dependencies": { 442 | "arrify": "^2.0.0", 443 | "base64-js": "^1.3.0", 444 | "ecdsa-sig-formatter": "^1.0.11", 445 | "fast-text-encoding": "^1.0.0", 446 | "gaxios": "^5.0.0", 447 | "gcp-metadata": "^5.0.0", 448 | "gtoken": "^6.1.0", 449 | "jws": "^4.0.0", 450 | "lru-cache": "^6.0.0" 451 | }, 452 | "engines": { 453 | "node": ">=12" 454 | } 455 | }, 456 | "node_modules/google-p12-pem": { 457 | "version": "4.0.1", 458 | "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", 459 | "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", 460 | "dependencies": { 461 | "node-forge": "^1.3.1" 462 | }, 463 | "bin": { 464 | "gp12-pem": "build/src/bin/gp12-pem.js" 465 | }, 466 | "engines": { 467 | "node": ">=12.0.0" 468 | } 469 | }, 470 | "node_modules/gtoken": { 471 | "version": "6.1.2", 472 | "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", 473 | "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", 474 | "dependencies": { 475 | "gaxios": "^5.0.1", 476 | "google-p12-pem": "^4.0.0", 477 | "jws": "^4.0.0" 478 | }, 479 | "engines": { 480 | "node": ">=12.0.0" 481 | } 482 | }, 483 | "node_modules/has": { 484 | "version": "1.0.3", 485 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 486 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 487 | "dependencies": { 488 | "function-bind": "^1.1.1" 489 | }, 490 | "engines": { 491 | "node": ">= 0.4.0" 492 | } 493 | }, 494 | "node_modules/has-symbols": { 495 | "version": "1.0.3", 496 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 497 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 498 | "engines": { 499 | "node": ">= 0.4" 500 | }, 501 | "funding": { 502 | "url": "https://github.com/sponsors/ljharb" 503 | } 504 | }, 505 | "node_modules/http-errors": { 506 | "version": "2.0.0", 507 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 508 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 509 | "dependencies": { 510 | "depd": "2.0.0", 511 | "inherits": "2.0.4", 512 | "setprototypeof": "1.2.0", 513 | "statuses": "2.0.1", 514 | "toidentifier": "1.0.1" 515 | }, 516 | "engines": { 517 | "node": ">= 0.8" 518 | } 519 | }, 520 | "node_modules/https-proxy-agent": { 521 | "version": "5.0.1", 522 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", 523 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", 524 | "dependencies": { 525 | "agent-base": "6", 526 | "debug": "4" 527 | }, 528 | "engines": { 529 | "node": ">= 6" 530 | } 531 | }, 532 | "node_modules/https-proxy-agent/node_modules/debug": { 533 | "version": "4.3.4", 534 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 535 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 536 | "dependencies": { 537 | "ms": "2.1.2" 538 | }, 539 | "engines": { 540 | "node": ">=6.0" 541 | }, 542 | "peerDependenciesMeta": { 543 | "supports-color": { 544 | "optional": true 545 | } 546 | } 547 | }, 548 | "node_modules/https-proxy-agent/node_modules/ms": { 549 | "version": "2.1.2", 550 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 551 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 552 | }, 553 | "node_modules/iconv-lite": { 554 | "version": "0.4.24", 555 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 556 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 557 | "dependencies": { 558 | "safer-buffer": ">= 2.1.2 < 3" 559 | }, 560 | "engines": { 561 | "node": ">=0.10.0" 562 | } 563 | }, 564 | "node_modules/inherits": { 565 | "version": "2.0.4", 566 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 567 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 568 | }, 569 | "node_modules/ipaddr.js": { 570 | "version": "1.9.1", 571 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 572 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 573 | "engines": { 574 | "node": ">= 0.10" 575 | } 576 | }, 577 | "node_modules/is-stream": { 578 | "version": "2.0.1", 579 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 580 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 581 | "engines": { 582 | "node": ">=8" 583 | }, 584 | "funding": { 585 | "url": "https://github.com/sponsors/sindresorhus" 586 | } 587 | }, 588 | "node_modules/json-bigint": { 589 | "version": "1.0.0", 590 | "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", 591 | "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", 592 | "dependencies": { 593 | "bignumber.js": "^9.0.0" 594 | } 595 | }, 596 | "node_modules/jwa": { 597 | "version": "2.0.0", 598 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", 599 | "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", 600 | "dependencies": { 601 | "buffer-equal-constant-time": "1.0.1", 602 | "ecdsa-sig-formatter": "1.0.11", 603 | "safe-buffer": "^5.0.1" 604 | } 605 | }, 606 | "node_modules/jws": { 607 | "version": "4.0.0", 608 | "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", 609 | "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", 610 | "dependencies": { 611 | "jwa": "^2.0.0", 612 | "safe-buffer": "^5.0.1" 613 | } 614 | }, 615 | "node_modules/jwt-decode": { 616 | "version": "3.1.2", 617 | "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", 618 | "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" 619 | }, 620 | "node_modules/lru-cache": { 621 | "version": "6.0.0", 622 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 623 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 624 | "dependencies": { 625 | "yallist": "^4.0.0" 626 | }, 627 | "engines": { 628 | "node": ">=10" 629 | } 630 | }, 631 | "node_modules/media-typer": { 632 | "version": "0.3.0", 633 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 634 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 635 | "engines": { 636 | "node": ">= 0.6" 637 | } 638 | }, 639 | "node_modules/merge-descriptors": { 640 | "version": "1.0.1", 641 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 642 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 643 | }, 644 | "node_modules/methods": { 645 | "version": "1.1.2", 646 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 647 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 648 | "engines": { 649 | "node": ">= 0.6" 650 | } 651 | }, 652 | "node_modules/mime": { 653 | "version": "1.6.0", 654 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 655 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 656 | "bin": { 657 | "mime": "cli.js" 658 | }, 659 | "engines": { 660 | "node": ">=4" 661 | } 662 | }, 663 | "node_modules/mime-db": { 664 | "version": "1.52.0", 665 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 666 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 667 | "engines": { 668 | "node": ">= 0.6" 669 | } 670 | }, 671 | "node_modules/mime-types": { 672 | "version": "2.1.35", 673 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 674 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 675 | "dependencies": { 676 | "mime-db": "1.52.0" 677 | }, 678 | "engines": { 679 | "node": ">= 0.6" 680 | } 681 | }, 682 | "node_modules/ms": { 683 | "version": "2.0.0", 684 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 685 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 686 | }, 687 | "node_modules/negotiator": { 688 | "version": "0.6.3", 689 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 690 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 691 | "engines": { 692 | "node": ">= 0.6" 693 | } 694 | }, 695 | "node_modules/node-fetch": { 696 | "version": "2.6.9", 697 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", 698 | "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", 699 | "dependencies": { 700 | "whatwg-url": "^5.0.0" 701 | }, 702 | "engines": { 703 | "node": "4.x || >=6.0.0" 704 | }, 705 | "peerDependencies": { 706 | "encoding": "^0.1.0" 707 | }, 708 | "peerDependenciesMeta": { 709 | "encoding": { 710 | "optional": true 711 | } 712 | } 713 | }, 714 | "node_modules/node-forge": { 715 | "version": "1.3.1", 716 | "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", 717 | "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", 718 | "engines": { 719 | "node": ">= 6.13.0" 720 | } 721 | }, 722 | "node_modules/object-inspect": { 723 | "version": "1.12.3", 724 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 725 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", 726 | "funding": { 727 | "url": "https://github.com/sponsors/ljharb" 728 | } 729 | }, 730 | "node_modules/on-finished": { 731 | "version": "2.4.1", 732 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 733 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 734 | "dependencies": { 735 | "ee-first": "1.1.1" 736 | }, 737 | "engines": { 738 | "node": ">= 0.8" 739 | } 740 | }, 741 | "node_modules/on-headers": { 742 | "version": "1.0.2", 743 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 744 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", 745 | "engines": { 746 | "node": ">= 0.8" 747 | } 748 | }, 749 | "node_modules/packet-reader": { 750 | "version": "1.0.0", 751 | "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", 752 | "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" 753 | }, 754 | "node_modules/parseurl": { 755 | "version": "1.3.3", 756 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 757 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 758 | "engines": { 759 | "node": ">= 0.8" 760 | } 761 | }, 762 | "node_modules/path-to-regexp": { 763 | "version": "0.1.7", 764 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 765 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 766 | }, 767 | "node_modules/pg": { 768 | "version": "8.9.0", 769 | "resolved": "https://registry.npmjs.org/pg/-/pg-8.9.0.tgz", 770 | "integrity": "sha512-ZJM+qkEbtOHRuXjmvBtOgNOXOtLSbxiMiUVMgE4rV6Zwocy03RicCVvDXgx8l4Biwo8/qORUnEqn2fdQzV7KCg==", 771 | "dependencies": { 772 | "buffer-writer": "2.0.0", 773 | "packet-reader": "1.0.0", 774 | "pg-connection-string": "^2.5.0", 775 | "pg-pool": "^3.5.2", 776 | "pg-protocol": "^1.6.0", 777 | "pg-types": "^2.1.0", 778 | "pgpass": "1.x" 779 | }, 780 | "engines": { 781 | "node": ">= 8.0.0" 782 | }, 783 | "peerDependencies": { 784 | "pg-native": ">=3.0.1" 785 | }, 786 | "peerDependenciesMeta": { 787 | "pg-native": { 788 | "optional": true 789 | } 790 | } 791 | }, 792 | "node_modules/pg-connection-string": { 793 | "version": "2.5.0", 794 | "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", 795 | "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" 796 | }, 797 | "node_modules/pg-int8": { 798 | "version": "1.0.1", 799 | "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", 800 | "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", 801 | "engines": { 802 | "node": ">=4.0.0" 803 | } 804 | }, 805 | "node_modules/pg-pool": { 806 | "version": "3.5.2", 807 | "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", 808 | "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", 809 | "peerDependencies": { 810 | "pg": ">=8.0" 811 | } 812 | }, 813 | "node_modules/pg-protocol": { 814 | "version": "1.6.0", 815 | "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", 816 | "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" 817 | }, 818 | "node_modules/pg-types": { 819 | "version": "2.2.0", 820 | "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", 821 | "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", 822 | "dependencies": { 823 | "pg-int8": "1.0.1", 824 | "postgres-array": "~2.0.0", 825 | "postgres-bytea": "~1.0.0", 826 | "postgres-date": "~1.0.4", 827 | "postgres-interval": "^1.1.0" 828 | }, 829 | "engines": { 830 | "node": ">=4" 831 | } 832 | }, 833 | "node_modules/pgpass": { 834 | "version": "1.0.5", 835 | "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", 836 | "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", 837 | "dependencies": { 838 | "split2": "^4.1.0" 839 | } 840 | }, 841 | "node_modules/postgres-array": { 842 | "version": "2.0.0", 843 | "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", 844 | "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", 845 | "engines": { 846 | "node": ">=4" 847 | } 848 | }, 849 | "node_modules/postgres-bytea": { 850 | "version": "1.0.0", 851 | "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", 852 | "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", 853 | "engines": { 854 | "node": ">=0.10.0" 855 | } 856 | }, 857 | "node_modules/postgres-date": { 858 | "version": "1.0.7", 859 | "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", 860 | "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", 861 | "engines": { 862 | "node": ">=0.10.0" 863 | } 864 | }, 865 | "node_modules/postgres-interval": { 866 | "version": "1.2.0", 867 | "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", 868 | "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", 869 | "dependencies": { 870 | "xtend": "^4.0.0" 871 | }, 872 | "engines": { 873 | "node": ">=0.10.0" 874 | } 875 | }, 876 | "node_modules/proxy-addr": { 877 | "version": "2.0.7", 878 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 879 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 880 | "dependencies": { 881 | "forwarded": "0.2.0", 882 | "ipaddr.js": "1.9.1" 883 | }, 884 | "engines": { 885 | "node": ">= 0.10" 886 | } 887 | }, 888 | "node_modules/qs": { 889 | "version": "6.11.0", 890 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 891 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 892 | "dependencies": { 893 | "side-channel": "^1.0.4" 894 | }, 895 | "engines": { 896 | "node": ">=0.6" 897 | }, 898 | "funding": { 899 | "url": "https://github.com/sponsors/ljharb" 900 | } 901 | }, 902 | "node_modules/random-bytes": { 903 | "version": "1.0.0", 904 | "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", 905 | "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", 906 | "engines": { 907 | "node": ">= 0.8" 908 | } 909 | }, 910 | "node_modules/range-parser": { 911 | "version": "1.2.1", 912 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 913 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 914 | "engines": { 915 | "node": ">= 0.6" 916 | } 917 | }, 918 | "node_modules/raw-body": { 919 | "version": "2.5.1", 920 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 921 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 922 | "dependencies": { 923 | "bytes": "3.1.2", 924 | "http-errors": "2.0.0", 925 | "iconv-lite": "0.4.24", 926 | "unpipe": "1.0.0" 927 | }, 928 | "engines": { 929 | "node": ">= 0.8" 930 | } 931 | }, 932 | "node_modules/safe-buffer": { 933 | "version": "5.2.1", 934 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 935 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 936 | "funding": [ 937 | { 938 | "type": "github", 939 | "url": "https://github.com/sponsors/feross" 940 | }, 941 | { 942 | "type": "patreon", 943 | "url": "https://www.patreon.com/feross" 944 | }, 945 | { 946 | "type": "consulting", 947 | "url": "https://feross.org/support" 948 | } 949 | ] 950 | }, 951 | "node_modules/safer-buffer": { 952 | "version": "2.1.2", 953 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 954 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 955 | }, 956 | "node_modules/send": { 957 | "version": "0.18.0", 958 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 959 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 960 | "dependencies": { 961 | "debug": "2.6.9", 962 | "depd": "2.0.0", 963 | "destroy": "1.2.0", 964 | "encodeurl": "~1.0.2", 965 | "escape-html": "~1.0.3", 966 | "etag": "~1.8.1", 967 | "fresh": "0.5.2", 968 | "http-errors": "2.0.0", 969 | "mime": "1.6.0", 970 | "ms": "2.1.3", 971 | "on-finished": "2.4.1", 972 | "range-parser": "~1.2.1", 973 | "statuses": "2.0.1" 974 | }, 975 | "engines": { 976 | "node": ">= 0.8.0" 977 | } 978 | }, 979 | "node_modules/send/node_modules/ms": { 980 | "version": "2.1.3", 981 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 982 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 983 | }, 984 | "node_modules/serve-static": { 985 | "version": "1.15.0", 986 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 987 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 988 | "dependencies": { 989 | "encodeurl": "~1.0.2", 990 | "escape-html": "~1.0.3", 991 | "parseurl": "~1.3.3", 992 | "send": "0.18.0" 993 | }, 994 | "engines": { 995 | "node": ">= 0.8.0" 996 | } 997 | }, 998 | "node_modules/setprototypeof": { 999 | "version": "1.2.0", 1000 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1001 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1002 | }, 1003 | "node_modules/side-channel": { 1004 | "version": "1.0.4", 1005 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1006 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1007 | "dependencies": { 1008 | "call-bind": "^1.0.0", 1009 | "get-intrinsic": "^1.0.2", 1010 | "object-inspect": "^1.9.0" 1011 | }, 1012 | "funding": { 1013 | "url": "https://github.com/sponsors/ljharb" 1014 | } 1015 | }, 1016 | "node_modules/split2": { 1017 | "version": "4.1.0", 1018 | "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", 1019 | "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", 1020 | "engines": { 1021 | "node": ">= 10.x" 1022 | } 1023 | }, 1024 | "node_modules/statuses": { 1025 | "version": "2.0.1", 1026 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1027 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1028 | "engines": { 1029 | "node": ">= 0.8" 1030 | } 1031 | }, 1032 | "node_modules/toidentifier": { 1033 | "version": "1.0.1", 1034 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1035 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1036 | "engines": { 1037 | "node": ">=0.6" 1038 | } 1039 | }, 1040 | "node_modules/tr46": { 1041 | "version": "0.0.3", 1042 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1043 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 1044 | }, 1045 | "node_modules/type-is": { 1046 | "version": "1.6.18", 1047 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1048 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1049 | "dependencies": { 1050 | "media-typer": "0.3.0", 1051 | "mime-types": "~2.1.24" 1052 | }, 1053 | "engines": { 1054 | "node": ">= 0.6" 1055 | } 1056 | }, 1057 | "node_modules/uid-safe": { 1058 | "version": "2.1.5", 1059 | "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", 1060 | "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", 1061 | "dependencies": { 1062 | "random-bytes": "~1.0.0" 1063 | }, 1064 | "engines": { 1065 | "node": ">= 0.8" 1066 | } 1067 | }, 1068 | "node_modules/unpipe": { 1069 | "version": "1.0.0", 1070 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1071 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1072 | "engines": { 1073 | "node": ">= 0.8" 1074 | } 1075 | }, 1076 | "node_modules/utils-merge": { 1077 | "version": "1.0.1", 1078 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1079 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1080 | "engines": { 1081 | "node": ">= 0.4.0" 1082 | } 1083 | }, 1084 | "node_modules/vary": { 1085 | "version": "1.1.2", 1086 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1087 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1088 | "engines": { 1089 | "node": ">= 0.8" 1090 | } 1091 | }, 1092 | "node_modules/webidl-conversions": { 1093 | "version": "3.0.1", 1094 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1095 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 1096 | }, 1097 | "node_modules/whatwg-url": { 1098 | "version": "5.0.0", 1099 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1100 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 1101 | "dependencies": { 1102 | "tr46": "~0.0.3", 1103 | "webidl-conversions": "^3.0.0" 1104 | } 1105 | }, 1106 | "node_modules/xtend": { 1107 | "version": "4.0.2", 1108 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 1109 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 1110 | "engines": { 1111 | "node": ">=0.4" 1112 | } 1113 | }, 1114 | "node_modules/yallist": { 1115 | "version": "4.0.0", 1116 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1117 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1118 | } 1119 | } 1120 | } 1121 | --------------------------------------------------------------------------------