diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d942911 --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +NODE_ENV=development + +PGUSER=dbuser +PGHOST=database.server.com +PGPASSWORD=dbuserpassword +PGDATABASE=mydatabase +PGPORT=5432 + +PUBLIC_SUBMIT_PAGE=false + +#MAIL_FROM=fromaddress@example.com +#MAIL_HOST=smtp.smtphost.net +#MAIL_PORT=465 +#MAIL_SECURE=true +#MAIL_USER=username +#MAIL_PASS=password diff --git a/.node-version b/.node-version index 2f76972..58a4133 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -8.11.1 +16.13.0 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b05495f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/bin/www" + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 41c390d..97abb5d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:8.11-alpine +FROM node:16.13.0-alpine3.12 WORKDIR /usr/src/app diff --git a/README.md b/README.md index c5659bc..6d558c8 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,53 @@ -### Node Express template project +# Score Tracker -This project is based on a GitLab [Project Template](https://docs.gitlab.com/ee/gitlab-basics/create-project.html). +Main repository: https://gitlab.sudoer.ch/sudoer777/score-tracker/ -Improvements can be proposed in the [original project](https://gitlab.com/gitlab-org/project-templates/express). +A web app designed to collect and display scores for sports -### CI/CD with Auto DevOps +## Branches -This template is compatible with [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/). +- [main](https://gitlab.sudoer.ch/sudoer777/score-tracker/-/tree/main) - Stable, production-ready code +- [testing](https://gitlab.sudoer.ch/sudoer777/score-tracker/-/tree/testing) - Nearly complete code being tested +- [develop](https://gitlab.sudoer.ch/sudoer777/score-tracker/-/tree/develop) - Unstable code under development -If Auto DevOps is not already enabled for this project, you can [turn it on](https://docs.gitlab.com/ee/topics/autodevops/#enabling-auto-devops) in the project settings. +## Installation -### Developing with Gitpod +This repository can be cloned and then pushed to Heroku/Dokku/etc. -This template has a fully-automated dev setup for [Gitpod](https://docs.gitlab.com/ee/integration/gitpod.html). +### Requirements -If you open this project in Gitpod, you'll get all Node dependencies pre-installed and Express will open a web preview. +- PostgreSQL (with an empty database created and an account to access it) + +### Environment Variables + +- `NODE_ENV` - set to `production`, `testing`, or `development` +- `PGHOST` - set to your database URL +- `PGPORT` - set to the database port +- `PGDATABASE` - set to the name of your database (i.e. `scoretrackerdb`) +- `PGUSER` - set to the user for managing the database +- `PGPASSWORD` - set to the password for that user +- `PUBLIC_SUBMIT_PAGE` (default: `false`) - set to `true` to allow score submissions without an account + +## Code + +This program uses Node.js/Express.js for the backend, PostgreSQL for the database (with node-postgres), and Passport.js for managing users and sessions. + +To view the code, clone the repository and open it in VSCode/VSCodium. + +### Structure + +- `database` folder contains backend scripts for managing and storing data. +- `mail` folder (currenly unused) contains backend scripts for sending emails. +- `public` folder contains publically accessible scripts and stylesheets for frontend. + - `scripts` folder contains scripts used by specific webpages. + - `stylesheets` folder contains CSS for various webpages. +- `routes` folder contains various routes used by the program. + - `about.js` directs to the about page (`/about`). + - `auth.js` deals with logging in and out (`/auth/*`). + - `checkLoginStatus.js` contains functions for checking the login status of the current user. + - `data.js` sends various data to the client in JSON format (`/data/*`). + - `fetch.js` sends more specific data formatted for specific pages in JSON format (`/fetch/*`) + - `index.js` directs to the home page (`/`). + - `manage.js` contains various functions that allows the user to add and edit items through the web browser (`/manage/*`). +- `views` folder contains pug templates for each webpage, and a `layout` template for the base layout of each page. +- `.env.example` is a template for the environment variables in a development workspace. Rename to `.env` and change values as needed. diff --git a/app.js b/app.js index ab7aed4..da69f40 100644 --- a/app.js +++ b/app.js @@ -3,12 +3,42 @@ var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); +var random = require('./database/accounts/random'); +const passport = require('passport'); +const session = require('express-session'); +const accounts = require('./database/accounts/accounts'); +var flash = require('connect-flash'); + var indexRouter = require('./routes/index'); -var usersRouter = require('./routes/users'); +var dataRouter = require('./routes/data'); +var manageRouter = require('./routes/manage'); +var authRouter = require('./routes/auth'); +var aboutRouter = require('./routes/about'); +var fetchRouter = require('./routes/fetch'); var app = express(); +// flash setup +app.use(flash()); + +// session setup +app.use( + session({ + secret: random.makeid(20), + resave: false, + saveUninitialized: true, + }) +); + +// passport setup +app.use(passport.initialize()); +app.use(passport.session()); + +//passport.use(accounts.createStrategy()); +//passport.serializeUser(accounts.serializeUser()); +//passport.deserializeUser(accounts.deserializeUser()); + // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'pug'); @@ -20,7 +50,12 @@ app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); -app.use('/users', usersRouter); +app.use('/data', dataRouter); +app.use('/manage', manageRouter); +app.use('/auth', authRouter); +app.use('/about', aboutRouter); +app.use('/fetch', fetchRouter); + // catch 404 and forward to error handler app.use(function(req, res, next) { diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js new file mode 100644 index 0000000..921a1cf --- /dev/null +++ b/database/accounts/accounts.js @@ -0,0 +1,144 @@ +const database = require('./../database'); +const passport = require('passport'); +const localStrategy = require('passport-local').Strategy; +const bcrypt = require('bcrypt'); + +class User { + constructor(id, email, isAdmin, name) { + this.id = id; + this.email = email; + this.isAdmin = isAdmin; + this.name = name; + } +} + + +async function checkForAdminAccount() { + + const adminUsersQuery = `SELECT * + FROM accounts.users + WHERE admin = true;`; + const adminUsers = await database.executeQuery(adminUsersQuery); + + if(adminUsers.length == 0) { + const passwordHash = await generateHash('admin'); + const createTempAdminQuery = `INSERT INTO accounts.users(email, password, admin) + VALUES('admin@example.com', $1, true);`; + database.executeQuery(createTempAdminQuery, [passwordHash]); + console.log("Created temp admin account 'admin@example.com' with password 'admin'."); + } +} +database.initializationStatus.then(() => checkForAdminAccount()); + + +passport.use(new localStrategy({ + usernameField: 'email', + passwordField: 'password'}, + (username, password, cb) => { + query = `SELECT user_id, email, password, admin + FROM accounts.users + WHERE email = $1`; + database.executeQuery(query, [username]) + .then(result => { + if(result.length > 0) { + const first = result[0]; + const matches = bcrypt.compareSync(password, first[2]); + if(matches) { + return cb(null, { id: first[0], email: first[1], admin: first[3] }) + } + else + { + return cb(null, false) + } + } else { + return cb(null, false) + } + }); +})); + +passport.serializeUser((user, done) => { + done(null, user.id) +}) + +passport.deserializeUser((id, cb) => { + query = `SELECT user_id, email, admin + FROM accounts.users + WHERE user_id = $1`; + database.executeQuery(query, [parseInt(id, 10)]) + .then(result => { + cb(null, result[0]); + }); +}); + + +async function generateHash(password) { + const salt = bcrypt.genSaltSync(); + return bcrypt.hashSync(password, salt); +} + +async function create(email, password, isAdmin, name) { + const hash = await generateHash(password); + + const query = `INSERT INTO accounts.users(email, password, admin, full_name) + VALUES($1, $2, $3, $4)`; + await database.executeQuery(query, [email, hash, isAdmin, name]); +} + +async function edit(id, email, password, isAdmin, name) { + if(password) { + const hash = await generateHash(password); + + const query = `UPDATE accounts.users + SET email = $2, + password = $3, + admin = $4, + full_name = $5 + WHERE user_id = $1;`; + await database.executeQuery(query, [id, email, hash, isAdmin, name]); + } else { + const query = `UPDATE accounts.users + SET email = $2, + admin = $3, + full_name = $4 + WHERE user_id = $1;`; + await database.executeQuery(query, [id, email, isAdmin, name]); + } + return new User(id, email, isAdmin, name); +} + +async function remove(id) { + const query = `DELETE FROM accounts.users + WHERE user_id = $1 + RETURNING email, admin, full_name;`; + const row = (await database.executeQuery(query, [id]))[0]; + return new User(id, row[0], row[1], row[2]); +} + +async function retrieveAll() { + const query = `SELECT user_id, email, admin, full_name + FROM accounts.users + ORDER BY full_name;`; + const table = await database.executeQuery(query); + + const accountsList = []; + table.forEach((row) => { + accountsList.push(new User(row[0], row[1], row[2], row[3])); + }); + return accountsList; +} + +async function getFromID(id) { + const query = `SELECT user_id, email, admin, full_name + FROM accounts.users + WHERE user_id = $1;`; + const row = (await database.executeQuery(query, [id]))[0]; + + return new User(id, row[1], row[2], row[3]); +} + +exports.create = create; +exports.edit = edit; +exports.remove = remove; +exports.retrieveAll = retrieveAll; +exports.getFromID = getFromID; +exports.passport = passport; \ No newline at end of file diff --git a/database/accounts/random.js b/database/accounts/random.js new file mode 100644 index 0000000..6d6a57d --- /dev/null +++ b/database/accounts/random.js @@ -0,0 +1,12 @@ +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; +} + +exports.makeid = makeid; \ No newline at end of file diff --git a/database/database.js b/database/database.js new file mode 100644 index 0000000..c0b7257 --- /dev/null +++ b/database/database.js @@ -0,0 +1,69 @@ +const app = require('../app'); +const { Client } = require('pg'); +const fs = require('fs'); + +if (process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'testing') { + require('dotenv').config(); +} + +const client = new Client(); +client.connect(); + +async function executeQuery(query, values = []) { + const result = await client.query({ + rowMode: 'array', + text: query, + values: values + }); + return result.rows; +} + +async function Initialize() { + console.log("Initializing database...") + const sql = fs.readFileSync('database/init_database.sql').toString(); + await executeQuery(sql); + console.log("Database initialized.") +} + + +async function checkForDatabaseInitialization() { + const databaseIsSetupQuery = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`; + let result = await executeQuery(databaseIsSetupQuery); + + const databaseIsSetup = result.length !== 0; + + if(!databaseIsSetup) { + await Initialize(); + } + + + let latestMigration; + try { + const latestMigrationQuery = `SELECT value FROM metadata WHERE property = 'latest_migration';`; + latestMigration = +((await executeQuery(latestMigrationQuery))[0][0]); + } catch { + latestMigration = 0; + } + + + await performMigrations(latestMigration); +} +const initializationStatus = checkForDatabaseInitialization(); + +async function performMigrations(currentMigration) { + const migrationFileList = fs.readdirSync('database/migrations'); + const latestMigration = +migrationFileList[migrationFileList.length - 1].slice(0, 1); + + for(let i = +currentMigration + 1; i <= latestMigration; i++) { + const sql = fs.readFileSync(`database/migrations/${i}.sql`).toString(); + await executeQuery(sql); + console.log(`Performed database migration ${i}`); + } +} + + + + + +exports.executeQuery = executeQuery; +exports.initializationStatus = initializationStatus; \ No newline at end of file diff --git a/database/init_database.sql b/database/init_database.sql new file mode 100644 index 0000000..8cd8747 --- /dev/null +++ b/database/init_database.sql @@ -0,0 +1,124 @@ +/* SCORE TRACKER DATABASE LAYOUT + +scores: + + sports: + *sport_id* | sport_name | currently_active + + divisions: + *division_id* | division_name | gender | *sport_id* | currently_active + + teams: + *team_id* | team_name | ~sport_id~ | currently_active + + seasons: + *season_id* | school_year + + games: + *game_id* | ~division_id~ | ~season_id~ | game_date | ~team1_id~ | ~team2_id~ | team1_score | team2_score | ~submitter_id~ | updated_timestamp | submitter_name + + + +accounts: + + users: + *user_id* | email | password | admin | full_name + +*/ + + +BEGIN; + + +CREATE SCHEMA IF NOT EXISTS accounts; + +CREATE TABLE IF NOT EXISTS accounts.users( + user_id BIGINT GENERATED ALWAYS AS IDENTITY, + email TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + admin BOOLEAN NOT NULL DEFAULT FALSE, + full_name TEXT NOT NULL, + PRIMARY KEY(user_id) + ); + + + +CREATE SCHEMA IF NOT EXISTS scores; + + +CREATE TABLE IF NOT EXISTS scores.sports( + sport_id BIGINT GENERATED ALWAYS AS IDENTITY, + sport_name TEXT UNIQUE NOT NULL, + currently_active BOOLEAN DEFAULT TRUE, + PRIMARY KEY(sport_id) + ); + +CREATE TABLE IF NOT EXISTS scores.divisions( + division_id BIGINT GENERATED ALWAYS AS IDENTITY, + division_name TEXT NOT NULL, + gender VARCHAR(1) NOT NULL CHECK (gender IN ( 'F', 'M' ) ), + sport_id BIGINT NOT NULL, + currently_active BOOLEAN DEFAULT TRUE, + PRIMARY KEY(division_id), + CONSTRAINT fk_sport + FOREIGN KEY(sport_id) + REFERENCES scores.sports(sport_id) + ); + +CREATE TABLE IF NOT EXISTS scores.teams( + team_id BIGINT GENERATED ALWAYS AS IDENTITY, + team_name TEXT NOT NULL, + sport_id BIGINT NOT NULL, + currently_active BOOLEAN DEFAULT TRUE, + PRIMARY KEY(team_id), + CONSTRAINT fk_sport + FOREIGN KEY(sport_id) + REFERENCES scores.sports(sport_id) + ); + +CREATE TABLE IF NOT EXISTS scores.seasons( + season_id BIGINT GENERATED ALWAYS AS IDENTITY, + school_year INTEGER NOT NULL, + PRIMARY KEY(season_id) + ); + +CREATE TABLE IF NOT EXISTS scores.games( + game_id BIGINT GENERATED ALWAYS AS IDENTITY, + division_id BIGINT NOT NULL, + season_id BIGINT NOT NULL, + game_date DATE NOT NULL, + team1_id BIGINT NOT NULL, + team2_id BIGINT NOT NULL, + team1_score INTEGER NOT NULL, + team2_score INTEGER NOT NULL, + submitter_name TEXT, + submitter_id BIGINT, + updated_timestamp TIMESTAMP WITH TIME ZONE DEFAULT now(), + PRIMARY KEY(game_id), + CONSTRAINT fk_division + FOREIGN KEY(division_id) + REFERENCES scores.divisions(division_id), + CONSTRAINT fk_season + FOREIGN KEY(season_id) + REFERENCES scores.seasons(season_id), + CONSTRAINT fk_team1 + FOREIGN KEY(team1_id) + REFERENCES scores.teams(team_id), + CONSTRAINT fk_team2 + FOREIGN KEY(team2_id) + REFERENCES scores.teams(team_id), + CONSTRAINT fk_submitter + FOREIGN KEY(submitter_id) + REFERENCES accounts.users(user_id) + ); + + +CREATE TABLE IF NOT EXISTS metadata( + property TEXT UNIQUE NOT NULL, + value TEXT NOT NULL +); + +INSERT INTO metadata(property, value) + VALUES("latest_migration", "3"); + +COMMIT; \ No newline at end of file diff --git a/database/migrations/1.sql b/database/migrations/1.sql new file mode 100644 index 0000000..f9a5c78 --- /dev/null +++ b/database/migrations/1.sql @@ -0,0 +1,13 @@ +/* ADD METADATA TABLE */ + +BEGIN; + +CREATE TABLE IF NOT EXISTS metadata( + property TEXT UNIQUE NOT NULL, + value TEXT NOT NULL +); + +INSERT INTO metadata(property, value) + VALUES('latest_migration', '1'); + +COMMIT; \ No newline at end of file diff --git a/database/migrations/2.sql b/database/migrations/2.sql new file mode 100644 index 0000000..6a22cdb --- /dev/null +++ b/database/migrations/2.sql @@ -0,0 +1,12 @@ +/* ADD ACCOUNT NAME COLUMN */ + +BEGIN; + +ALTER TABLE accounts.users +ADD COLUMN full_name TEXT; + +UPDATE metadata +SET value = '2' +WHERE property = 'latest_migration'; + +COMMIT; \ No newline at end of file diff --git a/database/migrations/3.sql b/database/migrations/3.sql new file mode 100644 index 0000000..117779a --- /dev/null +++ b/database/migrations/3.sql @@ -0,0 +1,15 @@ +/* ADD OPTIONAL SUBMITTER NAME COLUMN IN GAMES TABLE */ + +BEGIN; + +ALTER TABLE scores.games ALTER COLUMN submitter_id DROP NOT NULL; + +ALTER TABLE scores.games +ADD COLUMN submitter_name TEXT; + + +UPDATE metadata +SET value = '3' +WHERE property = 'latest_migration'; + +COMMIT; \ No newline at end of file diff --git a/database/scores/divisions.js b/database/scores/divisions.js new file mode 100644 index 0000000..7da86dd --- /dev/null +++ b/database/scores/divisions.js @@ -0,0 +1,99 @@ +const database = require('./../database'); +const genders = require('./genders'); + + + + + +class Division { + constructor(id, name, gender, sportID) { + this.id = id; + this.name = name; + this.gender = gender; + this.sportID = sportID; + } +} + + + +function getGenderID(gender) { + return (gender == genders.MALE) ? "M" : "F"; +} + +function getGenderFromID(genderID) { + return (genderID == "F") ? genders.FEMALE : genders.MALE; +} + + + +async function add(name, gender, sportID) { + const query = `INSERT INTO scores.divisions(division_name,gender,sport_id) + VALUES($1,$2,$3) + RETURNING division_id;`; + const genderID = getGenderID(gender); + const id = (await database.executeQuery(query, [name, genderID, sportID]))[0][0]; + return new Division(id, name); +} + +async function rename(id, name) { + const query = `UPDATE scores.divisions + SET division_name = $2 + WHERE division_id = $1;`; + await database.executeQuery(query, [id, name]); + return new Division(id, name); +} + +async function remove(id) { + const query = `DELETE FROM scores.divisions + WHERE division_id = $1 + RETURNING division_name;`; + const name = (await database.executeQuery(query, [id]))[0][0]; + return new Division(id, name); +} + +async function retrieve(sportID = undefined, gender = undefined) { + let table; + + if(sportID && gender) { + const query = `SELECT division_id, division_name, gender, sport_id + FROM scores.divisions + WHERE sport_id = $1 AND gender = $2 + ORDER BY division_name;`; + const genderID = getGenderID(gender); + table = await database.executeQuery(query, [sportID, genderID]); + } + else { + const query = `SELECT division_id, division_name, gender, sport_id + FROM scores.divisions + ORDER BY sport_id, + division_name, + gender;`; + table = await database.executeQuery(query); + } + + const divisionsList = []; + table.forEach((row) => { + divisionsList.push(new Division(row[0], row[1], getGenderFromID(row[2]), row[3])); + }); + return divisionsList; +} + +async function getFromID(id) { + const query = `SELECT division_id, division_name, gender, sport_id + FROM scores.divisions + WHERE division_id = $1;`; + const row = (await database.executeQuery(query, [id]))[0]; + + + return new Division(id, row[1], getGenderFromID(row[2]), row[3]); +} + + + + + +exports.add = add; +exports.rename = rename; +exports.remove = remove; +exports.retrieve = retrieve; +exports.getFromID = getFromID; \ No newline at end of file diff --git a/database/scores/games.js b/database/scores/games.js new file mode 100644 index 0000000..5680c58 --- /dev/null +++ b/database/scores/games.js @@ -0,0 +1,140 @@ +const database = require('./../database'); + + + + + +class Game { + constructor(id, date, team1ID, team2ID, team1Score, team2Score, divisionID, seasonID, submitterID, submitterName) { + this.id = id; + this.date = date; + this.team1ID = team1ID; + this.team2ID = team2ID; + this.team1Score = team1Score; + this.team2Score = team2Score; + this.divisionID = divisionID; + this.seasonID = seasonID; + this.submitterID = submitterID; + this.submitterName = submitterName; + } +} + + + +async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, submitterID, submitterName = undefined) { + let id; + if(submitterName) { + const query = `INSERT INTO scores.games(division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_name) + VALUES($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING game_id;`; + id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, submitterName]))[0][0]; + } else { + const query = `INSERT INTO scores.games(division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id) + VALUES($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING game_id;`; + id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, submitterID]))[0][0]; + } + + return new Game(id, date, team1ID, team2ID, team1Score, team2Score); +} + +async function remove(id) { + const query = `DELETE FROM scores.games + WHERE game_id = $1 + RETURNING * ;`; + const row = (await database.executeQuery(query, [id]))[0]; + return new Game(id, row[3], row[4], row[5], row[6], row[7]); +} + +async function retrieve(teamID, divisionID, seasonID) { + let table; + + if(teamID && divisionID && seasonID) { + const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id, submitter_name + FROM scores.games + WHERE (team1_id = $1 OR team2_id = $1) AND division_id = $2 AND season_id = $3 + ORDER BY game_date DESC;`; + table = await database.executeQuery(query, [teamID,divisionID,seasonID]); + } + else { + const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id, submitter_name + FROM scores.games + ORDER BY game_date DESC;`; + table = await database.executeQuery(query); + } + + + const gamesList = []; + table.forEach((row) => { + if(teamID) { + const opponentIsTeam2 = teamID != row[5]; + const opponentID = opponentIsTeam2 ? row[5] : row[4]; + const teamScore = opponentIsTeam2 ? row[6] : row[7]; + const opponentScore = opponentIsTeam2 ? row[7] : row[6]; + + gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), teamID, opponentID, teamScore, opponentScore, row[1], row[2], row[8], row[9])); + } + else { + gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2], row[8], row[9])); + } + }); + return gamesList; +} + +async function retrieveByUser(userID) { + const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score + FROM scores.games + WHERE submitter_id = $1 + ORDER BY game_date DESC;`; + const table = await database.executeQuery(query, [userID]); + + const gamesList = []; + table.forEach((row) => { + gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2])); + }); + return gamesList; +} + +async function edit(gameID, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) { + const query = `UPDATE scores.games + SET division_id = $2, + season_id = $3, + game_date = $4, + team1_id = $5, + team2_id = $6, + team1_score = $7, + team2_score = $8 + WHERE game_id = $1;`; + await database.executeQuery(query, [gameID, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score]); + return new Game(gameID, date, team1ID, team2ID, team1Score, team2Score, divisionID, seasonID); +} + +async function getFromID(gameID) { + const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id + FROM scores.games + WHERE game_id = $1;`; + const row = (await database.executeQuery(query, [gameID]))[0]; + return new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2], row[8]); +} + +async function getLatest(userID = undefined) { + if(userID) { + const games = await retrieveByUser(userID); + return games[0]; + } else { + const games = await retrieve(); + return games[0]; + } +} + + + + + +exports.add = add; +exports.remove = remove; +exports.retrieve = retrieve; +exports.retrieveByUser = retrieveByUser; +exports.edit = edit; +exports.getFromID = getFromID; +exports.getLatest = getLatest; \ No newline at end of file diff --git a/database/scores/genders.js b/database/scores/genders.js new file mode 100644 index 0000000..17e3ff2 --- /dev/null +++ b/database/scores/genders.js @@ -0,0 +1,48 @@ +const database = require('./../database'); + + + + + +class Gender { + constructor(name) { + this.name = name; + } +} +const MALE = new Gender("male"); +const FEMALE = new Gender("female"); + + + +async function retrieveBySport(sportID) { + const query = `SELECT DISTINCT(gender) + FROM scores.divisions + WHERE sport_id = $1;`; + const table = await database.executeQuery(query, [sportID]); + + const gendersList = []; + + if(table.length == 0) { + return gendersList; + } + if(table.length == 2) { + gendersList.push(FEMALE); + gendersList.push(MALE); + return gendersList; + } + else if(table[0][0] = "F") { + gendersList.push(FEMALE); + return gendersList; + } + else if(table[0][0] = "M") { + gendersList.push(MALE); + return gendersList; + } +} + + + + +exports.MALE = MALE; +exports.FEMALE = FEMALE; +exports.retrieveBySport = retrieveBySport; \ No newline at end of file diff --git a/database/scores/seasons.js b/database/scores/seasons.js new file mode 100644 index 0000000..f787ade --- /dev/null +++ b/database/scores/seasons.js @@ -0,0 +1,51 @@ +const database = require('./../database'); + + + + + +class Season { + constructor(id, year) { + this.id = id; + this.year = year; + } +} + + + +async function add(year) { + const query = `INSERT INTO scores.seasons(school_year) + VALUES($1) + RETURNING season_id;`; + const id = (await database.executeQuery(query, [year]))[0][0]; + return new Season(id, year); +} + +async function remove(id) { + const query = `DELETE FROM scores.seasons + WHERE season_id = $1 + RETURNING school_year;`; + const year = (await database.executeQuery(query, [id]))[0][0]; + return new Season(id, year); +} + +async function retrieveAll() { + const query = `SELECT * + FROM scores.seasons + ORDER BY school_year DESC;`; + const table = await database.executeQuery(query); + + const seasonsList = []; + table.forEach((row) => { + seasonsList.push(new Season(row[0], row[1])); + }); + return seasonsList; +} + + + + + +exports.add = add; +exports.remove = remove; +exports.retrieveAll = retrieveAll; \ No newline at end of file diff --git a/database/scores/sports.js b/database/scores/sports.js new file mode 100644 index 0000000..134009d --- /dev/null +++ b/database/scores/sports.js @@ -0,0 +1,67 @@ +const database = require('./../database'); + + + +class Sport { + constructor(id, name) { + this.id = id; + this.name = name; + } +} + + + +async function add(name) { + const query = `INSERT INTO scores.sports(sport_name) + VALUES($1) + RETURNING sport_id;`; + const id = (await database.executeQuery(query, [name]))[0][0]; + return new Sport(id, name); +} + +async function rename(id, name) { + const query = `UPDATE scores.sports + SET sport_name = $2 + WHERE sport_id = $1;`; + await database.executeQuery(query, [id, name]); + return new Sport(id, name); +} + +async function remove(id) { + const query = `DELETE FROM scores.sports + WHERE sport_id = $1 + RETURNING sport_name;`; + const name = (await database.executeQuery(query, [id]))[0][0]; + return new Sport(id, name); +} + +async function retrieveAll() { + const query = `SELECT * + FROM scores.sports + ORDER BY sport_name;`; + const table = await database.executeQuery(query); + + const sportsList = []; + table.forEach((row) => { + sportsList.push(new Sport(row[0], row[1])); + }); + return sportsList; +} + +async function getFromID(id) { + const query = `SELECT sport_name + FROM scores.sports + WHERE sport_id = $1;`; + const name = (await database.executeQuery(query, [id]))[0][0]; + return new Sport(id, name); +} + + + + + +exports.add = add; +exports.rename = rename; +exports.remove = remove; +exports.retrieveAll = retrieveAll; +exports.getFromID = getFromID; \ No newline at end of file diff --git a/database/scores/teams.js b/database/scores/teams.js new file mode 100644 index 0000000..20034ef --- /dev/null +++ b/database/scores/teams.js @@ -0,0 +1,83 @@ +const database = require('./../database'); + + + + + +class Team { + constructor(id, name, sportID) { + this.id = id; + this.name = name; + this.sportID = sportID; + } +} + + + +async function add(name, sportID) { + const query = `INSERT INTO scores.teams(team_name, sport_id) + VALUES($1, $2) + RETURNING team_id;`; + const id = (await database.executeQuery(query, [name, sportID]))[0][0]; + return new Team(id, name); +} + +async function rename(id, name) { + const query = `UPDATE scores.teams + SET team_name = $2 + WHERE team_id = $1;`; + await database.executeQuery(query, [id, name]); + return new Team(id, name); +} + +async function remove(id) { + const query = `DELETE FROM scores.teams + WHERE team_id = $1 + RETURNING team_name;`; + const name = (await database.executeQuery(query, [id]))[0][0]; + return new Team(id, name); +} + +async function retrieve(sportID = undefined) { + let table; + + if(sportID) { + const query = `SELECT team_id, team_name, sport_id + FROM scores.teams + WHERE sport_id = $1 + ORDER BY team_name;`; + table = await database.executeQuery(query, [sportID]); + } + else { + const query = `SELECT team_id, team_name, sport_id + FROM scores.teams + ORDER BY + sport_id, + team_name;`; + table = await database.executeQuery(query); + } + + const teamsList = []; + table.forEach((row) => { + teamsList.push(new Team(row[0], row[1], row[2])); + }); + return teamsList; +} + +async function getFromID(id) { + const query = `SELECT team_name, sport_id + FROM scores.teams + WHERE team_id = $1;`; + const row = (await database.executeQuery(query, [id]))[0]; + return new Team(id, row[0], row[1]); +} + + + + + +exports.add = add; +exports.rename = rename; +exports.remove = remove; +exports.retrieve = retrieve; +exports.getFromID = getFromID; \ No newline at end of file diff --git a/mail/mail.js b/mail/mail.js new file mode 100644 index 0000000..1c9ccef --- /dev/null +++ b/mail/mail.js @@ -0,0 +1,33 @@ +const app = require('../app'); +const nodemailer = require('nodemailer'); + +if (process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'testing') { + require('dotenv').config(); +} + +module.exports = { + send: function (recipient, subject, message) { + send(recipient, subject, message); + } + }; + + +var send = function (recipient, subject, message) { + transporter.sendMail({ + to: recipient, // list of receivers + subject: subject, // Subject line + html: message, // html body + }); +} + +let transporter = nodemailer.createTransport({ + host: process.env.MAIL_HOST, + port: process.env.MAIL_PORT, + secure: process.env.MAIL_SECURE, + auth: { + user: process.env.MAIL_USER, + pass: process.env.MAIL_PASS, + }, +}, { + from: process.env.MAIL_FROM +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4a8d7ba..90eb91e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,65 +1,1854 @@ { - "name": "demo", - "version": "0.0.0", - "lockfileVersion": 1, + "name": "score-tracker", + "version": "1.0.3-pre", + "lockfileVersion": 2, "requires": true, - "dependencies": { - "@types/babel-types": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.4.tgz", - "integrity": "sha512-WiZhq3SVJHFRgRYLXvpf65XnV6ipVHhnNaNvE8yCimejrGglkg38kEj0JcizqwSHxmPSjcTlig/6JouxLGEhGw==" - }, - "@types/babylon": { - "version": "6.16.3", - "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.3.tgz", - "integrity": "sha512-lyJ8sW1PbY3uwuvpOBZ9zMYKshMnQpXmeDHh8dj9j2nJm/xrW0FgB5gLSYOArj5X0IfaXnmhFoJnhS4KbqIMug==", - "requires": { - "@types/babel-types": "7.0.4" - } - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "2.1.19", - "negotiator": "0.6.1" - } - }, - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" - }, - "acorn-globals": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", - "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", - "requires": { - "acorn": "4.0.13" - }, + "packages": { + "": { + "name": "score-tracker", + "version": "1.0.3-pre", "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + "async": "^3.2.2", + "bcrypt": "^5.0.1", + "connect-flash": "^0.1.1", + "cookie-parser": "~1.4.3", + "debug": "~2.6.9", + "dotenv": "^10.0.0", + "express": "~4.16.0", + "express-session": "^1.17.2", + "http-errors": "~1.6.2", + "morgan": "~1.9.0", + "nodemailer": "^6.6.5", + "passport": "^0.5.0", + "passport-local": "^1.0.0", + "pg": "^8.7.1", + "pug": "^3.0.2" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.16.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", + "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.15.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.7.tgz", + "integrity": "sha512-PplSvl4pJ5N3BkVjAdDzpPhVUPdC73JgttkR+LnBx2OORC1GCQsBjUeEuipf9uOaAM1SbxcdZFfR3KDTKm2S0A==", + "dependencies": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.5", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "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.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" + "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" } }, - "amdefine": { + "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/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "node_modules/assert-never": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", + "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==" + }, + "node_modules/async": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "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.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", + "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dependencies": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "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-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.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "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/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "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": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/connect-flash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", + "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=", + "engines": { + "node": ">= 0.4.0" + } + }, + "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": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "engines": { + "node": ">= 0.6" + } + }, + "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.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "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/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "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": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "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": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "dependencies": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/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/express-session/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/express/node_modules/cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "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": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "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": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", + "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", + "dependencies": { + "ansi-regex": "^5.0.1", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "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-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "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": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "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.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "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": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "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-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "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-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "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/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/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + "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.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "bin": { + "mime": "cli.js" + } + }, + "node_modules/mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dependencies": { + "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "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.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "dependencies": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + }, + "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.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, + "node_modules/node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/nodemailer": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.2.tgz", + "integrity": "sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q==", + "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": "6.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", + "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "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": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "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.5.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz", + "integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.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": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "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": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=", + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "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": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, + "node_modules/pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=2.0.0" + }, + "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.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "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.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "dependencies": { + "split2": "^3.1.1" + } + }, + "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": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", + "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": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dependencies": { + "asap": "~2.0.3" + } + }, + "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/pug": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", + "integrity": "sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==", + "dependencies": { + "pug-code-gen": "^3.0.2", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz", + "integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==", + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.0.0", + "pug-runtime": "^3.0.0", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", + "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==" + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==" + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "engines": { + "node": ">= 0.8" + } + }, + "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.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dependencies": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "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/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + }, + "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": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/signal-exit": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" + }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "engines": { + "node": ">= 0.6" + } + }, + "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.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "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/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "engines": { + "node": ">=4" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "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/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "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/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/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "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/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "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": { + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==" + }, + "@babel/parser": { + "version": "7.16.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", + "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==" + }, + "@babel/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "to-fast-properties": "^2.0.0" + } + }, + "@mapbox/node-pre-gyp": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.7.tgz", + "integrity": "sha512-PplSvl4pJ5N3BkVjAdDzpPhVUPdC73JgttkR+LnBx2OORC1GCQsBjUeEuipf9uOaAM1SbxcdZFfR3KDTKm2S0A==", + "requires": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.5", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + } + }, + "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==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, + "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.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "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": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } }, "array-flatten": { "version": "1.1.1", @@ -71,101 +1860,89 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "assert-never": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", + "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==" }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "async": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" + }, + "babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.11.1" + "@babel/types": "^7.9.6" } }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.10", - "to-fast-properties": "1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" - }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "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.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", - "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", + "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.1" + "safe-buffer": "5.1.2" + } + }, + "bcrypt": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", + "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^3.1.0" } }, "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", "requires": { "bytes": "3.0.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.3", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.16" + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" } }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true + "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.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "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": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" } }, "character-parser": { @@ -173,66 +1950,41 @@ "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", "requires": { - "is-regex": "1.0.4" + "is-regex": "^1.0.3" } }, - "clean-css": { - "version": "3.4.28", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", - "integrity": "sha1-vxlF6C/ICPVWlebd6uwBQA79A/8=", - "requires": { - "commander": "2.8.1", - "source-map": "0.4.4" - } + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - } - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "commander": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", - "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", - "requires": { - "graceful-readlink": "1.0.1" - } - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true + "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": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "connect-flash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", + "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "constantinople": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", - "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", "requires": { - "@types/babel-types": "7.0.4", - "@types/babylon": "6.16.3", - "babel-types": "6.26.0", - "babylon": "6.18.0" + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" } }, "content-disposition": { @@ -246,16 +1998,16 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" }, "cookie-parser": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", - "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", "requires": { - "cookie": "0.3.1", + "cookie": "0.4.1", "cookie-signature": "1.0.6" } }, @@ -264,23 +2016,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, - "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", - "dev": true - }, - "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -289,16 +2024,10 @@ "ms": "2.0.0" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "delayed-stream": { + "delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "depd": { "version": "1.1.2", @@ -310,22 +2039,31 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, "doctypes": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" }, + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" + }, "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", @@ -336,64 +2074,81 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" - }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.5", "array-flatten": "1.1.1", - "body-parser": "1.18.2", + "body-parser": "1.18.3", "content-disposition": "0.5.2", - "content-type": "1.0.4", + "content-type": "~1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "finalhandler": "1.1.1", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.4", - "qs": "6.5.1", - "range-parser": "1.2.0", - "safe-buffer": "5.1.1", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", "send": "0.16.2", "serve-static": "1.13.2", "setprototypeof": "1.1.0", - "statuses": "1.4.0", - "type-is": "1.6.16", + "statuses": "~1.4.0", + "type-is": "~1.6.16", "utils-merge": "1.0.1", - "vary": "1.1.2" + "vary": "~1.1.2" + }, + "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + } } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "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.1", @@ -401,121 +2156,157 @@ "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "requires": { "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.4.0", - "unpipe": "1.0.0" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" } }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "dev": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.19" - } - }, - "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", - "dev": true - }, "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=" }, + "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": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, + "gauge": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", + "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "ansi-regex": "^5.0.1", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" } }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "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" + "function-bind": "^1.1.1" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { - "depd": "1.1.2", + "depd": "~1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.0", - "statuses": "1.4.0" + "statuses": ">= 1.4.0 < 2" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "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": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -524,50 +2315,46 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ipaddr.js": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + "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-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "requires": { + "has": "^1.0.3" + } }, "is-expression": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", - "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", "requires": { - "acorn": "4.0.13", - "object-assign": "4.1.1" - }, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" - } + "acorn": "^7.1.1", + "object-assign": "^4.1.1" } }, + "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-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "requires": { - "has": "1.0.3" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, "js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", @@ -578,32 +2365,32 @@ "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", "requires": { - "is-promise": "2.1.0", - "promise": "7.3.1" + "is-promise": "^2.0.0", + "promise": "^7.0.1" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "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": { - "is-buffer": "1.1.6" + "yallist": "^4.0.0" } }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + "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", @@ -626,88 +2413,58 @@ "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" }, "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" }, "mime-types": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", - "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", "requires": { - "mime-db": "1.35.0" + "mime-db": "1.51.0" } }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "minipass": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "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": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "morgan": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", - "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", "requires": { - "basic-auth": "2.0.0", + "basic-auth": "~2.0.0", "debug": "2.6.9", - "depd": "1.1.2", - "on-finished": "2.3.0", - "on-headers": "1.0.1" + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" } }, "ms": { @@ -716,9 +2473,46 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, + "node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "nodemailer": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.2.tgz", + "integrity": "sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q==" + }, + "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": "6.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", + "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.0", + "set-blocking": "^2.0.0" + } }, "object-assign": { "version": "4.1.1", @@ -734,266 +2528,347 @@ } }, "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + "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": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { - "wrappy": "1.0.2" + "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.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "passport": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz", + "integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "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": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "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=" }, - "process-nextick-args": { + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, + "pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "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.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "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.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "requires": { + "split2": "^3.1.1" + } + }, + "postgres-array": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "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": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "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": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "requires": { - "asap": "2.0.6" + "asap": "~2.0.3" } }, "proxy-addr": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", - "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "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", - "ipaddr.js": "1.8.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" } }, "pug": { - "version": "2.0.0-beta11", - "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.0-beta11.tgz", - "integrity": "sha1-Favmr1AEx+LPRhPksnRlyVRrXwE=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", + "integrity": "sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==", "requires": { - "pug-code-gen": "1.1.1", - "pug-filters": "2.1.5", - "pug-lexer": "3.1.0", - "pug-linker": "2.0.3", - "pug-load": "2.0.11", - "pug-parser": "2.0.2", - "pug-runtime": "2.0.4", - "pug-strip-comments": "1.0.3" + "pug-code-gen": "^3.0.2", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" } }, "pug-attrs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.3.tgz", - "integrity": "sha1-owlflw5kFR972tlX7vVftdeQXRU=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", "requires": { - "constantinople": "3.1.2", - "js-stringify": "1.0.2", - "pug-runtime": "2.0.4" + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" } }, "pug-code-gen": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-1.1.1.tgz", - "integrity": "sha1-HPcnRO8qA56uajNAyqoRBYcSWOg=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz", + "integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==", "requires": { - "constantinople": "3.1.2", - "doctypes": "1.1.0", - "js-stringify": "1.0.2", - "pug-attrs": "2.0.3", - "pug-error": "1.3.2", - "pug-runtime": "2.0.4", - "void-elements": "2.0.1", - "with": "5.1.1" + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.0.0", + "pug-runtime": "^3.0.0", + "void-elements": "^3.1.0", + "with": "^7.0.0" } }, "pug-error": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz", - "integrity": "sha1-U659nSm7A89WRJOgJhCfVMR/XyY=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", + "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==" }, "pug-filters": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-2.1.5.tgz", - "integrity": "sha512-xkw71KtrC4sxleKiq+cUlQzsiLn8pM5+vCgkChW2E6oNOzaqTSIBKIQ5cl4oheuDzvJYCTSYzRaVinMUrV4YLQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", "requires": { - "clean-css": "3.4.28", - "constantinople": "3.1.2", + "constantinople": "^4.0.1", "jstransformer": "1.0.0", - "pug-error": "1.3.2", - "pug-walk": "1.1.7", - "resolve": "1.8.1", - "uglify-js": "2.8.29" + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" } }, "pug-lexer": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-3.1.0.tgz", - "integrity": "sha1-/QhzdtSmdbT1n4/vQiiDQ06VgaI=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", "requires": { - "character-parser": "2.2.0", - "is-expression": "3.0.0", - "pug-error": "1.3.2" + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" } }, "pug-linker": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-2.0.3.tgz", - "integrity": "sha1-szH/olc33eacEntWwQ/xf652bco=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", "requires": { - "pug-error": "1.3.2", - "pug-walk": "1.1.7" + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" } }, "pug-load": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.11.tgz", - "integrity": "sha1-5kjlftET/iwfRdV4WOorrWvAFSc=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", "requires": { - "object-assign": "4.1.1", - "pug-walk": "1.1.7" + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" } }, "pug-parser": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-2.0.2.tgz", - "integrity": "sha1-U6aAz9BQOdywwn0CkJS8SnkmibA=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", "requires": { - "pug-error": "1.3.2", - "token-stream": "0.0.1" + "pug-error": "^2.0.0", + "token-stream": "1.0.0" } }, "pug-runtime": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.4.tgz", - "integrity": "sha1-4XjhvaaKsujArPybztLFT9iM61g=" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==" }, "pug-strip-comments": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.3.tgz", - "integrity": "sha1-8VWVkiBu3G+FMQ2s9K+0igJa9Z8=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", "requires": { - "pug-error": "1.3.2" + "pug-error": "^2.0.0" } }, "pug-walk": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.7.tgz", - "integrity": "sha1-wA1cUSi6xYBr7BXSt+fNq+QlMfM=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" }, "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "requires": { "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.4.0" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } } }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "requires": { - "path-parse": "1.0.5" + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" } }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { - "align-text": "0.1.4" + "glob": "^7.1.3" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } }, "send": { "version": "0.16.2", @@ -1001,18 +2876,18 @@ "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", "requires": { "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.6.3", + "http-errors": "~1.6.2", "mime": "1.4.1", "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.4.0" + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" } }, "serve-static": { @@ -1020,23 +2895,33 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", "send": "0.16.2" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "signal-exit": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" + }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", "requires": { - "amdefine": "1.0.1" + "readable-stream": "^3.0.0" } }, "statuses": { @@ -1045,104 +2930,83 @@ "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, + "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.1.1" - } - }, - "superagent": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", - "integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==", - "dev": true, - "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.2", - "debug": "3.1.0", - "extend": "3.0.2", - "form-data": "2.3.2", - "formidable": "1.2.1", - "methods": "1.1.2", - "mime": "1.4.1", - "qs": "6.5.1", - "readable-stream": "2.3.6" + "safe-buffer": "~5.2.0" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } + "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==" } } }, - "supertest": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.1.0.tgz", - "integrity": "sha512-O44AMnmJqx294uJQjfUmEyYOg7d9mylNFsMw/Wkz4evKd1njyPrtCN+U6ZIC7sKtfEVQhfTqFFijlXx8KP/Czw==", - "dev": true, + "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": { - "methods": "1.1.2", - "superagent": "3.8.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "requires": { - "has-flag": "3.0.0" + "ansi-regex": "^5.0.1" + } + }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "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" } }, "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "token-stream": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", - "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.19" + "mime-types": "~2.1.24" } }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } + "random-bytes": "~1.0.0" } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "optional": true - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1151,8 +3015,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", @@ -1165,45 +3028,57 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, - "with": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", - "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", "requires": { - "acorn": "3.3.0", - "acorn-globals": "3.1.0" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + "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" + } + }, + "with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "requires": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } + "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 20f3026..2e017a4 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,25 @@ { - "name": "demo", - "version": "0.0.0", + "name": "score-tracker", + "version": "1.2.0", "private": true, "scripts": { - "start": "node ./bin/www", - "test": "mocha" + "start": "node ./bin/www" }, "dependencies": { + "async": "^3.2.2", + "bcrypt": "^5.0.1", + "connect-flash": "^0.1.1", "cookie-parser": "~1.4.3", "debug": "~2.6.9", + "dotenv": "^10.0.0", "express": "~4.16.0", + "express-session": "^1.17.2", "http-errors": "~1.6.2", "morgan": "~1.9.0", - "pug": "2.0.0-beta11" - }, - "devDependencies": { - "mocha": "^5.1.1", - "supertest": "^3.0.0" + "nodemailer": "^6.6.5", + "passport": "^0.5.0", + "passport-local": "^1.0.0", + "pg": "^8.7.1", + "pug": "^3.0.2" } } diff --git a/public/scripts/data.js b/public/scripts/data.js new file mode 100644 index 0000000..9a1c12d --- /dev/null +++ b/public/scripts/data.js @@ -0,0 +1,99 @@ +export async function getSports() { + const response = await fetch(`/data/sports`); + const sportsList = await response.json(); + return sportsList; +} + +export async function getSportName(sportID) { + const response = await fetch(`/data/sport?sport=${sportID}`); + const sport = await response.json(); + return sport.name; +} + +export async function getSeasons() { + const response = await fetch(`/data/seasons`); + const seasonsList = await response.json(); + return seasonsList; +} + +export async function getGenders(sportID) { + const response = await fetch(`/data/genders?sport=${+sportID}`); + const gendersList = await response.json(); + return gendersList; +} + +export async function getDivisions(sportID = undefined, gender = undefined) { + let URL = '/data/divisions?'; + if(sportID) URL += `sport=${+sportID}&`; + if(gender) URL += `gender=${gender}&`; + + const response = await fetch(URL); + const divisionsList = await response.json(); + return divisionsList; +} + +export async function getDivision(divisionID) { + const response = await fetch(`/data/division?division=${divisionID}`); + const division = await response.json(); + return division; +} + +export async function getTeams(sportID = undefined) { + let URL = '/data/teams?'; + if(sportID) URL += `sport=${+sportID}&`; + + const response = await fetch(URL); + const teamsList = await response.json(); + return teamsList; +} + +export async function getTeam(teamID) { + const response = await fetch(`/data/team?team=${+teamID}`); + const team = await response.json(); + return team; +} + +export async function getGames(teamID = undefined, divisionID = undefined, seasonID = undefined) { + let URL = '/data/games?'; + if(teamID) URL += `team=${+teamID}&`; + if(divisionID) URL += `division=${+divisionID}&`; + if(seasonID) URL += `season=${+seasonID}`; + + + const response = await fetch(URL); + const gamesList = await response.json(); + return gamesList; +} + +export async function getGamesByUser() { + let URL = '/data/games?user=1'; + const response = await fetch(URL); + const gamesList = await response.json(); + return gamesList; +} + +export async function getGame(gameID) { + const response = await fetch(`/data/game?game=${gameID}`); + const game = await response.json(); + return game; +} + +export async function getLatestGame(ofUser = false) { + let URL = `/data/game?`; + if(ofUser) URL += `ofuser=1`; + const response = await fetch(URL); + const game = await response.json(); + return game; +} + +export async function getAccounts() { + const response = await fetch(`/data/accounts`); + const accounts = await response.json(); + return accounts; +} + +export async function getAccount(accountID) { + const response = await fetch(`/data/account?account=${accountID}`); + const account = await response.json(); + return account; +} \ No newline at end of file diff --git a/public/scripts/form.js b/public/scripts/form.js new file mode 100644 index 0000000..acc3eaa --- /dev/null +++ b/public/scripts/form.js @@ -0,0 +1,167 @@ +import * as Data from "./data.js"; + +export async function populateSports(sportDropdown, selectedSportID = undefined, data = undefined) { + sportDropdown.innerHTML = ""; + + let sportsList; + if(data) { + sportsList = data.sports; + } else { + sportsList = await Data.getSports(); + } + + let currentIndex = 0; + let selectedSportIndex; + sportsList.forEach(sport => { + const option = document.createElement('option'); + option.text = sport.name; + option.value = sport.id; + sportDropdown.appendChild(option); + + if(sport.id == selectedSportID || (data && sport.id == data.latestGame.sportID)) selectedSportIndex = currentIndex; + currentIndex++; + }); + + if(selectedSportIndex) sportDropdown.selectedIndex = selectedSportIndex; +} + +export async function populateSeasons(seasonDropdown, selectedSeasonID = undefined, data = undefined) { + seasonDropdown.innerHTML = ""; + + let seasonsList; + + if(data) { + seasonsList = data.seasons; + } else { + seasonsList = await Data.getSeasons(); + } + + let currentIndex = 0; + let selectedSeasonIndex; + seasonsList.forEach(season => { + const option = document.createElement('option'); + option.text = (season.year - 1) + "-" + season.year; + option.value = season.id; + seasonDropdown.appendChild(option); + + if(season.id == selectedSeasonID || (data && season.id == data.latestGame.seasonID)) selectedSeasonIndex = currentIndex; + currentIndex++; + }); + + if(selectedSeasonIndex) seasonDropdown.selectedIndex = selectedSeasonIndex; +} + +export async function populateGenders(genderDropdown, selectedSportID, selectedGender = undefined, data = undefined) { + genderDropdown.innerHTML = ""; + + let gendersList; + if(data) { + gendersList = data.genders; + selectedSportID = data.latestGame.sportID; + } else { + gendersList = await Data.getGenders(selectedSportID); + } + + if(selectedSportID) { + let currentIndex = 0; + let selectedGenderIndex; + gendersList.forEach(gender => { + const option = document.createElement('option'); + option.text = (gender.name == "female") ? "Female" : (gender.name == "male") ? "Male" : ""; + option.value = gender.name; + genderDropdown.appendChild(option); + + if(gender.name == selectedGender || (data && gender.name == data.latestGame.gender.name)) selectedGenderIndex = currentIndex; + currentIndex++; + }); + + if(selectedGenderIndex) genderDropdown.selectedIndex = selectedGenderIndex; + } +} + +export async function populateDivisions (divisionDropdown, selectedSportID, selectedGender, selectedDivisionID = undefined, data = undefined) { + divisionDropdown.innerHTML = ""; + + if(data) { + selectedSportID = data.latestGame.sportID; + selectedGender = data.latestGame.gender; + } + if(selectedSportID && selectedGender) { + let divisionsList; + + if(data) { + divisionsList = data.divisions; + } else { + divisionsList = await Data.getDivisions(selectedSportID, selectedGender); + } + + let currentIndex = 0; + let selectedDivisionIndex; + divisionsList.forEach(division => { + const option = document.createElement('option'); + option.text = division.name; + option.value = division.id; + divisionDropdown.appendChild(option); + + if(division.id == selectedDivisionID || (data && division.id == data.latestGame.divisionID)) selectedDivisionIndex = currentIndex; + currentIndex++; + }); + + if(selectedDivisionIndex) divisionDropdown.selectedIndex = selectedDivisionIndex; + } +} + +export async function populateTeams(teamDropdown, selectedSportID, selectedTeamID = undefined, data = undefined, useOpponent = false) { + teamDropdown.innerHTML = ""; + + if(data) { + selectedSportID = data.latestGame.sportID; + } + + if(selectedSportID) { + let teamsList; + if(data) { + teamsList = data.teams; + } else { + teamsList = await Data.getTeams(selectedSportID); + } + + let currentIndex = 0; + let selectedTeamIndex; + + if(data) { + selectedTeamID = useOpponent ? data.latestGame.team2ID : data.latestGame.team1ID; + } + + teamsList.forEach(team => { + const option = document.createElement('option'); + option.text = team.name; + option.value = team.id; + teamDropdown.appendChild(option); + + if(team.id == selectedTeamID) selectedTeamIndex = currentIndex; + currentIndex++; + }); + + if(selectedTeamIndex) teamDropdown.selectedIndex = selectedTeamIndex; + } +} + +export async function addHiddenValue(name, value, form) { + const valueInput = document.createElement('input'); + valueInput.setAttribute('name', name); + valueInput.setAttribute('value', value); + valueInput.setAttribute('type', 'hidden'); + form.appendChild(valueInput); +} + +export async function addRemoveFunction(removeButton, form, objectTitle) { + removeButton.addEventListener('click', async () => { + const verified = confirm(`This ${objectTitle} will be removed.`); + + if(verified) { + await addHiddenValue('remove', 1, form); + form.submit(); + } + }); +} \ No newline at end of file diff --git a/public/scripts/index.js b/public/scripts/index.js new file mode 100644 index 0000000..de182c7 --- /dev/null +++ b/public/scripts/index.js @@ -0,0 +1,135 @@ +import * as Data from "./data.js"; +import * as Form from "./form.js"; + +const dropdownsDiv = document.getElementById('dropdowns-div'); +const sportDropdown = document.getElementById('sport-dropdown'); +const seasonDropdown = document.getElementById('year-dropdown'); +const genderDropdown = document.getElementById('gender-dropdown'); +const divisionDropdown = document.getElementById('division-dropdown'); +const teamDropdown = document.getElementById('team-dropdown'); +const gamesTable = document.getElementById('games-table'); +const gamesTableHeader = document.getElementById('games-table-header'); +const noScoresMessage = document.getElementById('no-scores-message'); +const addScoreButton = document.getElementById('add-score-button'); +const manageButton = document.getElementById('manage-button'); +const loadingSpan = document.getElementById('loading'); + + + + + +async function initializeForm() { + const data = await (await fetch(`/fetch/index/dropdown`)).json(); + + await Form.populateSeasons(seasonDropdown, null, data); + await Form.populateSports(sportDropdown, null, data); + await Form.populateGenders(genderDropdown, null, null, data); + await Form.populateDivisions(divisionDropdown, null, null, null, data); + await Form.populateTeams(teamDropdown, null, null, data); + + seasonDropdown.onchange = loadTable; + + sportDropdown.onchange = async () => { + await Form.populateGenders(genderDropdown, sportDropdown.value) + await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + await Form.populateTeams(teamDropdown, sportDropdown.value); + loadTable(); + }; + + genderDropdown.onchange = async () => { + await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + loadTable(); + }; + + divisionDropdown.onchange = loadTable; + teamDropdown.onchange = loadTable; + + loadingSpan.textContent = ''; + dropdownsDiv.style.visibility = 'visible'; + + loadTable(); + + +} +initializeForm(); + +async function loadTable() { + gamesTable.innerHTML = ""; + gamesTableHeader.textContent = ""; + noScoresMessage.textContent = ""; + + const selectedTeamID = teamDropdown.value; + const selectedDivisionID = divisionDropdown.value; + const selectedSeasonID = seasonDropdown.value; + + if(selectedTeamID && selectedDivisionID) { + gamesTableHeader.textContent = `Scores for ${teamDropdown.options[teamDropdown.selectedIndex].text}`; + + noScoresMessage.textContent = "Loading scores..."; + const requestURL = `/fetch/index/scores?team=${+selectedTeamID}&division=${+selectedDivisionID}&season=${+selectedSeasonID}`; + const gamesList = await (await fetch(requestURL)).json(); + noScoresMessage.textContent = ""; + if(gamesList.length > 0) { + await setupGamesTableHeaders(); + + gamesList.forEach((game) => { + const row = document.createElement('tr'); + + const scoreCell = document.createElement('td'); + const winLossLine = document.createElement('span'); + winLossLine.textContent = (game.team1Score > game.team2Score) ? "Win" : (game.team1Score < game.team2Score) ? "Loss" : "Tie"; + scoreCell.appendChild(winLossLine); + const scoreLine = document.createElement('span'); + scoreLine.textContent = game.team1Score + "-" + game.team2Score; + scoreCell.appendChild(scoreLine); + row.appendChild(scoreCell); + + const opponentCell = document.createElement('td'); + opponentCell.textContent = game.opponent.name; + row.appendChild(opponentCell); + + const dateCell = document.createElement('td'); + dateCell.textContent = game.date; + dateCell.style['white-space'] = 'nowrap'; + row.appendChild(dateCell); + + gamesTable.appendChild(row); + }); + } + else { + noScoresMessage.textContent = "No scores available"; + } + } +} + +async function setupGamesTableHeaders() { + const row = document.createElement('tr'); + + const scoresHeader = document.createElement('th'); + scoresHeader.textContent = "Score" + row.appendChild(scoresHeader); + + const opponentHeader = document.createElement('th'); + opponentHeader.textContent = "Opponent"; + row.appendChild(opponentHeader); + + const dateHeader = document.createElement('th'); + dateHeader.textContent = "Date"; + row.appendChild(dateHeader); + + gamesTable.appendChild(row); +} + + + +if(addScoreButton) { + addScoreButton.addEventListener('click', () => { + window.location.href = '/manage/game'; + }); +} + +if(manageButton) { + manageButton.addEventListener('click', () => { + window.location.href = '/manage' + }); +} \ No newline at end of file diff --git a/public/scripts/main.js b/public/scripts/main.js new file mode 100644 index 0000000..aac4daf --- /dev/null +++ b/public/scripts/main.js @@ -0,0 +1,21 @@ +const logInButton = document.getElementById('login-button'); +const logOutButton = document.getElementById('logout-button'); +const homeButton = document.getElementById('home-button'); + +if(logInButton) { + logInButton.addEventListener('click', () => { + window.location.href = "/auth/login"; + }); +} + +if(logOutButton) { + logOutButton.addEventListener('click', () => { + window.location.href = "/auth/logout"; + }); +} + +if(homeButton) { + homeButton.addEventListener('click', () => { + window.location.href = '/'; + }); +} \ No newline at end of file diff --git a/public/scripts/manage.js b/public/scripts/manage.js new file mode 100644 index 0000000..db29cad --- /dev/null +++ b/public/scripts/manage.js @@ -0,0 +1,410 @@ +import * as Data from "./data.js"; + +const categoryDropdown = document.getElementById('category-dropdown'); +const itemsListTable = document.getElementById('items-list'); +const addNewButton = document.getElementById('add-new-button'); +const loadingSpan = document.getElementById('loading-message'); + + +function getGenderLetter(genderName) { + return genderName == "female" ? "F" : "M"; +} + +class Category { + constructor(name, getItems, listHeaders, listItem, addItem, editItem) { + this.name = name; + this.getItems = getItems; + this.listHeaders = listHeaders; + this.listItem = listItem; + this.addItem = addItem; + this.editItem = editItem; + } +} + +const CATEGORIES = []; + +CATEGORIES.push(new Category( + "seasons", + async function getSeasons() { + return await Data.getSeasons(); + }, + async function listSeasonHeaders() { + const headerRow = document.createElement('tr'); + + const yearsHeader = document.createElement('th'); + yearsHeader.textContent = "Years"; + + headerRow.appendChild(yearsHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + + itemsListTable.appendChild(headerRow); + }, + function listSeason(season, row) { + const yearCell = document.createElement('td'); + yearCell.textContent = (season.year - 1) + "-" + season.year; + row.appendChild(yearCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + }, + async function addSeason() { + window.location.href = "/manage/season"; + }, + async function editSeason(id) { + const verified = confirm(`This season will be removed.`); + + if(verified) { + const form = document.createElement('form'); + form.action = "/manage/season"; + form.method = "POST"; + form.style.visibility = "hidden"; + itemsListTable.appendChild(form); + + const remove = document.createElement('input'); + remove.setAttribute('name', 'remove'); + remove.setAttribute('value', 1); + form.appendChild(remove); + + const seasonID = document.createElement('input'); + seasonID.setAttribute('name', 'season'); + seasonID.setAttribute('value', id); + form.appendChild(seasonID); + + form.submit(); + } + } +)); + +CATEGORIES.push(new Category( + "sports", + async function getSports() { + return await Data.getSports(); + }, + async function listSportHeaders() { + const headerRow = document.createElement('tr'); + + const nameHeader = document.createElement('th'); + nameHeader.textContent = "Name"; + headerRow.appendChild(nameHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + + itemsListTable.appendChild(headerRow); + }, + function listSport(sport, row) { + const nameCell = document.createElement('td'); + nameCell.textContent = sport.name; + row.appendChild(nameCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + }, + async function addSport() { + window.location.href = `/manage/sport`; + }, + async function editSport(id) { + window.location.href = `/manage/sport?sport=${id}` + } +)); + +CATEGORIES.push(new Category( + "divisions", + async function getDivisions() { + return await (await fetch('/fetch/manage/divisions')).json(); + }, + async function listDivisionHeaders() { + const headerRow = document.createElement('tr'); + + const nameHeader = document.createElement('th'); + nameHeader.textContent = "Name"; + headerRow.appendChild(nameHeader); + + const genderHeader = document.createElement('th'); + headerRow.appendChild(genderHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + + const sportHeader = document.createElement('th'); + sportHeader.textContent = "Sport"; + headerRow.appendChild(sportHeader); + + itemsListTable.appendChild(headerRow); + }, + function listDivision(division, row) { + const nameCell = document.createElement('td'); + nameCell.textContent = division.name; + row.appendChild(nameCell); + + const genderCell = document.createElement('td'); + const gender = getGenderLetter(division.gender.name); + genderCell.textContent = gender; + row.appendChild(genderCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + + const sportCell = document.createElement('td'); + let sportName = division.sport.name; + sportCell.textContent = sportName; + row.appendChild(sportCell); + }, + async function addDivision() { + window.location.href = "/manage/division"; + }, + async function editDivision(id) { + window.location.href = `/manage/division?division=${id}` + } +)); + +CATEGORIES.push(new Category( + "teams", + async function getTeams() { + return await (await fetch('/fetch/manage/teams')).json(); + }, + async function listTeamHeaders() { + const headerRow = document.createElement('tr'); + + const nameHeader = document.createElement('th'); + nameHeader.textContent = "Name"; + headerRow.appendChild(nameHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + + const sportHeader = document.createElement('th'); + sportHeader.textContent = "Sport"; + headerRow.appendChild(sportHeader); + + itemsListTable.appendChild(headerRow); + }, + function listTeam(team, row) { + const nameCell = document.createElement('td'); + nameCell.textContent = team.name; + row.appendChild(nameCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + + const sportCell = document.createElement('td'); + let sportName = team.sport.name; + sportCell.textContent = sportName; + row.appendChild(sportCell); + }, + async function addTeam() { + window.location.href = "/manage/team"; + }, + async function editTeam(id) { + window.location.href = `/manage/team?team=${id}`; + } +)); + +CATEGORIES.push(new Category( + "games", + async function getGames() { + return await (await fetch('/fetch/manage/games')).json(); + }, + async function listGameHeaders() { + const headerRow = document.createElement('tr'); + + const teamsHeader = document.createElement('th'); + teamsHeader.textContent = "Teams"; + headerRow.appendChild(teamsHeader); + + const scoreHeader = document.createElement('th'); + headerRow.appendChild(scoreHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + + const sportNameHeader = document.createElement('th'); + sportNameHeader.textContent = "Sport"; + headerRow.appendChild(sportNameHeader); + + const dateHeader = document.createElement('th'); + dateHeader.textContent = "Date"; + headerRow.appendChild(dateHeader); + + const submitterHeader = document.createElement('th'); + submitterHeader.textContent = "Submitter"; + headerRow.appendChild(submitterHeader); + + itemsListTable.appendChild(headerRow); + }, + function listGame(game, row) { + const teamsCell = document.createElement('td'); + const team1NameSpan = document.createElement('span'); + let team1Name = game.team1.name; + team1NameSpan.textContent = team1Name; + teamsCell.appendChild(team1NameSpan); + const team2NameSpan = document.createElement('span'); + let team2Name = game.team2.name; + team2NameSpan.textContent = team2Name; + teamsCell.appendChild(team2NameSpan); + row.appendChild(teamsCell); + + const scoresCell = document.createElement('td'); + const team1ScoreSpan = document.createElement('span'); + team1ScoreSpan.textContent = game.team1Score; + scoresCell.appendChild(team1ScoreSpan); + const team2ScoreSpan = document.createElement('span'); + team2ScoreSpan.textContent = game.team2Score; + scoresCell.appendChild(team2ScoreSpan); + row.appendChild(scoresCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + + const sportCell = document.createElement('td'); + const sportSpan = document.createElement('span'); + const divisionSpan = document.createElement('span'); + divisionSpan.classList.add('flat-content'); + const divisionNameSpan = document.createElement('span'); + const divisionGenderSpan = document.createElement('span'); + divisionSpan.appendChild(divisionNameSpan); + divisionSpan.appendChild(divisionGenderSpan); + let divisionName = game.division.name; + let sportName = game.sport.name; + divisionNameSpan.textContent = divisionName; + sportSpan.textContent = sportName; + divisionGenderSpan.textContent = getGenderLetter(game.division.gender.name); + sportCell.appendChild(sportSpan); + sportCell.appendChild(divisionSpan); + row.appendChild(sportCell); + + const dateCell = document.createElement('td'); + const yearSpan = document.createElement('span'); + yearSpan.textContent = game.date.slice(0,4); + dateCell.appendChild(yearSpan); + const dateSpan = document.createElement('span'); + dateSpan.textContent = game.date.slice(5); + dateCell.appendChild(dateSpan); + row.appendChild(dateCell); + + const submitterCell = document.createElement('td'); + if(game.submitterID) { + let submitterName = game.submitter.name; + submitterCell.textContent = submitterName; + } else { + submitterCell.textContent = game.submitterName; + } + row.appendChild(submitterCell); + }, + async function addGame() { + window.location.href = "/manage/game"; + }, + async function editGame(id) { + window.location.href = `/manage/game?game=${id}`; + } +)); + +CATEGORIES.push(new Category( + "accounts", + async function getAccounts() { + return await Data.getAccounts(); + }, + async function listAccountHeaders() { + const headerRow = document.createElement('tr'); + + const nameHeader = document.createElement('th'); + nameHeader.textContent = "Name"; + headerRow.appendChild(nameHeader); + + const emailHeader = document.createElement('th'); + emailHeader.textContent = "Email"; + headerRow.appendChild(emailHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + + const adminHeader = document.createElement('th'); + adminHeader.textContent = "Admin?"; + headerRow.appendChild(adminHeader); + + itemsListTable.appendChild(headerRow); + }, + function listAccount(account, row) { + const nameCell = document.createElement('td'); + nameCell.textContent = account.name; + row.appendChild(nameCell); + + const emailCell = document.createElement('td'); + emailCell.textContent = account.email; + row.appendChild(emailCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + + const adminCell = document.createElement('td'); + adminCell.textContent = account.isAdmin; + row.appendChild(adminCell); + }, + async function addAccount() { + window.location.href = "/manage/account"; + }, + async function editAccount(id) { + window.location.href = `/manage/account?account=${id}`; + } +)); + + + + +async function listItems(category) { + loadingSpan.textContent = "Loading..."; + + itemsListTable.innerHTML = ""; + + const itemsList = await category.getItems(); + + await category.listHeaders(); + + itemsList.forEach(item => { + const row = document.createElement('tr'); + + category.listItem(item, row); + + const manageCell = document.createElement('td'); + + const editSpan = document.createElement('span'); + const editButton = document.createElement('button'); + editButton.textContent = "✎"; + editButton.style['font-size'] = '1.25em'; + editButton.addEventListener('click', () => { + CATEGORIES[categoryDropdown.selectedIndex].editItem(item.id); + }); + editSpan.appendChild(editButton); + manageCell.appendChild(editSpan); + + row.appendChild(manageCell); + + itemsListTable.appendChild(row); + }); + + loadingSpan.textContent = ''; +} +if(window.location.hash) { + let correctIndex; + let index = 0; + CATEGORIES.forEach(category => { + if(window.location.hash == '#' + category.name) correctIndex = index; + index++; + }) + if(correctIndex || correctIndex === 0) categoryDropdown.selectedIndex = correctIndex; +} +listItems(CATEGORIES[categoryDropdown.selectedIndex]); + + +categoryDropdown.onchange = () => { + listItems(CATEGORIES[categoryDropdown.selectedIndex]); +}; +addNewButton.addEventListener('click', () => CATEGORIES[categoryDropdown.selectedIndex].addItem()); \ No newline at end of file diff --git a/public/scripts/manage/account.js b/public/scripts/manage/account.js new file mode 100644 index 0000000..3e14638 --- /dev/null +++ b/public/scripts/manage/account.js @@ -0,0 +1,67 @@ +import * as Data from "../data.js"; +import * as Form from "../form.js"; + +const submissionForm = document.getElementById('submission-form'); +const nameTextbox = document.getElementById('name-textbox'); +const emailTextbox = document.getElementById('email-textbox'); +const passwordTextbox = document.getElementById('password-textbox'); +const adminCheckboxSection = document.getElementById('admin-checkbox-section'); +const adminCheckbox = document.getElementById('admin-checkbox'); +const submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); +const loadingSpan = document.getElementById('loading-message'); + +async function Initialize() { + let params = new URLSearchParams(location.search); + let accountID = params.get('account') || (document.getElementById('account-id') ? document.getElementById('account-id').value : null); + if(accountID) { + const account = await Data.getAccount(accountID); + + nameTextbox.value = account.name; + + emailTextbox.value = account.email; + + passwordTextbox.placeholder = "leave unchanged"; + + adminCheckbox.checked = account.isAdmin; + + if(!document.getElementById('account-id')) { + adminCheckboxSection.style.visibility = "visible"; + adminCheckbox.disabled = false; + + Form.addHiddenValue('account', accountID, submissionForm); + } + + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + } + else + { + adminCheckboxSection.style.visibility = "visible"; + adminCheckbox.disabled = false; + } + nameTextbox.disabled = false; + nameTextbox.addEventListener('keyup', checkDataValidity); + emailTextbox.disabled = false; + emailTextbox.addEventListener('keyup', checkDataValidity); + passwordTextbox.disabled = false; + passwordTextbox.addEventListener('keyup', checkDataValidity); + checkDataValidity(); + + loadingSpan.textContent = ''; + submissionForm.style.visibility = 'visible'; +} +Initialize(); + +async function checkDataValidity() { + let dataIsValid = true; + + if(!passwordTextbox.value && !passwordTextbox.placeholder) dataIsValid = false; + if(!nameTextbox.value) dataIsValid = false; + if(!emailTextbox.value) dataIsValid = false; + + if(dataIsValid) submitButton.disabled = false; + else submitButton.disabled = true; +} + +Form.addRemoveFunction(deleteButton, submissionForm, "account"); \ No newline at end of file diff --git a/public/scripts/manage/division.js b/public/scripts/manage/division.js new file mode 100644 index 0000000..8474b4b --- /dev/null +++ b/public/scripts/manage/division.js @@ -0,0 +1,70 @@ +import * as Data from "../data.js"; +import * as Form from "../form.js"; + + +const submissionForm = document.getElementById('submission-form'); +const sportDropdown = document.getElementById('sport-dropdown'); +const genderDropdown = document.getElementById('gender-dropdown'); +const nameTextbox = document.getElementById('name-textbox'); +const submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); +const loadingSpan = document.getElementById('loading-message'); + + +async function initializeForm() { + let params = new URLSearchParams(location.search); + let divisionID = params.get('division'); + if(divisionID) { + const division = await (await fetch(`/fetch/manage/division?division=${divisionID}`)).json(); + + nameTextbox.value = division.name; + + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + + const gender = division.gender.name; + + if(gender == 'female') genderDropdown.selectedIndex = 1; + else genderDropdown.selectedIndex = 2; + + let data = {}; + data.sports = [division.sport]; + data.latestGame = {sportID : division.sportID }; + + Form.populateSports(sportDropdown, null, data); + + Form.addHiddenValue('division', divisionID, submissionForm); + } + else { + Form.populateSports(sportDropdown); + + genderDropdown.disabled = false; + + sportDropdown.disabled = false; + } + + nameTextbox.disabled = false; + nameTextbox.addEventListener('keyup', checkDataValidity); + + loadingSpan.textContent = ''; + submissionForm.style.visibility = 'visible'; +} +initializeForm(); + +async function checkDataValidity() { + let dataIsValid = true; + + if(!nameTextbox.value) dataIsValid = false; + + const sportHasContent = sportDropdown.options.length; + const genderHasContent = genderDropdown.options.length; + + if(!sportHasContent || !genderHasContent) dataIsValid = false; + + + if(dataIsValid) submitButton.disabled = false; + else submitButton.disabled = true; +} + +Form.addRemoveFunction(deleteButton, submissionForm, "division"); + diff --git a/public/scripts/manage/game.js b/public/scripts/manage/game.js new file mode 100644 index 0000000..c61d05b --- /dev/null +++ b/public/scripts/manage/game.js @@ -0,0 +1,128 @@ +import * as Data from "./../data.js"; +import * as Form from "./../form.js"; + + +const submissionForm = document.getElementById('submission-form'); +const sportDropdown = document.getElementById('sport-dropdown'); +const seasonDropdown = document.getElementById('year-dropdown'); +const genderDropdown = document.getElementById('gender-dropdown'); +const divisionDropdown = document.getElementById('division-dropdown'); +const dateInput = document.getElementById('date-input'); +const team1Dropdown = document.getElementById('team1-dropdown'); +const team2Dropdown = document.getElementById('team2-dropdown'); +const team1ScoreTextbox = document.getElementById('team1-score-textbox'); +const team2ScoreTextbox = document.getElementById('team2-score-textbox'); +const nameTextbox = document.getElementById('name-textbox'); +const submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); +const loadingSpan = document.getElementById('loading-span'); + + +async function initializeForm() { + let params = new URLSearchParams(location.search); + let gameID = params.get('game'); + if(gameID) { + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + + const game = await Data.getGame(gameID); + + Form.addHiddenValue('game', gameID, submissionForm); + + Form.populateSeasons(seasonDropdown, game.seasonID); + const data = await Data.getDivision(game.divisionID) + await Form.populateSports(sportDropdown, data.sportID) + await Form.populateGenders(genderDropdown, sportDropdown.value, data.gender.name) + await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value, game.divisionID); + await Form.populateTeams(team1Dropdown, sportDropdown.value, game.team1ID); + await Form.populateTeams(team2Dropdown, sportDropdown.value, game.team2ID); + + dateInput.value = game.date; + team1ScoreTextbox.value = game.team1Score; + team2ScoreTextbox.value = game.team2Score; + } + else { + /*try {*/ + const data = await (await fetch(`/fetch/index/dropdown`)).json(); + + await Form.populateSeasons(seasonDropdown, null, data); + await Form.populateSports(sportDropdown, null, data); + await Form.populateGenders(genderDropdown, null, null, data); + await Form.populateDivisions(divisionDropdown, null, null, null, data); + await Form.populateTeams(team1Dropdown, null, null, data); + await Form.populateTeams(team2Dropdown, null, null, data, true); + /*} catch { + await Form.populateSeasons(seasonDropdown); + await Form.populateSports(sportDropdown) + await Form.populateGenders(genderDropdown, sportDropdown.value) + await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + await Form.populateTeams(team1Dropdown, sportDropdown.value); + await Form.populateTeams(team2Dropdown, sportDropdown.value); + }*/ + + dateInput.value = (new Date()).toISOString().slice(0,10); + } + seasonDropdown.disabled = false; + sportDropdown.disabled = false; + genderDropdown.disabled = false; + divisionDropdown.disabled = false; + dateInput.disabled = false; + team1Dropdown.disabled = false; + team2Dropdown.disabled = false; + team1ScoreTextbox.disabled = false; + team2ScoreTextbox.disabled = false; + if(nameTextbox) { + nameTextbox.disabled = false; + } + + sportDropdown.onchange = async () => { + await Form.populateGenders(genderDropdown, sportDropdown.value) + await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + await Form.populateTeams(team1Dropdown, sportDropdown.value); + await Form.populateTeams(team2Dropdown, sportDropdown.value); + checkDataValidity(); + }; + + genderDropdown.onchange = async () => { + await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + checkDataValidity(); + }; + + dateInput.addEventListener('keyup', checkDataValidity); + team1Dropdown.onchange = checkDataValidity; + team1ScoreTextbox.addEventListener('keyup', checkDataValidity); + team2Dropdown.onchange = checkDataValidity; + team2ScoreTextbox.addEventListener('keyup', checkDataValidity); + if(nameTextbox) nameTextbox.addEventListener('keyup', checkDataValidity); + + loadingSpan.textContent = ''; + submissionForm.style.visibility = 'visible'; + + checkDataValidity(); +} +initializeForm(); + +async function checkDataValidity() { + let dataIsValid = true; + + const seasonHasContent = seasonDropdown.options.length; + const sportHasContent = sportDropdown.options.length; + const genderHasContent = genderDropdown.options.length; + const divisionHasContent = divisionDropdown.options.length; + const team1HasContent = team1Dropdown.options.length; + const team2HasContent = team2Dropdown.options.length; + + if(!seasonHasContent || !sportHasContent || !genderHasContent || !divisionHasContent || !team1HasContent || !team2HasContent) dataIsValid = false; + + if(team1Dropdown.selectedIndex == team2Dropdown.selectedIndex) dataIsValid = false; + + if(team1ScoreTextbox.value == "" || team2ScoreTextbox.value == "") dataIsValid = false; + + if(dateInput.value == "") dataIsValid = false; + + if(nameTextbox && nameTextbox.value == "") dataIsValid = false; + + submitButton.disabled = !dataIsValid; +} + +Form.addRemoveFunction(deleteButton, submissionForm, "game"); diff --git a/public/scripts/manage/manage-nonadmin.js b/public/scripts/manage/manage-nonadmin.js new file mode 100644 index 0000000..86782aa --- /dev/null +++ b/public/scripts/manage/manage-nonadmin.js @@ -0,0 +1,132 @@ +import * as Data from "./../data.js"; + +const gamesListTable = document.getElementById('games-list'); +const addNewButton = document.getElementById('add-new-button'); +const manageAccountButton = document.getElementById('manage-account-button'); + + + +function getGenderLetter(genderName) { + return genderName == "female" ? "F" : "M"; +} + +async function listGameHeaders() { + const headerRow = document.createElement('tr'); + + const teamsHeader = document.createElement('th'); + teamsHeader.textContent = "Teams"; + headerRow.appendChild(teamsHeader); + + const scoreHeader = document.createElement('th'); + headerRow.appendChild(scoreHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + + const sportNameHeader = document.createElement('th'); + sportNameHeader.textContent = "Sport"; + headerRow.appendChild(sportNameHeader); + + const dateHeader = document.createElement('th'); + dateHeader.textContent = "Date"; + headerRow.appendChild(dateHeader); + + gamesListTable.appendChild(headerRow); +} + + +function listGame(game, row) { + const teamsCell = document.createElement('td'); + const team1NameSpan = document.createElement('span'); + Data.getTeam(game.team1ID) + .then(data => team1NameSpan.textContent = data.name); + teamsCell.appendChild(team1NameSpan); + const team2NameSpan = document.createElement('span'); + Data.getTeam(game.team2ID) + .then(data => team2NameSpan.textContent = data.name); + teamsCell.appendChild(team2NameSpan); + row.appendChild(teamsCell); + + const scoresCell = document.createElement('td'); + const team1ScoreSpan = document.createElement('span'); + team1ScoreSpan.textContent = game.team1Score; + scoresCell.appendChild(team1ScoreSpan); + const team2ScoreSpan = document.createElement('span'); + team2ScoreSpan.textContent = game.team2Score; + scoresCell.appendChild(team2ScoreSpan); + row.appendChild(scoresCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + + const sportCell = document.createElement('td'); + const sportSpan = document.createElement('span'); + const divisionSpan = document.createElement('span'); + divisionSpan.classList.add('flat-content'); + const divisionNameSpan = document.createElement('span'); + const divisionGenderSpan = document.createElement('span'); + divisionSpan.appendChild(divisionNameSpan); + divisionSpan.appendChild(divisionGenderSpan); + Data.getDivision(game.divisionID) + .then(data => { + Data.getSportName(data.sportID) + .then(data => sportSpan.textContent = data); + divisionNameSpan.textContent = data.name; + divisionGenderSpan.textContent = getGenderLetter(data.gender.name); + }); + sportCell.appendChild(sportSpan); + sportCell.appendChild(divisionSpan); + row.appendChild(sportCell); + + const dateCell = document.createElement('td'); + const yearSpan = document.createElement('span'); + yearSpan.textContent = game.date.slice(0,4); + dateCell.appendChild(yearSpan); + const dateSpan = document.createElement('span'); + dateSpan.textContent = game.date.slice(5); + dateCell.appendChild(dateSpan); + row.appendChild(dateCell); +} + +async function addGame() { + window.location.href = "/manage/game"; +} + +async function editGame(id) { + window.location.href = `/manage/game?game=${id}`; +} + + +async function listItems() { + const gamesList = await Data.getGamesByUser(); + + await listGameHeaders(); + + gamesList.forEach(game => { + const row = document.createElement('tr'); + + listGame(game, row); + + const manageCell = document.createElement('td'); + + const editSpan = document.createElement('span'); + const editButton = document.createElement('button'); + editButton.textContent = "E"; + editButton.addEventListener('click', () => { + editGame(game.id); + }); + editSpan.appendChild(editButton); + manageCell.appendChild(editSpan); + + row.appendChild(manageCell); + + gamesListTable.appendChild(row); + }); +} +listItems(); + +addNewButton.addEventListener('click', () => addGame()); +manageAccountButton.addEventListener('click', () => { + window.location.href = '/manage/account'; +}); \ No newline at end of file diff --git a/public/scripts/manage/season.js b/public/scripts/manage/season.js new file mode 100644 index 0000000..82d0204 --- /dev/null +++ b/public/scripts/manage/season.js @@ -0,0 +1,14 @@ +const seasonTextbox = document.getElementById('season-textbox'); +const submitButton = document.getElementById('submit-button'); + +async function checkDataValidity() { + let dataIsValid = true; + + if(seasonTextbox.value == "") dataIsValid = false; + + submitButton.disabled = !dataIsValid; +} +checkDataValidity(); + +seasonTextbox.addEventListener('keyup', checkDataValidity); +seasonTextbox.addEventListener('change', checkDataValidity); diff --git a/public/scripts/manage/sport.js b/public/scripts/manage/sport.js new file mode 100644 index 0000000..b44b41a --- /dev/null +++ b/public/scripts/manage/sport.js @@ -0,0 +1,59 @@ +import * as Data from "../data.js"; + + +const nameTextbox = document.getElementById('name-textbox'); +const submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); +const submissionForm = document.getElementById('submission-form'); +const loadingSpan = document.getElementById('loading-message'); + + +async function initializeForm() { + let params = new URLSearchParams(location.search); + let sportID = params.get('sport') + if(sportID) { + const sportName = await Data.getSportName(sportID); + + nameTextbox.value = sportName; + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + + const sportIDInput = document.createElement('input'); + sportIDInput.setAttribute('name', 'sport'); + sportIDInput.setAttribute('value', sportID); + sportIDInput.setAttribute('type', 'hidden'); + submissionForm.appendChild(sportIDInput); + } + nameTextbox.disabled = false; + + nameTextbox.addEventListener('keyup', checkDataValidity); + + loadingSpan.textContent = ''; + submissionForm.style.visibility = 'visible'; +} +initializeForm(); + +async function checkDataValidity() { + let dataIsValid = true; + + if(!nameTextbox.value) dataIsValid = false; + + + if(dataIsValid) submitButton.disabled = false; + else submitButton.disabled = true; +} + +async function removeSport() { + const removeInput = document.createElement('input'); + removeInput.setAttribute('name', 'remove'); + removeInput.setAttribute('value', 1); + removeInput.setAttribute('type', 'hidden'); + submissionForm.appendChild(removeInput); + submissionForm.submit(); +} + +deleteButton.addEventListener('click', () => { + const verified = confirm("This sport will be removed."); + + if(verified) removeSport(); +}); \ No newline at end of file diff --git a/public/scripts/manage/team.js b/public/scripts/manage/team.js new file mode 100644 index 0000000..a7eadce --- /dev/null +++ b/public/scripts/manage/team.js @@ -0,0 +1,57 @@ +import * as Data from "../data.js"; +import * as Form from "../form.js"; + + +const submissionForm = document.getElementById('submission-form'); +const sportDropdown = document.getElementById('sport-dropdown'); +const nameTextbox = document.getElementById('name-textbox'); +const submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); +const loadingSpan = document.getElementById('loading-message'); + + +async function initializeForm() { + let params = new URLSearchParams(location.search); + let teamID = params.get('team'); + if(teamID) { + const team = await (await fetch(`/fetch/manage/team?team=${teamID}`)).json(); + + nameTextbox.value = team.name; + + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + + let data = {}; + data.sports = [team.sport]; + data.latestGame = {sportID : team.sportID }; + + Form.populateSports(sportDropdown, null, data); + + Form.addHiddenValue('team', teamID, submissionForm); + } + else { + sportDropdown.disabled = false; + + Form.populateSports(sportDropdown); + } + + nameTextbox.disabled = false; + nameTextbox.addEventListener('keyup', checkDataValidity); + + loadingSpan.textContent = ''; + submissionForm.style.visibility = 'visible'; +} +initializeForm(); + +async function checkDataValidity() { + let dataIsValid = true; + + if(!nameTextbox.value) dataIsValid = false; + + const sportHasContent = sportDropdown.options.length; + if(!sportHasContent) dataIsValid = false; + + submitButton.disabled = !dataIsValid; +} + +Form.addRemoveFunction(deleteButton, submissionForm, "team"); \ No newline at end of file diff --git a/public/stylesheets/error.css b/public/stylesheets/error.css new file mode 100644 index 0000000..f1ac617 --- /dev/null +++ b/public/stylesheets/error.css @@ -0,0 +1,3 @@ +#mobile-view { + max-width: 100%; +} \ No newline at end of file diff --git a/public/stylesheets/form.css b/public/stylesheets/form.css new file mode 100644 index 0000000..c040b27 --- /dev/null +++ b/public/stylesheets/form.css @@ -0,0 +1,55 @@ +form { + display: flex; + flex-direction: column; + } + + span { + display: flex; + flex-direction: column; + } + + + .form-main-dropdown { + width: 100%; + } + + .form-section { + margin-bottom: 0.75em; + } + + .form-section-input { + flex-direction: row; + } + + input { + width: 100%; + } + + .form-score-input{ + width: 35%; + } + + #submit-button { + margin-top: 1.5em; + } + + #delete-button { + visibility: hidden; + } + + .form-section-checkbox { + flex-direction: row; + align-items: center; + } + + #admin-checkbox { + width: auto; + } + + .flat-form-section { + flex-direction: row; + } + +#submission-form { + visibility: hidden; +} \ No newline at end of file diff --git a/public/stylesheets/index.css b/public/stylesheets/index.css new file mode 100644 index 0000000..bf0a4ae --- /dev/null +++ b/public/stylesheets/index.css @@ -0,0 +1,38 @@ +h1 { + text-align: left; +} + +th { + text-align: left; +} + +#score-column { + width: 20%; +} +#opponent-column{ + width: 60%; +} +#date-column { + width: 20%; +} + +tr { + height: 3em; +} + +#header-div { + display: flex; + flex-direction: row; +} + +#add-score-button { + margin-right: auto; +} + +#login-button { + margin-left: auto; +} + +#dropdowns-div { + visibility: hidden; +} \ No newline at end of file diff --git a/public/stylesheets/manage.css b/public/stylesheets/manage.css new file mode 100644 index 0000000..6a5359f --- /dev/null +++ b/public/stylesheets/manage.css @@ -0,0 +1,11 @@ +th { + text-align: left; +} + +td { + white-space: nowrap; +} + +.spacer-column { + width: 100%; +} \ No newline at end of file diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 9453385..1255c30 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -1,8 +1,71 @@ body { - padding: 50px; + padding: 1em; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } a { color: #00B7FF; } + +#mobile-view { + max-width: 20em; + margin-left: auto; + margin-right: auto; + display: flex; + flex-direction: column; +} + +.flat-content { + flex-direction: row; + justify-content: space-between; +} + +.send-to-right { + margin-left: auto; +} + +#actions-div { + display: flex; +} + +#home-button { + margin-right: auto; +} + +button { + padding: 0.25em; + margin: 0em 0.1em; +} + +select { + padding: 0.25em; +} + +input { + padding: 0.25em; +} + +#logout-button { + margin-left: 0.5em; +} + +#noscript-message { + background-color: lightcoral; + padding: 1em; + margin-bottom: 3em; + border-radius: .25em; +} + +#about-footer { + margin-top: 3em; + text-align: center; +} + + +p { + line-height: 2; +} + +a { + color: black; +} \ No newline at end of file diff --git a/public/stylesheets/submit.css b/public/stylesheets/submit.css new file mode 100644 index 0000000..30e64a5 --- /dev/null +++ b/public/stylesheets/submit.css @@ -0,0 +1,7 @@ +h1 { + text-align: center; +} + +#admin-checkbox-section { + visibility: hidden; +} \ No newline at end of file diff --git a/routes/users.js b/routes/about.js similarity index 63% rename from routes/users.js rename to routes/about.js index 623e430..7162c62 100644 --- a/routes/users.js +++ b/routes/about.js @@ -1,9 +1,8 @@ var express = require('express'); var router = express.Router(); -/* GET users listing. */ router.get('/', function(req, res, next) { - res.send('respond with a resource'); + res.render('about', { title: 'About Score Tracker', hideHomeButton: false }); }); module.exports = router; diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..1e1940b --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,27 @@ +var express = require('express'); +var router = express.Router(); +const passport = require('passport'); +const app = require('../app'); + +router.get('/login', (req, res, next) => { + res.render('accounts/login', { title : "Login", message: req.flash('error') }); +}); + +router.get('/logout', (req, res, next) => { + req.logout(); + res.redirect("/"); +}); + +router.post('/login', + passport.authenticate('local', { + failureRedirect: '/auth/login', + successRedirect: '/', + failureFlash: "Invalid email or password.", + }), + (req, res, next) => { + console.log(req.user); +}); + + + +module.exports = router; \ No newline at end of file diff --git a/routes/checkLoginStatus.js b/routes/checkLoginStatus.js new file mode 100644 index 0000000..d9fd970 --- /dev/null +++ b/routes/checkLoginStatus.js @@ -0,0 +1,21 @@ +function adminLoggedIn(req, res, next) { + if (req.user && req.user[2]) { + next(); + } + else { + req.flash('error', 'An admin account is required to access this page.'); + res.redirect('/auth/login'); + } +} + +function userLoggedIn(req, res, next) { + if (req.user) { + next(); + } + else { + res.redirect('/auth/login'); + } +} + +exports.admin = adminLoggedIn; +exports.user = userLoggedIn; \ No newline at end of file diff --git a/routes/data.js b/routes/data.js new file mode 100644 index 0000000..55c1970 --- /dev/null +++ b/routes/data.js @@ -0,0 +1,167 @@ +var express = require('express'); +var router = express.Router(); + +var sports = require('../database/scores/sports'); +var seasons = require('../database/scores/seasons'); +var genders = require('../database/scores/genders'); +var divisions = require('../database/scores/divisions'); +var teams = require('../database/scores/teams'); +var games = require('../database/scores/games'); +var accounts = require('../database/accounts/accounts'); + +var checkLoginStatus = require('./checkLoginStatus'); + +router.get('/sports', async function(req, res, next) { + try { + const data = await sports.retrieveAll(); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}); + +router.get('/sport', async function(req, res, next) { + try { + const sportID = req.query.sport; + const data = await sports.getFromID(sportID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}); + +router.get('/seasons', async function(req, res, next) { + try { + const data = await seasons.retrieveAll(); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}) + +router.get('/genders', async function(req, res, next) { + try { + const sportID = req.query.sport; + const data = await genders.retrieveBySport(sportID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}) + +router.get('/divisions', async function(req, res, next) { + try{ + let gender; + if(req.query.gender) gender = (req.query.gender == 'female' ? genders.FEMALE : genders.MALE); + + const sportID = req.query.sport; + const data = await divisions.retrieve(sportID, gender); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}) + +router.get('/division', async function(req, res, next) { + try { + const divisionID = req.query.division; + const data = await divisions.getFromID(divisionID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}) + +router.get('/teams', async function(req, res, next) { + try { + const sportID = req.query.sport; + const data = await teams.retrieve(sportID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}) + +router.get('/team', async function(req, res, next) { + try { + const teamID = req.query.team; + const data = await teams.getFromID(teamID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}); + +router.get('/games', async function(req, res, next) { + try { + const userID = req.user ? req.user[0] : null; + if(req.query.user) { + const data = await games.retrieveByUser(userID); + res.json(data); + } else { + const seasonID = req.query.season; + const divisionID = req.query.division; + const teamID = req.query.team; + const data = await games.retrieve(teamID, divisionID, seasonID); + res.json(data); + } + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}) + +router.get('/game', async function(req, res, next) { + try { + const gameID = req.query.game; + const ofUser = req.query.ofuser; + const currentUserID = req.user ? req.user[0] : null; + + let data; + if(gameID) data = await games.getFromID(gameID); + else if(ofUser) data = await games.getLatest(currentUserID); + else data = await games.getLatest(); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}) + +router.get('/accounts', checkLoginStatus.admin, async function(req, res, next) { + try { + const data = await accounts.retrieveAll(); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}) + +router.get('/account', checkLoginStatus.user, async function(req, res, next) { + try{ + const userIsAdmin = req.user[2]; + const loggedInAccountID = req.user[0]; + const requestedAccountID = req.query.account; + + if(!userIsAdmin && loggedInAccountID != requestedAccountID) { + res.status(403).send("ACCESS DENIED"); + } else { + const data = await accounts.getFromID(req.query.account); + res.json(data); + } + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}) + +module.exports = router; \ No newline at end of file diff --git a/routes/fetch.js b/routes/fetch.js new file mode 100644 index 0000000..4958fd3 --- /dev/null +++ b/routes/fetch.js @@ -0,0 +1,200 @@ +var express = require('express'); +var router = express.Router(); + +var sports = require('../database/scores/sports'); +var seasons = require('../database/scores/seasons'); +var genders = require('../database/scores/genders'); +var divisions = require('../database/scores/divisions'); +var teams = require('../database/scores/teams'); +var games = require('../database/scores/games'); +var accounts = require('../database/accounts/accounts'); + +var checkLoginStatus = require('./checkLoginStatus'); + +router.get('/index/dropdown', async function(req, res, next) { + let latestGame; + let seasonsData; + let sportsData; + let gendersData; + let divisionsData; + let teamsData; + + try { + latestGame = await games.getLatest(); + + let division = await divisions.getFromID(latestGame.divisionID); + latestGame.sportID = division.sportID; + latestGame.gender = division.gender; + } catch { + latestGame = null; + } + + + try { + if(latestGame) { + seasonsData = await seasons.retrieveAll(); + sportsData = await sports.retrieveAll(); + gendersData = await genders.retrieveBySport(latestGame.sportID); + divisionsData = await divisions.retrieve(latestGame.sportID); + teamsData = await teams.retrieve(latestGame.sportID); + } else { + seasonsData = await seasons.retrieveAll(); + sportsData = await sports.retrieveAll(); + gendersData = await genders.retrieveBySport(sportsData[0].id); + divisionsData = await divisions.retrieve(sportsData[0].id); + teamsData = await teams.retrieve(sportsData[0].id); + } + + res.json({ + seasons : seasonsData, + sports : sportsData, + genders : gendersData, + divisions : divisionsData, + teams: teamsData, + latestGame + }); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}); + +router.get('/index/scores', async function (req, res, next) { + try { + const seasonID = req.query.season; + const divisionID = req.query.division; + const teamID = req.query.team; + const data = await games.retrieve(teamID, divisionID, seasonID); + for (const game of data) { + game.opponent = await teams.getFromID(game.team2ID); + } + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}); + + +router.get('/submit', async function(req, res, next) { + let latestGame; + let seasonsData; + let sportsData; + let gendersData; + let divisionsData; + let teamsData; + + const userID = req.user ? req.user[0] : null; + + try { + latestGame = await games.getLatest(userID); + + let division = await divisions.getFromID(latestGame.divisionID); + latestGame.sportID = division.sportID; + latestGame.gender = division.gender; + } catch { + latestGame = null; + } + + + try { + if(latestGame) { + seasonsData = await seasons.retrieveAll(); + sportsData = await sports.retrieveAll(); + gendersData = await genders.retrieveBySport(latestGame.sportID); + divisionsData = await divisions.retrieve(latestGame.sportID); + teamsData = await teams.retrieve(latestGame.sportID); + } else { + seasonsData = await seasons.retrieveAll(); + sportsData = await sports.retrieveAll(); + gendersData = await genders.retrieveBySport(sportsData[0].id); + divisionsData = await divisions.retrieve(sportsData[0].id); + teamsData = await teams.retrieve(sportsData[0].id); + } + + res.json({ + seasons : seasonsData, + sports : sportsData, + genders : gendersData, + divisions : divisionsData, + teams: teamsData, + latestGame + }); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}); + +router.get('/manage/divisions', async function (req, res, next) { + const data = await divisions.retrieve(); + + for(const division of data) { + division.sport = await sports.getFromID(division.sportID); + } + + res.json(data); +}); + +router.get('/manage/division', async function (req, res, next) { + try { + const divisionID = req.query.division; + const data = await divisions.getFromID(divisionID); + data.sport = await sports.getFromID(data.sportID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}); + +router.get('/manage/teams', async function (req, res, next) { + const data = await teams.retrieve(); + + for(const team of data) { + team.sport = await sports.getFromID(team.sportID); + } + + res.json(data); +}); + +router.get('/manage/team', async function (req, res, next) { + try { + const teamID = req.query.team; + const data = await teams.getFromID(teamID); + data.sport = await sports.getFromID(data.sportID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } +}); + +router.get('/manage/games', checkLoginStatus.user, async function (req, res, next) { + try{ + const userIsAdmin = req.user[2]; + const loggedInAccountID = req.user[0]; + + if(!userIsAdmin) { + res.status(403).send("ACCESS DENIED"); + } else { + const data = await games.retrieve(); + + for(const game of data) { + game.team1 = await teams.getFromID(game.team1ID); + game.team2 = await teams.getFromID(game.team2ID); + game.division = await divisions.getFromID(game.divisionID); + game.sport = await sports.getFromID(game.division.sportID); + game.submitter = game.submitterName || (await accounts.getFromID(game.submitterID)); + } + + res.json(data); + } + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } + +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index ecca96a..c18f5a5 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,9 +1,12 @@ var express = require('express'); var router = express.Router(); -/* GET home page. */ router.get('/', function(req, res, next) { - res.render('index', { title: 'Express' }); + res.render('index', { title: 'View Scores', userLoggedIn: !!req.user, hideHomeButton: true }); +}); + +router.get('/submit', function(req, res, next) { + res.redirect('/manage/game'); }); module.exports = router; diff --git a/routes/manage.js b/routes/manage.js new file mode 100644 index 0000000..bad5f4c --- /dev/null +++ b/routes/manage.js @@ -0,0 +1,259 @@ +var express = require('express'); +var router = express.Router(); + +var genders = require('../database/scores/genders'); +var games = require('../database/scores/games'); +var seasons = require('../database/scores/seasons'); +var sports = require('../database/scores/sports'); +var divisions = require('../database/scores/divisions'); +var genders = require('../database/scores/genders'); +var teams = require('../database/scores/teams'); +var accounts = require('../database/accounts/accounts'); + +var checkLoginStatus = require('./checkLoginStatus'); + +if (process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'testing') { + require('dotenv').config(); +} + + +router.get('/' ,checkLoginStatus.user, function(req, res, next) { + if(req.user[2]) res.render('manage', { title: 'Management Panel', userLoggedIn: !!req.user }); + else res.render('manage/manage-nonadmin', { title: "My Games", userLoggedIn: !!req.user }); +}); + +router.get('/game', function(req, res, next) { + if(!(process.env.PUBLIC_SUBMIT_PAGE && process.env.PUBLIC_SUBMIT_PAGE.toLowerCase() == 'true')) { + if (req.user) { + next(); + } + else { + res.redirect('/auth/login'); + } + } else { + next(); + } + }, + function(req, res, next) { + let title = req.query.game ? 'Edit Game' : 'Submit Score'; + + res.render('manage/addgame', { title, userLoggedIn: !!req.user, message: req.flash('error') }); +}); + +router.post('/game', function(req, res, next) { + if(!(process.env.PUBLIC_SUBMIT_PAGE && process.env.PUBLIC_SUBMIT_PAGE.toLowerCase() == 'true')) { + if (req.user) { + next(); + } + else { + res.redirect('/auth/login'); + } + } else { + next(); + } +}, + async function(req, res, next) { + const id = req.body['game']; + const remove = req.body['remove']; + + try { + const seasonID = req.body['year']; + const sportID = req.body['sport']; + const gender = (req.body['gender'] == "female") ? genders.FEMALE : genders.MALE; + const divisionID = req.body['division']; + const date = req.body['date']; + const team1ID = req.body['team1']; + const team1Score = req.body['team1-score']; + const team2ID = req.body['team2']; + const team2Score = req.body['team2-score']; + const submitterName = req.body['name']; + + let submitterID; + let loggedInUserID; + let loggedInUserIsAdmin; + if(req.user) { + submitterID = req.user[0]; + loggedInUserID = req.user[0]; + loggedInUserIsAdmin = req.user[2]; + } + + const game = id ? await games.getFromID(id) : null; + + if((!loggedInUserIsAdmin && game && loggedInUserID != game.submitterID) || (!req.user && game)) { + res.status(403).send("ACCESS DENIED"); + } + else if(remove) { + await games.remove(id); + res.redirect('/manage#games'); + } + else if(id) { + await games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score); + res.redirect('/manage#games'); + } + else { + await games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, submitterID, submitterName); + res.redirect('/'); + } + } catch(err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/game' + (id ? `?game=${id}` : '')); + } +}); + +router.get('/season', checkLoginStatus.admin, function(req, res, next) { + res.render('manage/addseason', { title: 'Add Season', currentYear : (new Date()).getFullYear(), userLoggedIn: !!req.user, message: req.flash('error') }); +}); + +router.post('/season', checkLoginStatus.admin, async function(req, res, next) { + const seasonID = req.body['season']; + const remove = req.body['remove']; + + try { + const year = req.body['year']; + + if(remove) await seasons.remove(seasonID); + else await seasons.add(year); + + res.redirect('/manage#seasons'); + } catch(err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/season' + (seasonID ? `?season=${seasonID}` : '')); + } +}); + +router.get('/sport', checkLoginStatus.admin, function(req, res, next) { + let title = req.query.sport ? 'Edit Sport' : 'Add Sport'; + + res.render('manage/addsport', { title, userLoggedIn: !!req.user, message: req.flash('error') }); +}); + +router.post('/sport', checkLoginStatus.admin, async function(req, res, next) { + const id = req.body['sport']; + const remove = req.body['remove']; + + try { + const name = req.body['name']; + + if(remove) await sports.remove(id); + else if(id) await sports.rename(id, name); + else await sports.add(name); + + res.redirect('/manage#sports'); + } catch(err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/sport' + (id ? `?sport=${id}` : '')); + } +}); + +router.get('/division', checkLoginStatus.admin, function(req, res, next) { + let title = req.query.division ? 'Edit Division' : 'Add Division' + + res.render('manage/adddivision', { title, userLoggedIn: !!req.user, message: req.flash('error') }); +}); + +router.post('/division', checkLoginStatus.admin, async function(req, res, next) { + const id = req.body['division']; + const remove = req.body['remove']; + + try { + const name = req.body['name']; + const sport = req.body['sport']; + const genderName = req.body['gender']; + + if(remove) await divisions.remove(id); + else if(id) await divisions.rename(id, name); + else { + if(genderName == 'both') { + await divisions.add(name, genders.FEMALE, sport); + await divisions.add(name, genders.MALE, sport); + } else { + const gender = (genderName == "female") ? genders.FEMALE : genders.MALE; + await divisions.add(name, gender, sport); + } + } + res.redirect('/manage#divisions'); + } catch(err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/division' + (id ? `?division=${id}` : '')); + } +}); + +router.get('/team', checkLoginStatus.admin, function(req, res, next) { + let title = req.query.team ? 'Edit Team' : 'Add Team' + + res.render('manage/addteam', { title, userLoggedIn: !!req.user, message: req.flash('error') }); +}); + +router.post('/team', checkLoginStatus.admin, async function(req, res, next) { + const id = req.body['team']; + const remove = req.body['remove']; + + try { + const name = req.body['name']; + const sport = req.body['sport']; + + if(remove) teams.remove(id).then(res.redirect('/manage#teams')); + else if(id) teams.rename(id, name).then(res.redirect('/manage#teams')); + else teams.add(name, sport).then(res.redirect('/manage#teams')); + } catch(err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/team' + (id ? `?team=${id}` : '')); + } +}); + +router.get('/account', checkLoginStatus.user, (req, res, next) => { + const userIsAdmin = req.user[2]; + const accountID = req.user[0]; + + if(userIsAdmin) { + let title = req.query.account ? 'Manage User' : 'Create User' + + res.render('accounts/createuser', { title, userLoggedIn: !!req.user, message: req.flash('error') }); + } + else { + let title = 'Manage Account'; + + res.render('accounts/createuser', { title, accountID, userLoggedIn: !!req.user, message: req.flash('error') }); + } +}); + +router.post('/account', checkLoginStatus.user, async function(req, res, next) { + const name = req.body.name; + const email = req.body.email; + const password = req.body.password; + + const accountID = req.body.account; + const remove = req.body.remove; + + const loggedInAccountIsAdmin = req.user[2]; + const loggedInAccountID = req.user[0]; + + if(!loggedInAccountIsAdmin && accountID != loggedInAccountID) { + res.status(403).send("ACCESS DENIED"); + } + else { + try { + const isAdmin = loggedInAccountIsAdmin ? !!req.body.admin : false; + + if(remove) await accounts.remove(accountID); + else if(accountID) await accounts.edit(accountID, email, password, isAdmin, name); + else await accounts.create(email, password, !!req.body.admin, name); + + res.redirect('/manage#accounts'); + } + catch (err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + let URL = '/manage/account'; + if(loggedInAccountIsAdmin && accountID) URL += `?account=${accountID}`; + res.redirect(URL); + } + } +}); + +module.exports = router; diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 4f2ac60..0000000 --- a/test/test.js +++ /dev/null @@ -1,10 +0,0 @@ -const request = require('supertest'); -const app = require('../app'); - -describe('App', function() { - it('has the default page', function(done) { - request(app) - .get('/') - .expect(/Welcome to Express/, done); - }); -}); diff --git a/views/about.pug b/views/about.pug new file mode 100644 index 0000000..d22f563 --- /dev/null +++ b/views/about.pug @@ -0,0 +1,13 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/index.css') + +block actions + + + +block content + p Created by #[a(href="https://ethanreece.com") Ethan Reece], a student at #[a(href="https://colevalleychristian.org") Cole Valley Christian Schools]. + p Need help? Found a bug? Email: #[a(href="mailto:scoretrackerhelp@ethanreece.com") scoretrackerhelp@ethanreece.com] + p #[a(href="https://gitlab.sudoer.ch/sudoer777/score-tracker") Git repo] \ No newline at end of file diff --git a/views/accounts/createuser.pug b/views/accounts/createuser.pug new file mode 100644 index 0000000..a43aad9 --- /dev/null +++ b/views/accounts/createuser.pug @@ -0,0 +1,35 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + span#loading-message Loading... + form#submission-form(action='/manage/account', method='POST') + if accountID + input#account-id(type="hidden" name="account" value=accountID) + span(class='form-section') + label Name + span(class='form-section-input') + input#name-textbox(type="text" name="name" disabled) + span(class='form-section') + label Email + span(class='form-section-input') + input#email-textbox(type="email" name="email" disabled) + span(class='form-section') + label Password + span(class='form-section-input' ) + input#password-textbox(type="password" name="password" disabled) + span#admin-checkbox-section(class='form-section') + span(class='form-section-checkbox') + input#admin-checkbox(type="checkbox" name="admin" disabled) + label(for="admin-checkbox") Grant admin privileges + .error #{message} + span(class='form-section') + button#submit-button(type="submit" disabled) Submit + span(class='form-section') + button#delete-button(type="delete" disabled) Delete + +block scripts + script(src='/scripts/manage/account.js' type="module") \ No newline at end of file diff --git a/views/accounts/login.pug b/views/accounts/login.pug new file mode 100644 index 0000000..cbc9044 --- /dev/null +++ b/views/accounts/login.pug @@ -0,0 +1,19 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + form(action='/auth/login', method='POST') + span(class='form-section') + label Email + span(class='form-section-input') + input(type="email", name="email") + span(class='form-section') + label Password + span(class='form-section-input') + input(type="password", name="password") + .error #{message} + span(class='form-section') + button#submit-button(type="submit") Submit \ No newline at end of file diff --git a/views/error.pug b/views/error.pug index 51ec12c..43a7357 100644 --- a/views/error.pug +++ b/views/error.pug @@ -1,6 +1,9 @@ extends layout +block stylesheets + link(rel='stylesheet', href='/stylesheets/error.css') + block content h1= message h2= error.status - pre #{error.stack} + pre #{error.stack} \ No newline at end of file diff --git a/views/index.pug b/views/index.pug index 3d63b9a..1a22bbe 100644 --- a/views/index.pug +++ b/views/index.pug @@ -1,5 +1,49 @@ extends layout +block stylesheets + link(rel='stylesheet', href='/stylesheets/index.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block actions + if userLoggedIn + button#add-score-button Submit score + button#manage-button Manage... + + block content - h1= title - p Welcome to #{title} + span#loading Loading... + div#dropdowns-div + span(class='form-section') + label Year + span(class='form-section-input') + select#year-dropdown(name="year" class="form-main-dropdown") + span(class='form-section flat-form-section') + span + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="form-main-dropdown") + span + label Gender + span(class='form-section-input') + select#gender-dropdown(name="gender") + span + label Division + span(class='form-section-input') + select#division-dropdown(name="division") + span(class='form-section') + label Team + span(class='form-section-input') + select#team-dropdown(name="team" class="form-main-dropdown") + div + h2#games-table-header + span#no-scores-message + table + colgroup + col#score-column(span="1") + col#opponent-column(span="1") + col#date-column(span="1") + tbody#games-table + + +block scripts + script(src='/scripts/index.js' type="module") \ No newline at end of file diff --git a/views/layout.pug b/views/layout.pug index 15af079..04577b9 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -1,7 +1,26 @@ doctype html html head - title= title + title= title + ' - Score Tracker' + meta(name='viewport', content='width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1, user-scalable=no') link(rel='stylesheet', href='/stylesheets/style.css') + block stylesheets body - block content + div#mobile-view + noscript + span#noscript-message Please enable JavaScript to run this app. + div#actions-div + if !hideHomeButton + button#home-button Home + block actions + if userLoggedIn + button#logout-button Log out + else if userLoggedIn !== undefined + button#login-button Log in + h1 #{title} + block content + div#about-footer + a(href="/about") Help/About + + block scripts + script(src='/scripts/main.js' type="module") diff --git a/views/manage.pug b/views/manage.pug new file mode 100644 index 0000000..bd8a228 --- /dev/null +++ b/views/manage.pug @@ -0,0 +1,26 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/manage.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + div + span(class='form-section') + label Category + span(class='form-section-input') + select#category-dropdown(name="category" class="form-main-dropdown") + option(value="seasons") Seasons + option(value="sports") Sports + option(value="divisions") Divisions + option(value="teams") Teams + option(value="games") Games + option(value="accounts") Accounts + div + h2#table-header + span#loading-message Loading... + table#items-list + button#add-new-button Add new... + +block scripts + script(src='/scripts/manage.js' type="module") \ No newline at end of file diff --git a/views/manage/adddivision.pug b/views/manage/adddivision.pug new file mode 100644 index 0000000..3471bc5 --- /dev/null +++ b/views/manage/adddivision.pug @@ -0,0 +1,32 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + span#loading-message Loading... + form#submission-form(action='./division', method='POST') + span(class='form-section') + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="form-main-dropdown" disabled) + span(class='form-section') + label Gender + span(class='form-section-input') + select#gender-dropdown(name="gender" class="form-main-dropdown" disabled) + option(value="both") Both + option(value="female") Female + option(value="male") Male + span(class='form-section') + label Division name + span(class='form-section-input') + input#name-textbox(type="text", name="name" disabled) + .error #{message} + span(class='form-section') + button#submit-button(type="submit" disabled) Submit + span(class='form-section') + button#delete-button(disabled) Delete + +block scripts + script(src='/scripts/manage/division.js' type="module") \ No newline at end of file diff --git a/views/manage/addgame.pug b/views/manage/addgame.pug new file mode 100644 index 0000000..dc56f5d --- /dev/null +++ b/views/manage/addgame.pug @@ -0,0 +1,61 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + span#loading-span Loading... + form#submission-form(action='./game', method='POST') + span(class='form-section') + label Year + span(class='form-section-input') + select#year-dropdown(name="year" class="form-main-dropdown" disabled) + span(class='form-section flat-form-section') + span + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="form-main-dropdown") + span + label Gender + span(class='form-section-input') + select#gender-dropdown(name="gender") + span + label Division + span(class='form-section-input') + select#division-dropdown(name="division") + span(class='form-section') + label Date of match + span(class='form-section-input') + input#date-input(type="date", name="date" value=date disabled) + span(class='form-section flat-form-section') + span + label Your team + span(class='form-section-input') + select#team1-dropdown(name="team1" class="form-main-dropdown" disabled) + span(class='form-score-input') + label Score + span(class='form-section-input') + input#team1-score-textbox(type="number", name="team1-score", value="0" disabled) + span(class='form-section flat-form-section') + span + label Opponent + span(class='form-section-input') + select#team2-dropdown(name="team2" class="form-main-dropdown" disabled) + span(class='form-score-input') + label Score + span(class='form-section-input') + input#team2-score-textbox(type="number", name="team2-score", value="0" disabled) + if !userLoggedIn + span(class='form-section') + label Your name + span(class='form-section-input') + input#name-textbox(type="text" name="name" disabled) + .error #{message} + span(class='form-section') + button#submit-button(type="submit" disabled) Submit + span(class='form-section') + button#delete-button(disabled) Delete + +block scripts + script(src='/scripts/manage/game.js' type="module") \ No newline at end of file diff --git a/views/manage/addseason.pug b/views/manage/addseason.pug new file mode 100644 index 0000000..ba43698 --- /dev/null +++ b/views/manage/addseason.pug @@ -0,0 +1,18 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + form(action='./season', method='POST') + span(class='form-section') + label Ending year + span(class='form-section-input') + input#season-textbox(type="number", name="year", value=currentYear) + .error #{message} + span(class='form-section') + button#submit-button(type="submit" disabled) Submit + +block scripts + script(src='/scripts/manage/season.js' type="module") \ No newline at end of file diff --git a/views/manage/addsport.pug b/views/manage/addsport.pug new file mode 100644 index 0000000..6530488 --- /dev/null +++ b/views/manage/addsport.pug @@ -0,0 +1,22 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + span#loading-message Loading... + form#submission-form(action='./sport', method='POST') + span(class='form-section') + label Sport name + span(class='form-section-input') + input#name-textbox(type="text" name="name" disabled) + .error #{message} + span(class='form-section') + button#submit-button(type="submit" disabled) Submit + span(class='form-section') + button#delete-button(disabled) Delete + + +block scripts + script(src='/scripts/manage/sport.js' type="module") \ No newline at end of file diff --git a/views/manage/addteam.pug b/views/manage/addteam.pug new file mode 100644 index 0000000..17d42d3 --- /dev/null +++ b/views/manage/addteam.pug @@ -0,0 +1,25 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + span#loading-message Loading... + form#submission-form(action='./team', method='POST') + span(class='form-section') + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="form-main-dropdown" disabled) + span(class='form-section') + label Team name + span(class='form-section-input') + input#name-textbox(type="text", name="name" disabled) + .error #{message} + span(class='form-section') + button#submit-button(type="submit" disabled) Submit + span(class='form-section') + button#delete-button(disabled) Delete + +block scripts + script(src='/scripts/manage/team.js' type="module") \ No newline at end of file diff --git a/views/manage/manage-nonadmin.pug b/views/manage/manage-nonadmin.pug new file mode 100644 index 0000000..890c739 --- /dev/null +++ b/views/manage/manage-nonadmin.pug @@ -0,0 +1,17 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/manage.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block actions + button#manage-account-button(class="send-to-right") Manage account + +block content + div + table#games-list + div + button#add-new-button Add new... + +block scripts + script(src='/scripts/manage/manage-nonadmin.js' type="module") \ No newline at end of file