diff --git a/.gitignore b/.gitignore index 91dfed8..7a8675d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .DS_Store -node_modules \ No newline at end of file +node_modules +.env +.env.test \ No newline at end of file diff --git a/app.js b/app.js index 4541e78..bda653e 100644 --- a/app.js +++ b/app.js @@ -1,17 +1,39 @@ -const express = require('express'); // import express module (simplifies routing/requests, among other things) -const app = express(); // create an instance of the express module (app is the conventional variable name used) -const fetch = require('node-fetch'); // import node-fetch (enables the fetch API to be used server-side) -const PORT = process.env.PORT || 5000; // use either the host env var port (PORT) provided by Heroku or the local port (5000) on your machine +//import libs +const express = require('express'); +const app = express(); +const PORT = process.env.PORT || 5000; +require('dotenv').config() -app.get('/', (req, res) => { // send a get request to root directory ('/' is this file (app.js)) - fetch('https://www.boredapi.com/api/activity') // fetch activity from bored API - https://www.boredapi.com/about - .then(res => res.json()) // return a promise containing the response - .then(json => res.send(`

Today's Activity: ${json.activity}!

`)) // extract the JSON body content from the response (specifically the activity value) and sends it to the client - .catch(function(err){ // catch any errors - console.log(err); // log errors to the console - }) +//logging middleware +const morgan = require('morgan'); +app.use(morgan('tiny')); + +//error handling for dev environment +if (process.env.NODE_ENV === 'development') { + const errorhandler = require('errorhandler'); + app.use(errorhandler()); +} + +//import routers +const roomRouter = require('./routers/roomRouter'); //legacy +const scenarioRouter = require('./routers/scenarioRouter'); //legacy + +const userRouter = require('./routers/userRouter'); +const nodeRouter = require('./routers/nodeRouter'); +const campRouter = require('./routers/campRouter'); +const infoRouter = require('./routers/infoRouter'); + +//mount routers +app.use('/room', roomRouter); //legacy +app.use('/scenario', scenarioRouter); //legacy +app.use('/user', userRouter); +app.use('/node', nodeRouter); +app.use('/camp', campRouter); +app.use('/info', infoRouter); + +//start server +app.listen(PORT, () => { + console.log(`App is running on ${PORT}`) }) -app.listen(PORT, () => { // start server and listen on specified port - console.log(`App is running on ${PORT}`) // confirm server is running and log port to the console -}) \ No newline at end of file +//test diff --git a/appInfo/balancing.js b/appInfo/balancing.js new file mode 100644 index 0000000..65edcf6 --- /dev/null +++ b/appInfo/balancing.js @@ -0,0 +1,13 @@ +const numbers = { + maxPlayersForQueueTop: 3, + titleMinChars: 3, + titleMaxChars: 50, + descriptionMinChars: 3, + descriptionMaxChars: 200, + scenarioMinCharacter: 3, + scenarioMaxCharacters: 600, +} + +module.exports = { + numbers +} \ No newline at end of file diff --git a/database/dbChecks.js b/database/dbChecks.js new file mode 100644 index 0000000..64e3625 --- /dev/null +++ b/database/dbChecks.js @@ -0,0 +1,107 @@ +//CHECKS STUFF IN THE DATABASE AND THROWS ERRORS IF THEY ARE FALSE +const db = require('./dbConnect.js'); + +//ERROR CHECKS +async function CanAddNode(campId, userId) { + + const lastFinishedNodeQ = await db.query( + ` + SELECT + camps.finished, + nodes_0.creator_id AS last_scenario_creator_id + FROM camps + JOIN nodes_0 on nodes_0.camp_id = camps.id + WHERE camps.id = $1 + AND nodes_0.finished_at IS NOT NULL + ORDER BY nodes_0.id DESC + LIMIT 1; + `, + [campId] + ); + + if (lastFinishedNodeQ.rowCount == 0) throw new Error('there is no camp with that id'); + const { finished, last_scenario_creator_id } = lastFinishedNodeQ.rows[0]; + if (finished) throw new Error('Cant add node, story is already finished'); + if (last_scenario_creator_id == userId) throw new Error('Cant add node, because user added the last scenario'); + + const lastNodeQ = await db.query( + ` + SELECT + nodes_0.created_at AS last_node_posted_at, + nodes_0.finished_at AS last_node_finished_at + FROM nodes_0 + WHERE nodes_0.camp_id = $1 + ORDER BY nodes_0.id DESC + LIMIT 1; + `, + [campId] + ); + + const { last_node_posted_at, last_node_finished_at } = lastNodeQ.rows[0]; + + //check if someone else is writing + if (last_node_finished_at) return; + const last_node_time = (new Date(last_node_posted_at)).getTime(); + const current_time = (new Date()).getTime(); + const diff = current_time - last_node_time; + const diffInMinutes = diff / 1000 / 60; + if (diffInMinutes < 20) { + throw new Error('Cant add node. Another player is currently writing'); + } + +} + +async function CanAddScenario(campId, userId, isEnd) { + + const q = await db.query( + ` + SELECT + nodes_0.creator_id AS node_creator_id, + finished_at AS node_finish_time, + scenario, + camps.finished AS camp_finished + FROM nodes_0 + LEFT JOIN scenarios_0 ON scenarios_0.node_id = nodes_0.id + JOIN camps ON camps.id = nodes_0.camp_id + WHERE camp_id = $1 + ORDER BY nodes_0.id; + `, + [campId] + ); + + const nodeCount = q.rowCount; + const lastNode = q.rows[q.rows.length - 1]; + const lastPosterId = lastNode.node_creator_id; + const lastNodeEmpty = (lastNode.node_finish_time == null) + const scenario = lastNode.scenario; + const campFinished = lastNode.camp_finished; + + if (!lastNodeEmpty) throw new Error('Cant add scenario, because the last node is marked as finished'); + if (lastPosterId != userId) throw new Error('Cant add scenario, because someone else owns the node'); + if (isEnd && nodeCount < 30) throw new Error('Cant add end, because the story is not long enough. Current nodecount: ', nodeCount); + if (!isEnd && nodeCount == 40) throw new Error('Cant add scenario. Must add end, because the story has reached its max length: ', nodeCount); + if (campFinished) throw new Error('cant add scenario, story marked as finished'); + if (scenario) throw new Error('Cant add scenario, there already exist one linked to the last node'); + +} +async function GoogleTokenExists(googleToken) { + + const tokenQ = await db.query( + ` + SELECT COUNT(*) + FROM users + WHERE google_token = $1 + `, + [googleToken] + ); + + if (tokenQ.rows[0].count == 0) return false; + else return true; + +} + +module.exports = { + CanAddNode, + CanAddScenario, + GoogleTokenExists +}; \ No newline at end of file diff --git a/database/dbConnect.js b/database/dbConnect.js new file mode 100644 index 0000000..355dd5e --- /dev/null +++ b/database/dbConnect.js @@ -0,0 +1,29 @@ +const Pool = require('pg').Pool + +const pool = ( + + process.env.DATABASE_URL ? + + //heroku + new Pool({ + connectionString: process.env.DATABASE_URL, + ssl: { + rejectUnauthorized: false + } + }) + + : + + //local + new Pool({ + user: 'postgres', + host: 'localhost', + database: 'unwritten', + password: 'postgres', + port: 5432, + }) + +) + + +module.exports = pool; \ No newline at end of file diff --git a/database/dbData.js b/database/dbData.js new file mode 100644 index 0000000..b1b037e --- /dev/null +++ b/database/dbData.js @@ -0,0 +1,395 @@ +//warning, starting to get bloated AGAIN! can break into more scripts + +const db = require('./dbConnect.js'); +const balancing = require('../appInfo/balancing'); + +//REQUEST TO APP + +//menu content +async function Feed() { + + const q = await db.query( + `SELECT + camps.title AS story_title, + camps.id AS room_id, + nodes_0.creator_id AS creator_id, + users.name AS creator_name, + nodes_0.id AS scenario_id, + scenarios_0.scenario, + nodes_0.finished_at + FROM nodes_0 + JOIN scenarios_0 ON scenarios_0.node_id = nodes_0.id + JOIN camps ON camps.id = nodes_0.camp_id + JOIN users ON users.id = nodes_0.creator_id + WHERE scenarios_0.scenario IS NOT NULL + ORDER BY nodes_0.id DESC + LIMIT 25` + ); + + return q.rows; + +} +async function PlayerStats(userId) { + + const q = await db.query( + ` + SELECT + COUNT(DISTINCT camps.id) AS camps, + SUM (CASE + WHEN camps.finished = TRUE THEN 1 + ELSE 0 + END + ) AS finished, + COUNT(DISTINCT nodes_0.id) AS contributions, + ( + SELECT COUNT(*) + FROM users + WHERE id = $1 + ) AS user_exist + FROM nodes_0 + JOIN camps ON camps.id = nodes_0.camp_id + WHERE nodes_0.creator_id = $1 + AND nodes_0.finished_at IS NOT NULL; + `, + [userId] + ); + + const stats = q.rows[0]; + + if (stats.user_exist == 0) throw new Error('No user with that ID exists'); + + return stats; + +} + +//players +async function PlayerName(playerId) { + + const nameQ = await db.query( + ` + SELECT name + FROM users + WHERE id = $1; + `, + [playerId] + ) + + return nameQ.rows[0].name; + +} +async function Player(googleId) { + + const playerQ = await db.query( + ` + SELECT * + FROM users + WHERE google_id = $1 + `, + [googleId] + ); + + if (playerQ.rowCount < 1) return null; + else return playerQ.rows[0]; + +} +async function PlayerWithGoogleToken(googleToken) { + + const playerQ = await db.query( + ` + SELECT * + FROM users + WHERE google_token = $1 + `, + [googleToken] + ); + + if (playerQ.rowCount == 0) return null; + else return playerQ.rows[0]; + +} + +//camp data +async function CampData(campId) { + + const campQ = await db.query( + ` + SELECT * + FROM camps + WHERE id=$1 + `, + [campId] + ); + + if (campQ.rowCount < 1) throw new Error('there are no camps with that id'); + const camp = campQ.rows[0]; + + return camp; +} +async function PlayersInCamp(campId) { + + const playerQuery = await db.query( + ` + SELECT + users.id, + users.name + FROM nodes_0 + JOIN users ON creator_id = users.id + WHERE camp_id = $1 + GROUP BY users.id; + `, + [campId] + ) + + if (playerQuery.rowCount < 1) throw new Error('found no players in the camp with that id'); + const players = playerQuery.rows; + return players; + +} +async function ScenariosInCamp(campId) { + + console.log('starting scenario q'); + + const scenarioQ = await db.query( + ` + SELECT + nodes_0.id as node_id, + scenario, + creator_id, + prompt + FROM scenarios_0 + JOIN nodes_0 + ON scenarios_0.node_id = nodes_0.id + WHERE nodes_0.camp_id = $1 + AND scenario IS NOT NULL + ORDER BY nodes_0.id; + `, + [campId] + ); + + if (scenarioQ.rowCount < 1) throw new Error('no scenarios in the camp witht that id'); + const scenarios = scenarioQ.rows; + + const likesQ = await db.query( + ` + SELECT node_id, user_id, name + FROM likes + JOIN nodes_0 ON likes.node_id = nodes_0.id + JOIN users on users.id = user_id + WHERE nodes_0.camp_id = $1; + `, + [campId] + ); + + const likes = likesQ.rows; + + scenarios.forEach(scenario => { + const scenarioLikes = likes.filter(like => (like.node_id == scenario.node_id)); + scenario.likes = scenarioLikes; + }); + + return scenarios; + +} + +async function GetCampPlayersExpoTokens(campId, playerExceptionId) { + + const tokenQ = await db.query( + ` + SELECT + users.expo_push_token + FROM nodes_0 + JOIN users ON creator_id = users.id + WHERE camp_id = $1 + AND finished_at > '2023-02-03T14:13:18.424666' + AND users.id != $2 + AND expo_push_token IS NOT NULL + GROUP BY users.id, users.expo_push_token; + `, + [campId, playerExceptionId] + ); + + const tokens = tokenQ.rows.map(row => row.expo_push_token); + return tokens; + +} +async function StoryTitle(campId) { + + const titleQ = await db.query( + ` + SELECT title + FROM camps + WHERE id = $1; + `, + [campId] + ) + + return titleQ.rows[0].title; + +} + +//camps +async function ActiveCamps(userId) { + + console.log('userid: ', userId); + console.log('balancing number: ', balancing.numbers.maxPlayersForQueueTop); + + const campQuery = await db.query( + ` + SELECT + camps.id, + camps.title, + camps.description, + users.name AS creator_name, + COUNT(DISTINCT nodes_0.id) AS node_count, + COUNT(DISTINCT nodes_0.creator_id) AS contributor_count, + camps.created_at + FROM camps + JOIN users ON users.id = camps.creator_id + JOIN nodes_0 ON nodes_0.camp_id = camps.id + WHERE NOT EXISTS( + SELECT * + FROM nodes_0 + WHERE creator_id = $1 + AND camp_id = camps.id + AND finished_at IS NOT NULL + ) + AND finished = 'false' + GROUP BY camps.id, users.name, camps.title, camps.description + ORDER BY (COUNT(DISTINCT nodes_0.creator_id) > $2), created_at + ; + `, + [userId, balancing.numbers.maxPlayersForQueueTop] + ); + + console.log('camp count found: ', campQuery.rowCount); + + return campQuery.rows; + +} +async function PlayerCamps(userId) { + + console.log('with user id: ', userId); + console.log('and maxplayerforquetop: ', balancing.numbers.maxPlayersForQueueTop); + + const campQuery = await db.query( + ` + SELECT + camps.id, + camps.title, + camps.description, + users.name AS creator_name, + COUNT(DISTINCT nodes_0.id) AS node_count, + COUNT(DISTINCT nodes_0.creator_id) AS contributor_count, + camps.created_at + FROM camps + JOIN users ON users.id = camps.creator_id + JOIN nodes_0 ON nodes_0.camp_id = camps.id + WHERE EXISTS( + SELECT * + FROM nodes_0 + WHERE creator_id = $1 + AND camp_id = camps.id + AND finished_at IS NOT NULL + ) + AND finished = 'false' + GROUP BY camps.id, users.name, camps.title, camps.description + ORDER BY (COUNT(DISTINCT nodes_0.creator_id) > $2), created_at + ; + `, + [userId, balancing.numbers.maxPlayersForQueueTop] + ); + + console.log('asked for user rooms and got count: ', campQuery.rowCount); + + return campQuery.rows; + +} + +// PlayerCamps(95); +async function FinishedStories() { + + const campQuery = await db.query( + ` + SELECT + camps.id, + camps.title, + camps.description, + users.name AS creator_name, + COUNT(DISTINCT nodes_0.id) AS node_count, + COUNT(DISTINCT nodes_0.creator_id) AS contributor_count, + camps.created_at + FROM camps + JOIN users ON users.id = camps.creator_id + JOIN nodes_0 ON nodes_0.camp_id = camps.id + WHERE finished = 'true' + GROUP BY camps.id, users.name, camps.title, camps.description + ORDER BY created_at + ; + ` + ); + + return campQuery.rows; + +} + +//info +async function LatestNews() { + + const campQuery = await db.query( + ` + SELECT message, author, created_at + FROM news + ORDER BY created_at DESC + LIMIT 1; + ` + ); + + return campQuery.rows[0]; + +} + + +//HELPERS +async function LastNodeInCamp(campId) { + + const last_node_q = await db.query( + ` + SELECT + nodes_0.id as node_id, + creator_id, + created_at, + finished_at, + users.name as creator_name, + scenarios_0.prompt + FROM nodes_0 + JOIN scenarios_0 ON scenarios_0.node_id = nodes_0.id + JOIN users on users.id = nodes_0.creator_id + WHERE camp_id = $1 + ORDER BY nodes_0.id DESC + LIMIT 1; + `, + [campId] + ); + const node = last_node_q.rows[0]; + return node; + +} + +//EXPORT +module.exports = { + Feed, + LastNodeInCamp, + CampData, + PlayersInCamp, + ScenariosInCamp, + ActiveCamps, + PlayerCamps, + PlayerStats, + FinishedStories, + LatestNews, + GetCampPlayersExpoTokens, + PlayerName, + Player, + StoryTitle, + PlayerWithGoogleToken +}; \ No newline at end of file diff --git a/database/dbFunctions.js b/database/dbFunctions.js new file mode 100644 index 0000000..50ddde9 --- /dev/null +++ b/database/dbFunctions.js @@ -0,0 +1,771 @@ +const db = require('./dbConnect.js'); +const bcrypt = require('bcrypt'); +const { SendStrikeNotification, SendKickNotification, SendTurnNotification } = require('../notifications/notifications.js'); +const { ValidateChars } = require('../middleware/validation'); + +//AUTH +async function CreateUser(name, email, password, pushToken) { + + const checkEmail = await db.query('SELECT * FROM users WHERE email = $1', [email]); + if (checkEmail.rowCount > 0) throw new Error('email already in use'); + + const checkName = await db.query('SELECT * FROM users WHERE name = $1', [name]); + if (checkName.rowCount > 0) throw new Error('display name already taken'); + + const salt = await bcrypt.genSalt(10); + const hash = await bcrypt.hash(password, salt); + + await db.query( + 'INSERT INTO users (name, email, password, expo_push_token) VALUES ($1, $2, $3, $4) RETURNING *', + [name, email, hash, pushToken] + ); + +} +async function Login(email, password) { + + const query = await db.query( + 'SELECT * FROM users WHERE email = $1', [email] + ); + + if (!query.rows[0]) throw new Error('user with that email does not exist') + if (!await bcrypt.compare(password, query.rows[0].password)) throw new Error('wrong password. hash is: ' + hash); + + return query.rows[0]; + +} + +//GETTERS +async function GetRoomInfo(roomId) { + const roomQuery = await db.query( + 'SELECT * FROM rooms WHERE id=$1', + [roomId] + ); + + MakeSureRoomExists(roomQuery); + + return roomQuery.rows[0]; +} +async function GetPlayersInRoom(roomId) { + const query = await db.query( + `SELECT + users.id, + users.name, + rooms_users.char_count, + active, + strikes + FROM rooms_users + JOIN users ON rooms_users.user_id = users.id + WHERE rooms_users.room_id = $1 + ORDER BY rooms_users.id`, + [roomId] + ); + + return query.rows; +} +async function GetActivePlayers(roomId) { + const query = await db.query( + `SELECT users.id, users.name, rooms_users.char_count, active, strikes + FROM rooms_users + JOIN users ON rooms_users.user_id = users.id + WHERE rooms_users.room_id = $1 + AND rooms_users.active = true + ORDER BY rooms_users.id`, + [roomId] + ); + + return query.rows; +} +const GetScenariosInRoom = async (roomId) => { + + const scenarioQuery = await db.query( + `SELECT scenario, creator_id, prompt + FROM scenarios + JOIN nodes ON scenarios.node_id = nodes.id + WHERE nodes.room_id = $1 + AND scenario IS NOT NULL + ORDER BY nodes.id;`, + [roomId] + ); + + return scenarioQuery.rows; + +} + +async function GetCurrentPrompt(roomId) { + + const q = await db.query( + `SELECT prompt + FROM scenarios + JOIN nodes ON scenarios.node_id = nodes.id + WHERE nodes.room_id = $1 + ORDER BY nodes.id DESC + LIMIT 1`, + [roomId] + ); + + return q.rows[0].prompt; + +} + +async function GetNextPlayerId(roomId, currentPlayerId) { + + //get all the active players in order + const players = await GetPlayersInRoom(roomId) + + //find the index of the player that is after the current one + let i = 0; + players.forEach((player, j) => { + if (player.id != currentPlayerId) return; + if (j == (players.length - 1)) return; + i = j + 1; + }); + + //increment index until we find a player that is still active + while (!players[i].active) { + i++; + if (i >= players.length) i = 0; + } + + //return the id of the player + const nextPlayerId = players[i].id; + return nextPlayerId; +} +async function GetLoggedUserInfo(id) { + if (!id) throw new Error('No user to query for user info'); + + const query = await db.query('SELECT * FROM users WHERE id=$1', [id]); + + if (!query.rows) throw new Error('Query returned nothing'); + if (query.rowCount < 1) throw new Error('Found no user with that id'); + if (query.rows.length > 2) throw new Error('Query returned multiple users'); + + return query.rows[0]; +} +async function GetPushToken(userId) { + const query = await db.query('SELECT expo_push_token FROM users WHERE id = $1', [userId]); + if (query.rowCount != 0) return query.rows[0].expo_push_token; + else return null; +} +async function GetUserChars(roomId, userId) { + const query = await db.query( + `SELECT char_count + FROM rooms_users + WHERE room_id = $1 AND user_id = $2`, + [roomId, userId] + ) + if (query.rowCount != 0) return query.rows[0].char_count; + else return null; +} +async function GetScenarioCount(roomId) { + + const q = await db.query( + `SELECT COUNT(*) AS count + FROM nodes + WHERE room_id = $1 + AND creator_id IS NOT NULL`, + [roomId] + ); + + const { count } = q.rows[0]; + + return count; + +} +async function GetDeadline(roomId) { + + console.log('getting deadline for room with id: ', roomId); + + const q = await db.query( + `SELECT turn_end + FROM rooms + WHERE id = $1`, + [roomId] + ); + + const deadline = q.rows[0].turn_end; + + return deadline; + +} +async function GetScenarioFeed() { + + const q = await db.query( + `SELECT + rooms.title AS story_title, + rooms.id AS room_id, + nodes.creator_id AS creator_id, + users.name AS creator_name, + nodes.id AS scenario_id, + scenarios.scenario, + nodes.created_at + FROM nodes + JOIN scenarios ON scenarios.node_id = nodes.id + JOIN rooms ON rooms.id = nodes.room_id + JOIN users ON users.id = nodes.creator_id + WHERE scenarios.scenario IS NOT NULL + ORDER BY nodes.id DESC + LIMIT 25` + ); + + return q.rows; + +} +async function GetRandomPrompt() { + + const q = await db.query( + `SELECT prompt + FROM prompts + ORDER BY random() + LIMIT 1;` + ); + + const prompt = q.rows[0]; + + return prompt; + +} +async function GetPlayerStats(id) { + + const q = await db.query( + `SELECT + COUNT(*) AS camps, + SUM (CASE + WHEN rooms.finished = TRUE THEN 1 + ELSE 0 + END + ) AS finished, + ( + SELECT COUNT(*) + FROM nodes + WHERE creator_id = $1 + ) AS contributions, + ( + SELECT COUNT(*) + FROM users + WHERE id = $1 + ) AS user_exist + FROM rooms_users + JOIN rooms ON rooms.id = rooms_users.room_id + WHERE rooms_users.user_id = $1;`, + [id] + ); + + const stats = q.rows[0]; + + if (stats.user_exist == 0) return null; + + return stats; + +} + +//CHECKS +function MakeSurePlayerHasEnoughChars(players, scenario, userId) { + players.forEach(player => { + if (player.id == userId && player.char_count < scenario.length) { + throw new Error('player does not have enough characters left'); + }; + }); +} +function MakeSurePlayerIsActive(players, userId) { + let playerFound = false; + players.forEach(player => { + if (player.id == userId) { + playerFound = true; + if (!player.active) throw new Error('player has been kicked'); + }; + }); + + if (!playerFound) throw new Error('player is not in this room'); +} +async function MakeSureItsNotTheLastTurn(roomId) { + const count = await GetScenarioCount(roomId); + if (count >= 39) throw Error('Scenario limit reached! Must create ending'); +} +async function MakeSureItsNotFinished(roomId) { + const q = await db.query(` + SELECT finished + FROM rooms + WHERE id = $1 + `, [roomId]); + + const finished = q.rows[0].finished; + if (finished) throw new Error('Story has already been finished'); +} +function MakeSureItsPlayersTurn(room, userId) { + + console.log('room is: ', room); + + if (!room.next_player_id) { + throw new Error(`It's not the players turn to write!`); + } + + if (!room.next_player_id || room.next_player_id != userId) { + throw new Error(`its not the logged players turn. poster id was ${userId}, but its actually user with id ${room.next_player_id} who is next`); + } + +} +function MakeSureRoomExists(roomQuery) { + if (roomQuery.rowCount == 0) + throw new Error('No room found with the given id'); +} +async function EmailExists(email) { + + const query = await db.query(` + SELECT * + FROM users + WHERE email = $1 + `, [email]); + + return (query.rowCount > 0); + +} +async function PlayerHasWrittenInRoom(roomID, userID) { + + const query = await db.query(` + SELECT * + FROM nodes + WHERE room_id = $1 AND creator_id = $2 + `, [roomID, userID]); + + return (query.rowCount > 0); + +} +async function CanEnd(roomId) { + + const count = await GetScenarioCount(roomId); + return (count >= 30); //this value should be grabbed from a balancing sheet + +} + +//SETTERS +async function AddScenario(scenario, roomId, userId) { + const scenarioQuery = await db.query( + `WITH node_set AS ( + UPDATE nodes + SET creator_id = $2, + created_at = NOW() + WHERE id = ( + SELECT id + FROM nodes + WHERE room_id = $3 + ORDER BY id DESC + LIMIT 1 + ) + RETURNING id + ) + UPDATE scenarios + SET scenario = $1 + WHERE node_id = (SELECT id FROM node_set) + RETURNING node_id;`, + [scenario, userId, roomId] + ); + return scenarioQuery.rows[0].node_id; +} +async function CreateNewNode(roomId) { + + const q = await db.query( + `WITH new_node AS ( + INSERT INTO nodes (room_id) + VALUES ($1) + RETURNING id + ) + INSERT INTO scenarios (node_id, prompt) + VALUES ( + (SELECT id FROM new_node), + (SELECT prompt FROM prompts ORDER BY random() LIMIT 1) + ) + RETURNING scenarios.node_id;`, + [roomId] + ); + + return q.rows[0].node_id; + +} +async function UpdateCharCount(scenario, roomId, userId) { + await db.query( + 'UPDATE rooms_users SET char_count = (char_count - $1 + 500) WHERE user_id = $2 AND room_id = $3', + [scenario.length, userId, roomId] + ); +} +async function GiveKeyToEachPlayer(roomId) { + await db.query( + ` + UPDATE users + SET room_keys = room_keys + 1 + WHERE EXISTS( + SELECT FROM rooms_users + WHERE rooms_users.room_id = $1 + AND rooms_users.user_id = users.id + ); + `, + [roomId] + ); +} +async function SetNextPlayerInRoom(roomId, userId) { + await db.query( + 'UPDATE rooms SET next_player_id=$1 WHERE id=$2', + [userId, roomId] + ); +} +async function UpdateRoomFullStatus(roomId) { + await db.query( + `UPDATE rooms + SET "full"=((SELECT COUNT(*) FROM rooms_users WHERE rooms_users.room_id = rooms.id) >= 4) + WHERE id = $1`, + [roomId] + ); +} +async function SetDeadlineIn2Days(roomId) { + await db.query( + `UPDATE rooms SET turn_end=(NOW() + interval '2 day') WHERE id=$1`, + [roomId] + ); +} +async function SetDeadlineIn30Min(roomId) { + await db.query( + `UPDATE rooms SET turn_end=(NOW() + interval '30 min') WHERE id=$1`, + [roomId] + ); +} +async function AddUserToRoom(roomId, userId) { + await db.query( + 'INSERT INTO rooms_users (room_id, user_id) VALUES ($1, $2)', + [roomId, userId] + ); +} +async function CreateNewRoom(title, description, scenario, creator_id) { + const roomId = await AddRoom(title, description, creator_id); + await AddUserToRoom(roomId, creator_id); + await CreateNewNode(roomId); + await AddScenario(scenario, roomId, creator_id); + await CreateNewNode(roomId); + return roomId; +} +async function AddRoom(title, description, creator_id) { + const query = await db.query( + 'INSERT INTO rooms(title, description, creator_id) VALUES($1, $2, $3) RETURNING *', + [title, description, creator_id] + ); + return query.rows[0].id; +} +async function AddUserToRoom(roomID, userID) { + await db.query( + 'INSERT INTO rooms_users(room_id, user_id) VALUES($1, $2)', + [roomID, userID] + ); +} +async function RemoveKeyFromLoggedUser(userId) { + await db.query( + 'UPDATE users SET room_keys = room_keys-1 WHERE id = $1', + [userId] + ); +} +async function AddStrike(roomId, userId) { + + const query = await db.query( + `UPDATE rooms_users + SET strikes = LEAST(3, strikes + 1) + WHERE room_id = $1 AND user_id = $2 + RETURNING strikes`, + [roomId, userId] + ); + + return query.rows[0].strikes; + +} +async function DeactivatePlayer(roomId, userId) { + + await db.query(` + UPDATE rooms_users + SET active = false + WHERE room_id = $1 AND user_id = $2 + `, [roomId, userId]); + +} +async function SetRoomSearching(roomId) { + + await db.query(` + UPDATE rooms + SET "full"=false, next_player_id=null, turn_end=null + WHERE id=$1 + `, [roomId]); + +} +async function Add2DaysToDeadline(room) { + + let newTurnEnd; + if (room.turn_end) newTurnEnd = new Date(new Date(room.turn_end).getTime() + 172800000); + else newTurnEnd = new Date(new Date().getTime() + 172800000); + + await db.query(` + UPDATE rooms + SET turn_end = $2 + WHERE id = $1 + `, [room.id, newTurnEnd]); + +} +async function SetRoomFull(room, players, scenarios) { + + if (room.full && room.next_player_id && room.turn_end) return; + + const nextPlayerId = ( + room.next_player_id ? + room.next_player_id : + GetNextPlayerId(players, scenarios[scenarios.length - 1].creator_id) + ); + + await db.query(` + UPDATE rooms + SET + "full" = true, + next_player_id = $1 + WHERE id = $2 + `, [nextPlayerId, room.id] + ); + + const mustUpdateTurnEnd = (!room.turn_end || room.turn_end < new Date()); + if (mustUpdateTurnEnd) await Add2DaysToDeadline(room); + +} +async function CorrectRoomSearching(room, players, scenarios) { + + console.log('checking if room search needs to be corrected'); + + const activePlayers = players.filter(player => player.active); + const roomSetToSearching = (!room.full && !room.turn_end && !room.next_player_id); + const isNewRoom = (scenarios.length < 4); + + //new room logic + if (isNewRoom) { + if (scenarios.length >= activePlayers.length) { + if (roomSetToSearching) { + return false; + } + else { + console.log('new room, more scenarios than active players, and room is not yey searching - setting searching now!'); + await SetRoomSearching(room.id); + return true; + } + } + else { + if (!room.turn_end && !room.next_player_id) { + console.log(` + New room, + more or equal amount of active players to scenarios, + turn end and next player id is null, + -> setting full to false, turn end in 2 days, and next player id to something + `); + await db.query(` + UPDATE rooms + SET + "full" = false, + turn_end = NOW() + Interval '2 day', + next_player_id = $1 + WHERE id = $2 + `, [activePlayers[activePlayers.length - 1].id, room.id]); + return true; + } + else { + return false; + } + } + } + + //old room logic + if (activePlayers.length == 4) { + if (room.full && room.next_player_id && room.turn_end) return false; + else { + await SetRoomFull(room, players, scenarios); + return true; + } + } + else { + if (!room.full && !room.turn_end && !room.next_player_id) return false; + else { + await SetRoomSearching(room.id); + return true; + } + } + +} +async function CheckRoomDeadline(room) { + + //checking if deadline has been reached + const q = await db.query(` + SELECT (turn_end < NOW()) AS passed + FROM rooms + WHERE id = $1; + `, [room.id]); + const { passed } = q.rows[0]; + + //handling and returning true if it was + if (passed) await HandleDeadlinePassed(room); + return passed; + +} +async function HandleDeadlinePassed(room) { + + console.log('deadline passed for room with id: ', room.id); + + const playerThatMissedID = room.next_player_id; + + //kick if new player + const isNewPlayer = !(await PlayerHasWrittenInRoom(room.id, playerThatMissedID)); + if (isNewPlayer) { + console.log(`Kicking new player ${playerThatMissedID} from room ${room.id}`); + await DeactivatePlayer(room.id, playerThatMissedID); + await SetRoomSearching(room.id); + return; + } + + //add strike + const strikes = await AddStrike(room.id, playerThatMissedID); + + //get push token + const pushToken = await GetPushToken(playerThatMissedID); + + //kick if 3 strikes + if (strikes >= 3) { + console.log(`Kicking player ${playerThatMissedID} from room ${room.id}`); + await DeactivatePlayer(room.id, playerThatMissedID); + await SetRoomSearching(room.id); + SendKickNotification(pushToken, room.title); + return; + } + + //pass the turn + await PassTurn(room, playerThatMissedID); + SendStrikeNotification(pushToken, room.title, strikes, room.id, playerThatMissedID); +} +async function CheckRoomInfo(room, players, scenarios) { + + console.log('checking the room info'); + + //this function is a bit bloated. Wierd that it is separated in 2. + //Should probably just check everything here. maybe... + + //this function returns TRUE if a correction was made to who is the next player + + //check to see if the room has space and if so set it searching for new players + const roomSearchingCorrected = await CorrectRoomSearching(room, players, scenarios); + if (roomSearchingCorrected) { + room = await GetRoomInfo(room.id); + } + const turnPassed = await CheckRoomDeadline(room); + return (turnPassed || roomSearchingCorrected); + +} +async function PassTurn(room, currentPlayerId) { + + if (!room.full) { + SetRoomSearching(room.id); + return; + } + + // const players = await GetPlayersInRoom(room.id); + const nextPlayerId = await GetNextPlayerId(room.id, currentPlayerId); + await SetNextPlayerInRoom(room.id, nextPlayerId) + await SetDeadlineIn2Days(room.id); + const nextPlayer = await GetLoggedUserInfo(nextPlayerId); + SendTurnNotification(nextPlayer.expo_push_token, room.id, room.title, nextPlayerId); + console.log('turn passed :)'); + +} +async function EndStory(roomId) { + GiveKeyToEachPlayer(roomId); + db.query( + `UPDATE rooms + SET + turn_end = null, + next_player_id = null, + finished = true + WHERE id = $1`, + [roomId] + ); +} +async function AddPasswordResetCode(code, userEmail) { + + await db.query(` + UPDATE users + SET + password_reset_code = $1, + password_reset_timeout = NOW() + Interval '30 min' + WHERE email = $2; + `, [code, userEmail]); + + return; + +} + +//TRANSACTIONS +async function BeginTransaction() { + await db.query('BEGIN'); +} +function Rollback() { + db.query('ROLLBACK'); +} +async function Commit() { + await db.query('COMMIT'); +} +async function TryTransaction(req, res, next) { + + try { + await BeginTransaction(); + await req.Transaction(); //attach the query function you want to user + await Commit(); + res.send(req.responseMessage); //attach a response message to send on a successfull transaction + } + catch (error) { + Rollback(); + console.error(error); + res.status(400).send({ ok: false, message: error.message }); + } + +} + +//EXPORT +module.exports = { + GetRoomInfo, + GetPlayersInRoom, + GetScenariosInRoom, + MakeSureRoomExists, + MakeSurePlayerHasEnoughChars, + MakeSureItsNotTheLastTurn, + MakeSureItsNotFinished, + MakeSureItsPlayersTurn, + AddScenario, + BeginTransaction, + Rollback, + UpdateCharCount, + GiveKeyToEachPlayer, + Commit, + TryTransaction, + RemoveKeyFromLoggedUser, + CreateNewRoom, + SetDeadlineIn2Days, + SetDeadlineIn30Min, + UpdateRoomFullStatus, + SetNextPlayerInRoom, + AddUserToRoom, + CreateUser, + GetLoggedUserInfo, + Login, + GetPushToken, + GetNextPlayerId, + CheckRoomInfo, + MakeSurePlayerIsActive, + PassTurn, + EndStory, + GetUserChars, + HandleDeadlinePassed, + CheckRoomDeadline, + EmailExists, + AddPasswordResetCode, + CanEnd, + GetDeadline, + GetScenarioFeed, + GetRandomPrompt, + GetPlayerStats, + CreateNewNode, + GetCurrentPrompt +}; \ No newline at end of file diff --git a/database/dbPosts.js b/database/dbPosts.js new file mode 100644 index 0000000..3d93894 --- /dev/null +++ b/database/dbPosts.js @@ -0,0 +1,332 @@ +//POST STUFF TO THE DATABASE +const db = require('./dbConnect.js'); +const dbData = require('./dbData'); +const notifications = require('../notifications/notifications'); + +//PLAYERS +async function NewPlayer(googleId, googleToken) { + + const addPlayerQ = await db.query( + ` + INSERT INTO users (google_id, google_token) + VALUES ($1, $2) + RETURNING * + `, + [googleId, googleToken] + ); + + if (addPlayerQ.rowCount > 0) { + const player = addPlayerQ.rows[0]; + console.log('added new player with id: ', player.id); + return player; + } + else { + console.error('failed to add new player. query returned null'); + return null; + } + +} +async function UpdateGoogleToken(googleId, googleToken) { + + const updatePlayerQ = await db.query( + ` + UPDATE users + SET google_token = $1 + WHERE google_id = $2 + RETURNING * + `, + [googleToken, googleId] + ); + + if (updatePlayerQ.rowCount > 0) { + const player = updatePlayerQ.rows[0]; + console.log('updatet google token for player with id: ', player.id); + return player; + } + else { + console.error('failed to update google token for player. query returned null'); + return null; + }; + +} +async function Name(googleToken, name) { + + //Check if the name is already taken + const nameExistsQ = await db.query( + ` + SELECT google_token + FROM users + WHERE name = $1; + `, + [name] + ); + + if (nameExistsQ.rowCount > 0) { + if (nameExistsQ.rows[0].google_token == googleToken) throw new Error('User already has that name'); + else throw new Error('Name is already taken'); + } + + //if not, set it! + const nameUpdateQ = await db.query( + ` + UPDATE users + SET name = $1 + WHERE google_token = $2 + RETURNING * + `, + [name, googleToken] + ) + if (nameUpdateQ.rowCount == 0) throw new Error('found no user with that token'); + +} +async function ExpoToken(googleToken, expoToken) { + + notifications.IsExpoToken(expoToken); + + //if not, set it! + const tokenUpdateQ = await db.query( + ` + UPDATE users + SET expo_push_token = $1 + WHERE google_token = $2 + `, + [expoToken, googleToken] + ) + if (tokenUpdateQ.rowCount == 0) throw new Error('found no user with that google token'); + +} +async function StampLogin(userId) { + + try { + + await db.query( + ` + INSERT INTO logins (user_id) + VALUES ($1) + `, + [userId] + ); + + } + catch (error) { + + console.error('failed to stamp user login: ', error.message); + + } + +} + +//NODES +async function Node(campId, userId) { + + const q = await db.query( + ` + SELECT finished_at, id + FROM nodes_0 + WHERE camp_id = $1 + ORDER BY id DESC + LIMIT 1; + `, + [campId] + ); + + const { finished_at } = q.rows[0]; + const last_node_id = q.rows[0].id; + + if (finished_at) { + + //the last node is finished, and we need to add one + const nodeQ = await db.query( + ` + INSERT INTO nodes_0 (creator_id, camp_id) + VALUES ($1, $2) + RETURNING id + `, + [userId, campId] + ); + const newNodeId = nodeQ.rows[0].id; + const scenarioAddQ = await db.query( + ` + INSERT INTO scenarios_0 (node_id, prompt) + VALUES ( + $1, + (SELECT prompt FROM prompts ORDER BY random() LIMIT 1) + ) + RETURNING prompt; + `, + [newNodeId] + ) + const { prompt } = scenarioAddQ.rows[0]; + return prompt; + + } + else { + + //the last node is NOT finished, and we need to update it + await db.query( + ` + UPDATE nodes_0 + SET + creator_id = $1, + created_at = NOW() + WHERE + finished_at IS NULL + AND camp_id = $2 + `, + [userId, campId] + ); + const scenarioQ = await db.query( + ` + SELECT prompt + FROM scenarios_0 + WHERE node_id = $1 + `, + [last_node_id] + ) + const { prompt } = scenarioQ.rows[0]; + return prompt; + + } + + + +} +async function Scenario(campId, text, isEnd) { + + const lastNode = await dbData.LastNodeInCamp(campId); + const lastNodeId = lastNode.node_id; + + //insert text into scenarios + await db.query( + ` + UPDATE scenarios_0 + SET scenario = $1 + WHERE node_id = $2 + `, + [text, lastNodeId] + ); + + //add a finished at stamp in the node + await db.query( + ` + UPDATE nodes_0 + SET finished_at = now() + WHERE id = $1 + `, + [lastNodeId] + ); + + if (!isEnd) return; + + //if end + + //set camp to finished + await db.query( + ` + UPDATE camps + SET finished = 'true' + WHERE id = $1 + `, + [campId] + ); + + //hand out logs to every participant + await db.query( + ` + WITH players_in_camp AS ( + SELECT creator_id + FROM nodes_0 + WHERE camp_id = $1 + GROUP BY creator_id + ) + UPDATE users + SET room_keys = room_keys + 1 + FROM players_in_camp + WHERE users.id = players_in_camp.creator_id + `, + [campId] + ); + +} + +//LIKES +async function Like(nodeId, userId) { + + const likeQ = await db.query( + ` + INSERT INTO likes (node_id, user_id) + VALUES ($1, $2) + ON CONFLICT DO NOTHING; + `, + [nodeId, userId] + ); + + if (likeQ.rowCount == 0) throw Error('user already liked that node'); + return; + +} +async function Dislike(nodeId, userId) { + + const dislikeQ = await db.query( + ` + DELETE FROM likes + WHERE node_id = $1 + AND user_id = $2 + `, + [nodeId, userId] + ); + + if (dislikeQ.rowCount == 0) throw Error('user doesnt like that node'); + return; + +} + +//CAMPS +async function Camp(title, /*description,*/ scenario, creator_id) { + + const campTitleQ = await db.query( + ` + SELECT id + FROM camps + WHERE title = $1 + `, + [title] + ); + + if (campTitleQ.rowCount > 0) throw new Error('Story title already exists.'); + + const campQ = await db.query( + ` + WITH new_camp AS( + INSERT INTO camps(title, creator_id) + VALUES($1, $2) + RETURNING id + ), new_node AS ( + INSERT INTO nodes_0 (creator_id, camp_id, finished_at) + VALUES ($2, (SELECT id FROM new_camp), now()) + RETURNING id + ) + INSERT INTO scenarios_0 (scenario, node_id) + VALUES ($3, (SELECT id FROM new_node)) + RETURNING (SELECT id FROM new_camp); + ` + , + [title, creator_id, scenario] + ); + const campId = campQ.rows[0].id; + return campId; + +} + +module.exports = { + AddNode: Node, + AddScenario: Scenario, + Camp, + Like, + Dislike, + NewPlayer, + UpdateGoogleToken, + Name, + ExpoToken, + StampLogin +}; \ No newline at end of file diff --git a/database/dbTransactions.js b/database/dbTransactions.js new file mode 100644 index 0000000..796fb08 --- /dev/null +++ b/database/dbTransactions.js @@ -0,0 +1,19 @@ +const db = require('./dbConnect.js'); + +//TRANSACTIONS +async function Begin() { + await db.query('BEGIN'); +} +function Rollback() { + db.query('ROLLBACK'); +} +async function Commit() { + await db.query('COMMIT'); +} + +//EXPORT +module.exports = { + Begin, + Rollback, + Commit +}; \ No newline at end of file diff --git a/database/dbUpdates.js b/database/dbUpdates.js new file mode 100644 index 0000000..39df7e2 --- /dev/null +++ b/database/dbUpdates.js @@ -0,0 +1,31 @@ +//Update rows in the database +const db = require('./dbConnect.js'); + +//users +async function RemoveLog(userId) { + + const keyQ = await db.query( + ` + SELECT room_keys + FROM users + WHERE id = $1 + `, + [userId] + ); + + if (keyQ.rows[0].room_keys == 0) throw new Error('User has no logs left!'); + + await db.query( + ` + UPDATE users + SET room_keys = room_keys - 1 + WHERE id = $1 + `, + [userId] + ); + +} + +module.exports = { + RemoveLog, +} \ No newline at end of file diff --git a/helpers/generateString.js b/helpers/generateString.js new file mode 100644 index 0000000..887ca9c --- /dev/null +++ b/helpers/generateString.js @@ -0,0 +1,11 @@ +function makeid(length) { + var result = ''; + var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +module.exports = {makeid}; \ No newline at end of file diff --git a/middleware/authentication.js b/middleware/authentication.js new file mode 100644 index 0000000..b174830 --- /dev/null +++ b/middleware/authentication.js @@ -0,0 +1,30 @@ +const dbData = require('../database/dbData'); + +const isAuth = async (req, res, next) => { + + try { + + const googleToken = req.headers['authorization']; + + if (typeof googleToken == 'undefined' || !googleToken) { + throw new Error('no google token provided in auth header'); + } + + const user = await dbData.PlayerWithGoogleToken(googleToken); + if (!user) throw new Error('no player with that google token found'); + + req.loggedUser = user; + + next(); + + } catch (error) { + + res.status(403).send({ + ok: false, + message: 'cant authorize user: ' + error.message + }); + + } +} + +module.exports = { isAuth }; \ No newline at end of file diff --git a/middleware/validation.js b/middleware/validation.js new file mode 100644 index 0000000..1c05863 --- /dev/null +++ b/middleware/validation.js @@ -0,0 +1,37 @@ +function ValidateCharsNoEmojis(text) { + const arr = text.split(""); + arr.forEach(char => { + if (!CharAllowedNoEmoji(char)) { + throw new Error(`The following character is not allowed: ${char}`); + } + }); +} + +function ValidateChars(text) { + const arr = text.split(""); + arr.forEach(char => { + if (!CharAllowed(char)) { + throw new Error(`The following character is not allowed: ${char}`); + } + }); +} + +function CharAllowedNoEmoji(char) { + const re = /[ A-Z a-z 0-9 . , ; : ' " ( ) @ # % / ! ? * = ½ -]/; + const allowed = re.test(char); + return allowed; +} + +function CharAllowed(char) { + const re = /[ A-Z a-z 0-9 . , ; : ' " ( ) @ # % / ! ? * = ½ \u00a9 \u00ae \u2000-\u3300 \ud83c \ud000-\udfff \ud83e \ud000-\udfff -]/; + const allowed = re.test(char); + return allowed; +} + +function CharsAllowed(str) { + const re = /^[ A-Z a-z 0-9 . , ; : ' " ( ) @ # % / ! ? * = ½ \u00a9 \u00ae \u2000-\u3300 \ud83c \ud000-\udfff \ud83e \ud000-\udfff -]+$/; + const allowed = re.test(str); + return allowed; +} + +module.exports = { CharsAllowed, ValidateChars, ValidateCharsNoEmojis }; \ No newline at end of file diff --git a/notifications/notifications.js b/notifications/notifications.js new file mode 100644 index 0000000..5f0c2c6 --- /dev/null +++ b/notifications/notifications.js @@ -0,0 +1,143 @@ +const { Expo } = require('expo-server-sdk'); +const dbData = require('../database/dbData'); + +let expo = new Expo(); + +//CHECK +const IsExpoToken = token => { + + if (!Expo.isExpoPushToken(token)) { + console.error(`Push token ${token} is not a valid Expo push token`); + } + +} + +//ACTIVE +const SendNotification = async (pushToken, title, body, data) => { + + IsExpoToken(pushToken); + + const message = { + to: pushToken, + sound: 'default', + title: title, + body: body, + data: data, + } + + const chunks = expo.chunkPushNotifications([message]); + const chunk = chunks[0]; + + try { + const ticketChunk = await expo.sendPushNotificationsAsync(chunk); + } catch (error) { + console.error(error); + } + +} + +const SendTestNotification = () => { + + SendNotification( + 'ExponentPushToken[ZeNN1xHXaxNE3Nl0NBQxMT]', + 'Scheduled notification', + 'Hello smoggy! you should receive this notification when heroku runs its script :)', + {} + ) + +} + +const SendScenarioNotifications = async (campId, creatorId, creatorName, storyTitle) => { + + //get the expo tokens for the given camp id EXCEPT for the poster + const tokens = await dbData.GetCampPlayersExpoTokens(campId, creatorId); + + //create the messages + let messages = []; + for (let token of tokens) { + + if (!Expo.isExpoPushToken(token)) { + console.error(`Push token ${token} is not a valid Expo push token`); + continue; + } + + messages.push({ + to: token, + sound: 'default', + body: `${creatorName} updated "${storyTitle}"!`, + data: { + type: 'scenario', + roomId: campId, + }, + }) + } + + //chunk and send them + const chunks = expo.chunkPushNotifications(messages); + let tickets = []; + (async () => { + for (let chunk of chunks) { + try { + let ticketChunk = await expo.sendPushNotificationsAsync(chunk); + tickets.push(...ticketChunk); + } catch (error) { + console.error(error); + } + } + })(); + +} + +//LEGACY +const SendTurnNotification = (pushToken, roomId, storyTitle, userId) => { + + SendNotification( + pushToken, + 'Your turn!', + storyTitle + ' was updated. You are the next player in line to write!', + { + type: 'turn', + roomId: roomId, + userId: userId + } + ); + +} + +const SendStrikeNotification = async (pushToken, storyTitle, strikes, roomId, userId) => { + + SendNotification( + pushToken, + `❌ You missed your turn in "${storyTitle}"`, + `You now have ${strikes} strikes. 3 Strikes and you are out!`, + { + type: 'strike', + roomId: roomId, + userId: userId + } + ) + +} + +const SendKickNotification = async (pushToken, storyTitle) => { + + SendNotification( + pushToken, + `You got kicked from "${storyTitle}"`, + `You missed 3 turns. You will no longer be able to contribute to this story`, + { + type: 'kick', + } + ) + +} + + +module.exports = { + SendTurnNotification, + SendStrikeNotification, + SendKickNotification, + SendTestNotification, + SendScenarioNotifications, + IsExpoToken +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c3961d6..bd526f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,1822 @@ { "name": "heroku-backend-final", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "heroku-backend-final", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "bcrypt": "^5.1.0", + "body-parser": "^1.20.1", + "dotenv": "^16.0.3", + "errorhandler": "^1.5.1", + "expo-server-sdk": "^3.7.0", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", + "morgan": "^1.10.0", + "node-cron": "^3.0.2", + "node-fetch": "^2.6.9", + "node-schedule": "^2.1.0", + "nodemailer": "^6.8.0", + "passport": "^0.6.0", + "passport-local": "^1.0.0", + "pg": "^8.8.0" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/cron-parser": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz", + "integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==", + "dependencies": { + "is-nan": "^1.3.2", + "luxon": "^1.26.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" + }, + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "dependencies": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expo-server-sdk": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/expo-server-sdk/-/expo-server-sdk-3.7.0.tgz", + "integrity": "sha512-SMZuBiIWejAdMMIOTjGQlprcwvSyLfeUQlooyGB5q6GvZ8zHjp+if8Q4k7xczUBTqIqTzs5IvTZnTiqA9Oe9WA==", + "dependencies": { + "node-fetch": "^2.6.0", + "promise-limit": "^2.7.0", + "promise-retry": "^2.0.1" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/luxon": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", + "engines": { + "node": "*" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", + "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" + }, + "node_modules/node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-schedule": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.0.tgz", + "integrity": "sha512-nl4JTiZ7ZQDc97MmpTq9BQjYhq7gOtoh7SiPH069gBFBj0PzD8HI7zyFs6rzqL8Y5tTiEEYLxgtbx034YPrbyQ==", + "dependencies": { + "cron-parser": "^3.5.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/nodemailer": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz", + "integrity": "sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/passport": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, + "node_modules/pg": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", + "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, + "node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + }, "dependencies": { + "@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "dependencies": { + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" } }, "array-flatten": { @@ -18,34 +1824,123 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + } + }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "requires": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" } }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } } }, "content-type": { @@ -54,15 +1949,24 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cron-parser": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz", + "integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==", + "requires": { + "is-nan": "^1.3.2", + "luxon": "^1.26.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -71,25 +1975,76 @@ "ms": "2.0.0" } }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" + }, + "errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "requires": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + } }, "escape-html": { "version": "1.0.3", @@ -99,79 +2054,222 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "expo-server-sdk": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/expo-server-sdk/-/expo-server-sdk-3.7.0.tgz", + "integrity": "sha512-SMZuBiIWejAdMMIOTjGQlprcwvSyLfeUQlooyGB5q6GvZ8zHjp+if8Q4k7xczUBTqIqTzs5IvTZnTiqA9Oe9WA==", + "requires": { + "node-fetch": "^2.6.0", + "promise-limit": "^2.7.0", + "promise-retry": "^2.0.1" + } }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" + }, + "dependencies": { + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" + }, + "dependencies": { + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + } } }, "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "iconv-lite": { @@ -182,16 +2280,150 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "luxon": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==" + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -213,16 +2445,58 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "requires": { - "mime-db": "1.44.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" } }, "ms": { @@ -231,14 +2505,79 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-addon-api": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", + "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" + }, + "node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "requires": { + "uuid": "8.3.2" + } }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-schedule": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.0.tgz", + "integrity": "sha512-nl4JTiZ7ZQDc97MmpTq9BQjYhq7gOtoh7SiPH069gBFBj0PzD8HI7zyFs6rzqL8Y5tTiEEYLxgtbx034YPrbyQ==", + "requires": { + "cron-parser": "^3.5.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + } + }, + "nodemailer": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz", + "integrity": "sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==" + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "on-finished": { "version": "2.3.0", @@ -248,29 +2587,175 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "passport": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + } + }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "requires": { + "passport-strategy": "1.x.x" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, + "pg": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", + "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", + "requires": {} + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "requires": { + "split2": "^4.1.0" + } + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, + "promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==" + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "requires": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } }, "range-parser": { "version": "1.2.1", @@ -278,16 +2763,39 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -298,58 +2806,152 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } } } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, + "split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "type-is": { "version": "1.6.18", @@ -365,15 +2967,62 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index a8c8110..0210f0f 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,21 @@ "author": "", "license": "ISC", "dependencies": { + "bcrypt": "^5.1.0", + "body-parser": "^1.20.1", + "dotenv": "^16.0.3", + "errorhandler": "^1.5.1", + "expo-server-sdk": "^3.7.0", "express": "^4.17.1", - "node-fetch": "^2.6.1" + "jsonwebtoken": "^8.5.1", + "morgan": "^1.10.0", + "node-cron": "^3.0.2", + "node-fetch": "^2.6.9", + "node-schedule": "^2.1.0", + "nodemailer": "^6.8.0", + "passport": "^0.6.0", + "passport-local": "^1.0.0", + "pg": "^8.8.0" }, - "devDependencies": {}, "description": "" } diff --git a/routers/campRouter.js b/routers/campRouter.js new file mode 100644 index 0000000..c52807a --- /dev/null +++ b/routers/campRouter.js @@ -0,0 +1,173 @@ +//IMPORTS +const express = require('express'); +const campRouter = express.Router(); + +const { isAuth } = require('../middleware/authentication'); +const { ValidateChars } = require('../middleware/validation'); + +const dbData = require('../database/dbData'); +const dbTransactions = require('../database/dbTransactions'); +const dbPosts = require('../database/dbPosts'); +const dbUpdates = require('../database/dbUpdates'); + +const balancing = require('../appInfo/balancing'); + +//FUNCTIONS +const CreateCamp = async (req, res, next) => { + + let transactionInitiated = false; + + try { + + const userId = req.loggedUser.id; + const { title, description, scenario } = req.query; + const balanceSheet = balancing.numbers; + + //ERROR CHECKS + if (!title) throw new Error('Please provide a title'); + if (title.length < balanceSheet.titleMinChars) throw new Error('Title is too short. Minimum chars: ', titleMinChars); + if (title.length > balanceSheet.titleMaxChars) throw new Error('Title is too long. Maximum chars: ', titleMaxChars); + // if (!description) throw new Error('Please provide a description'); + // if (description.length < balanceSheet.descriptionMinChars) throw new Error('Description is too short. Minimum chars: ', descriptionMinChars); + // if (description.length > balanceSheet.descriptionMaxChars) throw new Error('Description is too long. Maximum chars: ', descriptionMaxChars); + if (!scenario) throw new Error('Please provide a starting scenario'); + if (scenario.length < balanceSheet.scenarioMinCharacter) throw new Error('Scenario is too short. Minimum chars: ', scenarioMinCharacter); + if (scenario.length > balanceSheet.scenarioMaxCharacters) throw new Error('Scenario is too long. Maximum chars: ', scenarioMaxCharacters); + ValidateChars(title); + // ValidateChars(description); + ValidateChars(scenario); + + //TRY ADD TO DATABASE + await dbTransactions.Begin(); + transactionInitiated = true; + await dbUpdates.RemoveLog(userId); + // const campId = await dbPosts.Camp(title, description, scenario, userId); + const campId = await dbPosts.Camp(title, scenario, userId); + await dbTransactions.Commit(); + + res.status(200).send({ + ok: true, + message: 'New camp created successfully!', + campId: campId + }); + + } + catch (error) { + + if (transactionInitiated) dbTransactions.Rollback(); + const message = 'Failed to create camp: ' + error.message; + console.error(message); + res.status(400).send({ + ok: false, + message: message + }); + + } + +} + +const GetCampData = async (req, res, next) => { + + try { + + const campId = req.params.id; + + const camp = await dbData.CampData(campId); + const players = await dbData.PlayersInCamp(campId); + const scenarios = await dbData.ScenariosInCamp(campId); + const lastNode = await dbData.LastNodeInCamp(campId); + + camp.players = players; + camp.scenarios = scenarios; + camp.lastNode = lastNode; + + res.status(200).send(camp); + + } + catch (error) { + + console.error(error); + res.status(400).send('Failed to get room: ' + error.message); + + } + +} + +const GetActiveCamps = async (req, res, next) => { + + try { + + if (!req.loggedUser.id) throw new Error('no user ID attached in req'); + const camps = await dbData.ActiveCamps(req.loggedUser.id); + res.status(200).send({ + ok: true, + message: 'found camps', + data: camps + }); + + } + catch (error) { + + console.error(error); + res.status(400).send('Failed to get active camps: ' + error.message); + + } + +} + +const GetPlayerCamps = async (req, res, next) => { + + try { + + const { id } = req.loggedUser; + if (!id) throw new Error('cant fetch player camps because no userId was attached in the req'); + const camps = await dbData.PlayerCamps(id); + res.status(200).send({ + ok: true, + message: 'found camps', + data: camps + }); + + } + catch (error) { + + console.error(error); + res.status(400).send('Failed to get active camps: ' + error.message); + + } + +} + +const GetFinishedStories = async (req, res, next) => { + + try { + + const camps = await dbData.FinishedStories(); + res.status(200).send({ + ok: true, + message: 'found camps', + data: camps + }); + + } + catch (error) { + + console.error(error); + res.status(400).send('Failed to get active camps: ' + error.message); + + } + +} + + + +//ROUTES +campRouter.use(isAuth); +campRouter.get('/data/:id', GetCampData); +campRouter.get('/active', GetActiveCamps); +campRouter.get('/player', GetPlayerCamps); +campRouter.get('/finished', GetFinishedStories); +campRouter.post('/', CreateCamp); + +//ROUTER EXPORT +module.exports = campRouter; \ No newline at end of file diff --git a/routers/infoRouter.js b/routers/infoRouter.js new file mode 100644 index 0000000..169fda8 --- /dev/null +++ b/routers/infoRouter.js @@ -0,0 +1,32 @@ +const express = require('express'); +const infoRouter = express.Router(); +const dbData = require('../database/dbData'); +const { isAuth } = require('../middleware/authentication'); + +const GetLatestNews = async (req, res, next) => { + + try { + + const news = await dbData.LatestNews(); + + res.status(200).send({ + ok: true, + message: 'successfully retrieved latest news', + data: news + }) + + } + catch (error) { + + res.status(400).send({ + ok: false, + message: 'Could not retrieve latest news: ' + error.message, + }) + + } + +} + +infoRouter.get('/news', isAuth, GetLatestNews); + +module.exports = infoRouter; \ No newline at end of file diff --git a/routers/nodeRouter.js b/routers/nodeRouter.js new file mode 100644 index 0000000..fd7335a --- /dev/null +++ b/routers/nodeRouter.js @@ -0,0 +1,191 @@ +const express = require('express'); +const nodeRouter = express.Router(); + +const dbChecks = require('../database/dbChecks'); +const dbPosts = require('../database/dbPosts'); +const dbTransactions = require('../database/dbTransactions'); +const dbData = require('../database/dbData'); + +const { isAuth } = require('../middleware/authentication'); +const { ValidateChars } = require('../middleware/validation'); +const balancing = require('../appInfo/balancing'); +const notifications = require('../notifications/notifications'); + +const GetFeed = async (req, res, next) => { + + try { + + + const feed = await dbData.Feed(); + + if (!feed || feed.length < 1) { + console.error('could not get feed. backend threw back: ', feed); + throw new Error('could not get a feed'); + }; + + res.status(200).send({ + ok: true, + message: 'successfully retrieved scenario feed', + data: feed + }) + + } catch (error) { + + console.error(error); + res.status(400).send({ + ok: false, + message: error.message + }); + + } + +} + +const TryAddNode = async (req, res, next) => { + + let transactionInitiated = false; + + try { + + const userId = req.loggedUser.id; + const { campId } = req.query + + //initial error checks + if (!campId) throw new Error('Please provide a campId'); + if (!userId) throw new Error('no userId. Make sure you have a valid token and are logged correctly') + await dbChecks.CanAddNode(campId, userId); + + //post dat shit + await dbTransactions.Begin(); + transactionInitiated = true; + const prompt = await dbPosts.AddNode(campId, userId); + await dbTransactions.Commit(); + + //send response + res.send({ + ok: true, + message: 'Node added! Created a prompt', + data: { prompt } + }); + + } + catch (error) { + if (transactionInitiated) dbTransactions.Rollback(); + console.error(error); + res.status(400).send({ ok: false, message: error.message }); + } + +} + +const TryAddScenario = async (req, res, next) => { + + let transactionInitiated = false; + + try { + + //params + const userId = req.loggedUser.id; + const { campId, text, end } = req.query + const isEnd = (end == "true"); + + //checks + ValidateChars(text); + if (!campId) throw new Error('No room_id provided'); + if (!text) throw new Error('No text provided'); + if (text.length < balancing.numbers.scenarioMinCharacter) throw new Error('min chars in scenario: ', scenarioMinCharacters); + if (text.length > balancing.numbers.scenarioMaxCharacters) throw new Error('max chars in scenario: ', scenarioMaxCharacters); + if (!userId) throw new Error('no userId. Make sure you have a valid token and are logged correctly'); + await dbChecks.CanAddScenario(campId, userId, isEnd); + + //transaction + await dbTransactions.Begin(); + transactionInitiated = true; + await dbPosts.AddScenario(campId, text, isEnd); + await dbTransactions.Commit(); + + //notify everyone in a story! + const creatorName = await dbData.PlayerName(userId); + const storyTitle = await dbData.StoryTitle(campId); + notifications.SendScenarioNotifications(campId, userId, creatorName, storyTitle); + + //send response + let responseMessage = 'new scenario added!'; + if (isEnd) responseMessage = 'you ended the story! And got a log!'; + res.send({ ok: true, message: responseMessage }); + } + catch (error) { + if (transactionInitiated) dbTransactions.Rollback(); + console.error(error); + res.status(400).send({ ok: false, message: error.message }); + } + +} + +const Like = async (req, res, next) => { + + try { + //params + const userId = req.loggedUser.id; + const { nodeId } = req.query + + //checks + if (!nodeId) throw new Error('No nodeId provided'); + if (!userId) throw new Error('no userId. Make sure you have a valid token and are logged correctly'); + + //transaction + await dbPosts.Like(nodeId, userId); + + //send response + res.send({ + ok: true, + message: 'liked node successfully' + }); + } + catch (error) { + console.error(error); + res.status(400).send({ + ok: false, + message: ('unable to add like: ' + error.message) + }); + } + +} + +const Dislike = async (req, res, next) => { + + try { + //params + const userId = req.loggedUser.id; + const { nodeId } = req.query + + //checks + if (!nodeId) throw new Error('No nodeId provided'); + if (!userId) throw new Error('no userId. Make sure you have a valid token and are logged correctly'); + + //transaction + await dbPosts.Dislike(nodeId, userId); + + //send response + res.send({ + ok: true, + message: 'removed like successfully' + }); + } + catch (error) { + console.error(error); + res.status(400).send({ + ok: false, + message: ('unable to remove like: ' + error.message) + }); + } + +} + +nodeRouter.use(isAuth); +nodeRouter.post('/', TryAddNode); +nodeRouter.post('/scenario', TryAddScenario); +nodeRouter.post('/like', Like); +nodeRouter.post('/dislike', Dislike); +nodeRouter.get('/feed', GetFeed); + +module.exports = nodeRouter; \ No newline at end of file diff --git a/routers/roomRouter.js b/routers/roomRouter.js new file mode 100644 index 0000000..498731c --- /dev/null +++ b/routers/roomRouter.js @@ -0,0 +1,348 @@ +//LEGACY - will be replaced by camp router on update +const express = require('express'); +const roomRouter = express.Router(); +const db = require('../database/dbConnect.js'); +const dbFunctions = require('../database/dbFunctions'); +const { isAuth } = require('../middleware/authentication'); +const { ValidateChars } = require('../middleware/validation'); + +//GETTER FUNCTIONS +const GetRoomData = async (req, res, next) => { + + try { + + const roomId = req.params.id; + + let room = await dbFunctions.GetRoomInfo(roomId) + let players = await dbFunctions.GetPlayersInRoom(roomId); + const scenarios = await dbFunctions.GetScenariosInRoom(roomId); + + const roomCorrected = await dbFunctions.CheckRoomInfo(room, players, scenarios); + if (roomCorrected) { + room = await dbFunctions.GetRoomInfo(roomId); + players = await dbFunctions.GetPlayersInRoom(roomId); + } + + const prompt = await dbFunctions.GetCurrentPrompt(roomId); + + room.players = players; + room.scenarios = scenarios; + room.prompt = prompt; + + res.status(200).send(room); + } + catch (error) { + console.error(error); + res.status(400).send('Failed to get room: ' + error.message); + } + +} +const AttachAvailableRoomsQuery = async (req, res, next) => { + + try { + + //Easier to parse + // SELECT + // rooms.id, + // rooms.title AS title, + // rooms.description AS description, + // STRING_AGG(users.name, ';') AS user, + // (SELECT name FROM users WHERE id = rooms.creator_id) AS creator, + // (SELECT COUNT(*) FROM scenarios WHERE room_id = rooms.id) AS scenario_count + // FROM rooms + // JOIN rooms_users ON rooms.id = rooms_users.room_id + // JOIN users ON rooms_users.user_id = users.id + // WHERE + // rooms.full = false + // AND rooms.finished = false + // AND rooms.next_player_id IS NULL + // AND rooms_users.active = true + // AND NOT EXISTS(SELECT * FROM rooms_users WHERE user_id = 1 AND room_id = rooms.id) + // GROUP BY (rooms.id) + // ORDER BY id + + const availableRooms = await GetRoomsDb( + `SELECT + rooms.id, + rooms.title AS title, + rooms.description AS description, + users.name AS user, + (SELECT name FROM users WHERE id = rooms.creator_id) AS creator, + COUNT(scenarios.id) AS scenario_count + FROM rooms + JOIN rooms_users ON rooms.id = rooms_users.room_id + JOIN users ON rooms_users.user_id = users.id + JOIN scenarios ON scenarios.room_id = rooms.id + WHERE + rooms.full = false + AND rooms.finished = false + AND rooms.next_player_id IS NULL + AND rooms_users.active = true + AND NOT EXISTS(SELECT * FROM rooms_users WHERE user_id = $1 AND room_id = rooms.id) + GROUP BY (rooms_users.user_id, rooms.id, users.name) + ORDER BY id`, + [req.userId] + ) + + const newRooms = availableRooms.filter(room => + (room.scenario_count < 4) + ); + const oldRooms = availableRooms.filter(room => room.scenario_count >= 4); + + if (newRooms.length > 3) newRooms.splice(3, newRooms.length - 3); + if (oldRooms.length > 3) oldRooms.splice(3, oldRooms.length - 3); + + res.status(200).json({ new: newRooms, old: oldRooms }); + + } catch (error) { + + res.status(400).send('unable to get available rooms: ' + error.message); + + } +} +const AttachUserRoomsQuery = async (req, res, next) => { + + req.roomQuery = ( + `SELECT + rooms.id, + title, + description, + finished, + (SELECT name FROM users WHERE id = rooms.creator_id) AS creator, + (SELECT name FROM users WHERE id = rooms_users.user_id) AS user, + (SELECT COUNT(*) FROM scenarios WHERE room_id = rooms.id) AS scenario_count, + case when rooms.next_player_id = $1 then true else false end as users_turn + FROM rooms + JOIN rooms_users ON rooms_users.room_id = rooms.id + WHERE EXISTS ( + SELECT * FROM rooms_users + WHERE room_id = rooms.id + AND user_id = $1 + AND active = true + ) + AND rooms_users.active = true;` + ); + req.roomQueryParams = [req.userId]; + next(); + +} +const AttachArchiveQuery = async (req, res, next) => { + + req.roomQuery = ( + `SELECT + rooms.id, + title, + description, + (SELECT name FROM users WHERE id = rooms.creator_id) AS creator, + (SELECT name FROM users WHERE id = rooms_users.user_id) AS user, + (SELECT COUNT(*) FROM scenarios WHERE room_id = rooms.id) AS scenario_count + FROM rooms + JOIN rooms_users ON rooms_users.room_id = rooms.id + WHERE rooms.finished = true` + ); + req.roomQueryParams = []; + next(); + +} +const AttachOngoingRoomsQuery = async (req, res, next) => { + + req.roomQuery = ( + `SELECT + rooms.id, + title, + description, + (SELECT name FROM users WHERE id = rooms.creator_id) AS creator, + (SELECT name FROM users WHERE id = rooms_users.user_id) AS user, + (SELECT COUNT(*) FROM scenarios WHERE room_id = rooms.id) AS scenario_count + FROM rooms + JOIN rooms_users ON rooms_users.room_id = rooms.id + WHERE NOT EXISTS ( + SELECT * FROM rooms_users + WHERE room_id = rooms.id + AND user_id = $1 + AND active = true + ) + AND rooms_users.active = true + AND rooms.finished = false + AND rooms.full = true;` + ); + req.roomQueryParams = [req.userId]; + next(); + +} +const GetUserChars = async (req, res, next) => { + + try { + const { userId, roomId } = req.query; + if (!userId) throw new Error('must provide a userId to get the chars'); + if (!roomId) throw new Error('must provide a roomId to get the chars'); + const chars = await dbFunctions.GetUserChars(roomId, userId); + if (!chars) throw new Error('could not find any chars for that room and user'); + res.status(200).send({ + ok: true, + message: 'successfully found chars', + chars: chars + }) + } catch (error) { + console.error(error); + res.status(400).send({ + ok: false, + message: 'Failed to get chars: ' + error.message + }); + } + +} +const GetDeadline = async (req, res, next) => { + + try { + const { roomId } = req.query; + if (!roomId) throw new Error('must provide a roomId to get the deadline'); + + const deadline = await dbFunctions.GetDeadline(roomId); + if (!deadline) throw new Error('could not find any deadline for that room'); + res.status(200).send({ + ok: true, + message: 'successfully found deadline', + deadline: deadline + }) + } catch (error) { + console.error(error); + res.status(400).send({ + ok: false, + message: 'Failed to get deadline: ' + error.message + }); + } + +} + +//POST TRANSACTION FUNCTIONS +const AttachCreateRoomTransaction = async (req, res, next) => { + + req.Transaction = async () => { + + const title = req.query.title; + const description = req.query.description; + const scenario = req.query.scenario; + + //ERROR CHECKS + if (!title) throw new Error('Please provide a title'); + if (title.length < 3) throw new Error('Title must be at least 3 chars long'); + if (title.length > 50) throw new Error('Title can me maximum 50 characters long'); + + if (!description) throw new Error('Please provide a description'); + if (description.length < 3) throw new Error('Description must be at least 3 chars long'); + if (description.length > 200) throw new Error('Description can be at max 200 characters'); + + if (!scenario) throw new Error('Please provide a starting scenario'); + if (scenario.length < 20) throw new Error('Starting scenario must be at least 20 characters'); + + // ++ add this back later. Now it's a problem since there is no feedback in the frontend about it + // if (scenario.length > 500) throw new Error('Starting scenario can be at max 500 characters'); + + ValidateChars(title); + ValidateChars(description); + ValidateChars(scenario); + + //TRY ADD TO DATABASE + await dbFunctions.RemoveKeyFromLoggedUser(req.userId); + const newRoomId = await dbFunctions.CreateNewRoom(title, description, scenario, req.userId); + req.responseMessage = { success: true, message: 'new room added!', roomId: newRoomId }; + } + + next(); +} +const AttachJoinRoomTransaction = async (req, res, next) => { + + req.Transaction = async () => { + + const roomId = req.query.room_id; + if (!roomId) throw new Error('Please provide a room_id!'); + + //Checks + const room = await dbFunctions.GetRoomInfo(roomId); + if (room.full) throw new Error('Room is full'); + if (room.finished) throw new Error('Story has already been finished'); + + //update + await dbFunctions.AddUserToRoom(roomId, req.userId); + await dbFunctions.UpdateRoomFullStatus(roomId); + await dbFunctions.SetDeadlineIn30Min(roomId); + await dbFunctions.SetNextPlayerInRoom(roomId, req.userId); + + //response + req.responseMessage = 'Successfully joined the room!'; + } + + next(); +} + +//MOUNT ROUTes +roomRouter.use(isAuth); + +roomRouter.get('/data/:id', GetRoomData); +roomRouter.get('/user/chars', GetUserChars); +roomRouter.get('/deadline', GetDeadline); + +roomRouter.get('/available', AttachAvailableRoomsQuery, RetrieveRooms); +roomRouter.get('/user', AttachUserRoomsQuery, RetrieveRooms); +roomRouter.get('/archive', AttachArchiveQuery, RetrieveRooms); +roomRouter.get('/ongoing', AttachOngoingRoomsQuery, RetrieveRooms); + +roomRouter.post('/', AttachCreateRoomTransaction, dbFunctions.TryTransaction); +roomRouter.post('/join', AttachJoinRoomTransaction, dbFunctions.TryTransaction); + +//EXPORT +module.exports = roomRouter; + +//MIDDLEWARE +async function RetrieveRooms(req, res) { + + try { + const query = await db.query(req.roomQuery, req.roomQueryParams); + const rooms = ParseRoomsForFrontend(query.rows); + res.status(200).json(rooms); + } + catch (error) { + res.status(400).send('unable to get your rooms: ' + error.message); + } + +} + +async function GetRoomsDb(roomQuery, roomQueryParams) { + + const query = await db.query(roomQuery, roomQueryParams); + const rooms = ParseRoomsForFrontend(query.rows); + return rooms; + +} + +function ParseRoomsForFrontend(unparsedRooms) { + //I have like... no idea what this function does + //I think it organizes the room so that users, writers, creators, players etc + //are put in the way expected by frontend + let rooms = []; + unparsedRooms.forEach(room => { + + let roomAlreadyInArray = false; + + rooms.forEach(roomToCheck => { + if (roomToCheck.id == room.id) { + roomAlreadyInArray = true; + if (room.user == room.creator) + return; + roomToCheck.writers.push(room.user); + } + }); + + if (!roomAlreadyInArray) { + if (room.creator != room.user) { + room.writers = [room.user]; + } + else + room.writers = []; + delete room.user; + rooms.push(room); + } + }); + return rooms; +} diff --git a/routers/scenarioRouter.js b/routers/scenarioRouter.js new file mode 100644 index 0000000..e4c2dc7 --- /dev/null +++ b/routers/scenarioRouter.js @@ -0,0 +1,145 @@ +//LEGACY - will be replaced by node router on update + +const express = require('express'); +const scenarioRouter = express.Router(); +const dbFunctions = require('../database/dbFunctions'); +const dbChecks = require('../database/dbChecks'); +const { isAuth } = require('../middleware/authentication'); +const { ValidateChars } = require('../middleware/validation'); + +const TryAddScenario = async (req, res, next) => { + + let transactionInitiated = false; + + try { + + const userId = req.loggedUser.id; + const { roomId, text } = req.query + const isEnd = (req.query.end == "true"); + + await dbFunctions.MakeSureItsNotFinished(roomId); + + //checka om end vs scenario - att det verkligen är tillåtet + if (isEnd) { + const canEnd = await dbFunctions.CanEnd(roomId); + if (!canEnd) throw new Error(`Cant end the story yet! Not enough paragraphs written`); + } + else { + await dbFunctions.MakeSureItsNotTheLastTurn(roomId); + } + + //initial error checks + ValidateChars(text); + if (!roomId) throw new Error('No room_id provided'); + if (!text) throw new Error('No text provided'); + if (text.length < 3) throw new Error('text must be at least 3 characters long'); + if (!userId) throw new Error('no userId. Make sure you have a valid token and are logged correctly') + + //make queries + let room = await dbFunctions.GetRoomInfo(roomId); + let players = await dbFunctions.GetPlayersInRoom(roomId); + let scenarios = await dbFunctions.GetScenariosInRoom(roomId); + + const correctionsMade = await dbFunctions.CheckRoomInfo(room, players, scenarios); + if (correctionsMade) { + room = await dbFunctions.GetRoomInfo(roomId); + players = await dbFunctions.GetPlayersInRoom(roomId); + scenarios = await dbFunctions.GetScenariosInRoom(roomId); + } + + //some db checks + dbFunctions.MakeSurePlayerIsActive(players, userId); + dbFunctions.MakeSurePlayerHasEnoughChars(players, text, userId); + dbFunctions.MakeSureItsPlayersTurn(room, userId); + + //transaction (things in here will be rolled back on error) + await dbFunctions.BeginTransaction(); + transactionInitiated = true; + + const scenarioId = await dbFunctions.AddScenario(text, roomId, userId); + + if (isEnd) { + await dbFunctions.EndStory(roomId); + } + else { + await dbFunctions.CreateNewNode(roomId); + await dbFunctions.PassTurn(room, userId); + await dbFunctions.UpdateCharCount(text, roomId, userId); + } + + await dbFunctions.Commit(); + + //send response + let responseMessage; + if (!isEnd) responseMessage = 'new scenario added with id: ' + scenarioId + else responseMessage = 'you ended the story! And got a key!: ' + scenarioId; + res.send({ ok: true, message: responseMessage }); + } + catch (error) { + if (transactionInitiated) dbFunctions.Rollback(); + console.error(error); + res.status(400).send({ ok: false, message: error.message }); + } +} +const GetScenarioFeed = async (req, res, next) => { + + try { + + const feed = await dbFunctions.GetScenarioFeed(); + + if (!feed || feed.length < 1) { + console.error('could not get feed. backend threw back: ', feed); + throw new Error('could not get a feed'); + }; + + res.status(200).send({ + ok: true, + message: 'successfully retrieved scenario feed', + data: feed + }) + + } catch (error) { + + console.error(error); + res.status(400).send({ + ok: false, + message: error.message + }); + + } + +} +const GetPrompt = async (req, res, next) => { + + try { + + //get prompt from db + const prompt = await dbFunctions.GetRandomPrompt(); + + //check to make sure you got a valid one + if (!prompt || prompt.length < 1) throw new Error('could not get prompt'); + + res.status(200).send({ + ok: true, + message: 'successfully retrieved a prompt', + data: prompt + }) + + } catch (error) { + + console.error(error); + res.status(400).send({ + ok: false, + message: error.message + }); + + } + +} + +scenarioRouter.use(isAuth); +scenarioRouter.post('/', TryAddScenario); +scenarioRouter.get('/feed', GetScenarioFeed); +scenarioRouter.get('/prompt', GetPrompt); + +module.exports = scenarioRouter; \ No newline at end of file diff --git a/routers/userRouter.js b/routers/userRouter.js new file mode 100644 index 0000000..2842300 --- /dev/null +++ b/routers/userRouter.js @@ -0,0 +1,192 @@ +const express = require('express'); +const userRouter = express.Router(); +const dbData = require('../database/dbData'); +const dbPosts = require('../database/dbPosts'); +const { isAuth } = require('../middleware/authentication'); +const { ValidateCharsNoEmojis } = require('../middleware/validation'); +const fetch = require('node-fetch'); +const { StampLogin } = require('../database/dbPosts'); + +const GetUserInfo = async (req, res, next) => { + + try { + const googleToken = req.headers['authorization']; + const user = await dbData.PlayerWithGoogleToken(req.headers['authorization']) + res.json({ + id: user.id, + name: user.name, + room_keys: user.room_keys, + // email: user.email, + // premium: user.premium + }); + } + catch (error) { + res.status(400).send('Unable to get user. ' + error.message); + } + +} +const GetUserStats = async (req, res, next) => { + + try { + + const { userId } = req.query; + if (!userId) throw new Error('no user id provided in query. Cannot return stats'); + const stats = await dbData.PlayerStats(userId); + + res.status(200).send({ + ok: true, + message: 'successfully retrieved user stats', + data: stats + }) + + } + catch (error) { + + res.status(400).send({ + ok: false, + message: 'Could not retrieve stats: ' + error.message, + }) + + } + +} +const Login = async (req, res, next) => { + try { + + const googleToken = req.headers['authorization']; + if (!googleToken) throw new Error('no google token in auth header'); + + //fetch user from google using the token + let response = await fetch("https://www.googleapis.com/userinfo/v2/me", { + headers: { Authorization: `Bearer ${googleToken}` } + }); + const userInfo = await response.json(); + + if (userInfo.error) { + console.error('user tried to log in with invalid token. Error: ', userInfo.error); + throw new Error('Invalid google token'); + } + + //token is valid! and we got some info from the google api + const googleId = userInfo.id; + + //use it to get player from the db + let player = await dbData.Player(googleId); + + //correct anything that is missing in the user db + if (!player) { + player = await dbPosts.NewPlayer(googleId, googleToken); + if (!player) throw new Error('unable to add new player to the database'); + } + else if (player.google_token != googleToken) { + player = await dbPosts.UpdateGoogleToken(googleId, googleToken); + if (!player) throw new Error('unable to update google token in database'); + } + + //add a login row in db for tracking + await StampLogin(player.id); + + console.log('player logged in. ID: ', player.id); + + //and return the player! + res.status(201).send({ + ok: true, + message: 'succesfully logged in!', + data: { + player: { + id: player.id, + displayName: player.name, + keys: player.room_keys, + } + } + }); + + } + catch (error) { + console.log('player failed to login: ', error.message); + res.status(400).send({ + ok: false, + message: 'Cant login: ' + error.message, + }); + + } +} +const SetDisplayName = async (req, res, next) => { + + try { + + const { name } = req.query; + const googleToken = req.headers['authorization']; + + //checks + if (name.length < 3) throw new Error('Name must be at least 4 chars long'); + ValidateCharsNoEmojis(name); + + //post it + await dbPosts.Name(googleToken, name); + + //response + res.status(201).send({ + ok: true, + message: 'succesfully updated name!', + data: { + name: name + } + }); + + } + catch (error) { + + //fail response + res.status(400).send({ + ok: false, + message: 'Cant update name: ' + error.message, + }); + + } + +} +const SetExpoToken = async (req, res, next) => { + + try { + + const { expoToken } = req.query; + const googleToken = req.headers['authorization']; + + //checks + if (!expoToken) throw new Error('No expo token provided'); + if (!googleToken) throw new Error('No auth header provided'); + // ValidateCharsNoEmojis(expoToken); + + //post it + await dbPosts.ExpoToken(googleToken, expoToken); + + //response + res.status(201).send({ + ok: true, + message: 'succesfully updated expo token for notifications!', + data: { + expoToken: expoToken + } + }); + + } + catch (error) { + + //fail response + res.status(400).send({ + ok: false, + message: 'Cant update expo token: ' + error.message, + }); + + } + +} + +userRouter.get('/', isAuth, GetUserInfo); +userRouter.get('/stats', isAuth, GetUserStats); +userRouter.post('/login', Login); +userRouter.post('/name', isAuth, SetDisplayName); +userRouter.post('/expoToken', isAuth, SetExpoToken); + +module.exports = userRouter; \ No newline at end of file diff --git a/scheduling/checkDeadlines.js b/scheduling/checkDeadlines.js new file mode 100644 index 0000000..666e86c --- /dev/null +++ b/scheduling/checkDeadlines.js @@ -0,0 +1,31 @@ +const db = require('../database/dbConnect'); +const { HandleDeadlinePassed, CheckRoomDeadline } = require('../database/dbFunctions'); + +const checkDeadlines = async () => { + + const query = await db.query( + `SELECT *, EXTRACT(epoch FROM (turn_end - NOW())/3600) AS time_left + FROM rooms + WHERE turn_end - NOW() < Interval '30 min' + AND finished = false + AND next_player_id IS NOT NULL` + ) + + if (query.rowCount < 1) return; + + query.rows.forEach(room => { + + if (room.time_left < 0) { + HandleDeadlinePassed(room); + } + else { + const delay = room.time_left * 60 * 60 * 1000; + setTimeout(CheckRoomDeadline, delay, room); + } + + }) + + +} + +checkDeadlines(); \ No newline at end of file