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