diff --git a/.env.example b/.env.example index 9ef5217..11d1e69 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,11 @@ NODE_ENV=development +PGUSER=dbuser +PGHOST=database.server.com +PGPASSWORD=dbuserpassword +PGDATABASE=mydatabase +PGPORT=5432 + MAIL_FROM=fromaddress@example.com MAIL_HOST=smtp.smtphost.net MAIL_PORT=465 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/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/app.js b/app.js index 3cff1ca..4065b14 100644 --- a/app.js +++ b/app.js @@ -3,13 +3,40 @@ 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 submitRouter = require('./routes/submit') +var dataRouter = require('./routes/data'); +var manageRouter = require('./routes/manage'); +var authRouter = require('./routes/auth'); 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'); @@ -21,8 +48,10 @@ app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); -app.use('/users', usersRouter); -app.use('/submit', submitRouter); +app.use('/data', dataRouter); +app.use('/manage', manageRouter); +app.use('/auth', authRouter); + // 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..d33d11f --- /dev/null +++ b/database/accounts/accounts.js @@ -0,0 +1,141 @@ +const database = require('./../database'); +const passport = require('passport'); +const localStrategy = require('passport-local').Strategy; +const bcrypt = require('bcrypt'); + +class User { + constructor(id, email, isAdmin) { + this.id = id; + this.email = email; + this.isAdmin = isAdmin; + } +} + + +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) { + const hash = await generateHash(password); + + const query = `INSERT INTO accounts.users(email, password, admin) + VALUES($1, $2, $3)`; + await database.executeQuery(query, [email, hash, isAdmin]); +} + +async function edit(id, email, password, isAdmin) { + if(password) { + const hash = await generateHash(password); + + const query = `UPDATE accounts.users + SET email = $2, + password = $3, + admin = $4 + WHERE user_id = $1;`; + await database.executeQuery(query, [id, email, hash, isAdmin]); + } else { + const query = `UPDATE accounts.users + SET email = $2, + admin = $3 + WHERE user_id = $1;`; + await database.executeQuery(query, [id, email, isAdmin]); + } + return new User(id, email, isAdmin); +} + +async function remove(id) { + const query = `DELETE FROM accounts.users + WHERE user_id = $1 + RETURNING email, admin;`; + const row = (await database.executeQuery(query, [id]))[0]; + return new User(id, row[0], row[1]); +} + +async function retrieveAll() { + const query = `SELECT user_id, email, admin + FROM accounts.users + ORDER BY email;` + const table = await database.executeQuery(query); + + const accountsList = []; + table.forEach((row) => { + accountsList.push(new User(row[0], row[1], row[2])); + }); + return accountsList; +} + +async function getFromID(id) { + const query = `SELECT user_id, email, admin + FROM accounts.users + WHERE user_id = $1;`; + const row = (await database.executeQuery(query, [id]))[0]; + + return new User(id, row[1], row[2]); +} + +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..7390426 --- /dev/null +++ b/database/database.js @@ -0,0 +1,46 @@ +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 scoresSchemaExistsQuery = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`; + let result = await executeQuery(scoresSchemaExistsQuery); + + const scoresSchemaExists = result.length !== 0; + + if(!scoresSchemaExists) { + await Initialize(); + } +} +const initializationStatus = checkForDatabaseInitialization(); + + + + + +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..404addf --- /dev/null +++ b/database/init_database.sql @@ -0,0 +1,113 @@ +/* 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 + + + +accounts: + + users: + *user_id* | email | password | admin + +*/ + + +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, + 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_id BIGINT NOT NULL, + 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) + ); + +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..8792b2e --- /dev/null +++ b/database/scores/games.js @@ -0,0 +1,120 @@ +const database = require('./../database'); + + + + + +class Game { + constructor(id, date, team1ID, team2ID, team1Score, team2Score, divisionID, seasonID, submitterID) { + 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; + } +} + + + +async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID) { + 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;`; + + const id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID]))[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 + 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 + 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])); + } + 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])); + } + }); + 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]); +} + + + + + +exports.add = add; +exports.remove = remove; +exports.retrieve = retrieve; +exports.retrieveByUser = retrieveByUser; +exports.edit = edit; +exports.getFromID = getFromID; \ 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/package-lock.json b/package-lock.json index 006fc12..1903fa7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,20 @@ "name": "demo", "version": "0.0.0", "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", "nodemailer": "^6.6.5", + "passport": "^0.5.0", + "passport-local": "^1.0.0", + "pg": "^8.7.1", "pug": "2.0.0-beta11" }, "devDependencies": { @@ -22,6 +29,25 @@ "supertest": "^3.0.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/@types/babel-types": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.4.tgz", @@ -35,6 +61,11 @@ "@types/babel-types": "*" } }, + "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.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -77,6 +108,38 @@ "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 + } + } + }, + "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/align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", @@ -98,6 +161,44 @@ "node": ">=0.4.2" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/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/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -108,6 +209,11 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "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/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -145,8 +251,7 @@ "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "node_modules/basic-auth": { "version": "2.0.0", @@ -159,6 +264,19 @@ "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.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", @@ -183,7 +301,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -195,6 +312,14 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "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", @@ -231,6 +356,14 @@ "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/clean-css": { "version": "3.4.28", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", @@ -256,6 +389,14 @@ "wordwrap": "0.0.2" } }, + "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/combined-stream": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", @@ -288,8 +429,20 @@ "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=", - "dev": true + "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": "3.1.2", @@ -386,6 +539,11 @@ "node": ">=0.4.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", @@ -399,6 +557,17 @@ "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/diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -426,6 +595,11 @@ "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", @@ -504,6 +678,59 @@ "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/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/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/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -564,17 +791,46 @@ "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=", - "dev": true + "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/glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -626,6 +882,11 @@ "node": ">=4" } }, + "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/he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -649,6 +910,39 @@ "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.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", @@ -661,7 +955,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -705,6 +998,14 @@ "node": ">=0.4.0" } }, + "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.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -773,6 +1074,39 @@ "node": ">=0.10.0" } }, + "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", @@ -825,7 +1159,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -839,6 +1172,29 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, + "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": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -921,6 +1277,22 @@ "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.6.5", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.5.tgz", @@ -929,6 +1301,34 @@ "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", @@ -949,9 +1349,9 @@ } }, "node_modules/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==", "engines": { "node": ">= 0.8" } @@ -960,24 +1360,62 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "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.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==", "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=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -992,6 +1430,120 @@ "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/process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -1140,6 +1692,14 @@ "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.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -1236,11 +1796,58 @@ "node": ">=0.10.0" } }, + "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/rimraf/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/safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "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", @@ -1278,11 +1885,21 @@ "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/source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", @@ -1294,6 +1911,27 @@ "node": ">=0.8.0" } }, + "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/split2/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/statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -1306,11 +1944,34 @@ "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, "dependencies": { "safe-buffer": "~5.1.0" } }, + "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/superagent": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", @@ -1366,6 +2027,33 @@ "node": ">=4" } }, + "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/tar/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/to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -1379,6 +2067,11 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" }, + "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.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", @@ -1423,6 +2116,17 @@ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", "optional": true }, + "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", @@ -1434,8 +2138,7 @@ "node_modules/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=" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -1461,6 +2164,28 @@ "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/window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -1489,8 +2214,20 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "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==" }, "node_modules/yargs": { "version": "3.10.0", @@ -1505,6 +2242,22 @@ } }, "dependencies": { + "@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" + } + }, "@types/babel-types": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.4.tgz", @@ -1518,6 +2271,11 @@ "@types/babel-types": "*" } }, + "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.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -1547,6 +2305,29 @@ } } }, + "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==" + } + } + }, "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", @@ -1562,6 +2343,37 @@ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, + "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" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1572,6 +2384,11 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "async": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1606,8 +2423,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "basic-auth": { "version": "2.0.0", @@ -1617,6 +2433,15 @@ "safe-buffer": "5.1.1" } }, + "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", @@ -1638,7 +2463,6 @@ "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", "concat-map": "0.0.1" @@ -1650,6 +2474,11 @@ "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", @@ -1677,6 +2506,11 @@ "is-regex": "^1.0.3" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, "clean-css": { "version": "3.4.28", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", @@ -1696,6 +2530,11 @@ "wordwrap": "0.0.2" } }, + "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==" + }, "combined-stream": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", @@ -1722,8 +2561,17 @@ "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", @@ -1801,6 +2649,11 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -1811,6 +2664,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -1832,6 +2690,11 @@ "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", @@ -1895,6 +2758,38 @@ "vary": "~1.1.2" } }, + "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": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "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==" + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1942,17 +2837,40 @@ "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==" }, + "gauge": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", + "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", + "requires": { + "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" + } + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -1992,6 +2910,11 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -2009,6 +2932,30 @@ "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", @@ -2018,7 +2965,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -2055,6 +3001,11 @@ } } }, + "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", @@ -2111,6 +3062,29 @@ "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2148,7 +3122,6 @@ "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.7" } @@ -2159,6 +3132,23 @@ "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", @@ -2226,11 +3216,43 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, + "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.6.5", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.5.tgz", "integrity": "sha512-C/v856DBijUzHcHIgGpQoTrfsH3suKIRAGliIzCstatM2cAa+MYX3LuyCrABiO/cdJTxgBBHXxV1ztiqUwst5A==" }, + "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", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2245,29 +3267,54 @@ } }, "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" } }, + "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", @@ -2279,6 +3326,89 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "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/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" + } + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -2421,6 +3551,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" }, + "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", @@ -2501,11 +3636,42 @@ "align-text": "^0.1.1" } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "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" + } + } + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "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", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", @@ -2537,11 +3703,21 @@ "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==" }, + "signal-exit": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" + }, "source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", @@ -2550,6 +3726,26 @@ "amdefine": ">=0.0.4" } }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "requires": { + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -2559,11 +3755,28 @@ "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, "requires": { "safe-buffer": "~5.1.0" } }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "superagent": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", @@ -2612,6 +3825,26 @@ "has-flag": "^3.0.0" } }, + "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" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -2622,6 +3855,11 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" }, + "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", @@ -2654,6 +3892,14 @@ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", "optional": true }, + "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": { + "random-bytes": "~1.0.0" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2662,8 +3908,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", @@ -2680,6 +3925,28 @@ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -2702,8 +3969,17 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "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==" }, "yargs": { "version": "3.10.0", diff --git a/package.json b/package.json index 0a035a8..7b97c2d 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,25 @@ { - "name": "demo", - "version": "0.0.0", + "name": "score-tracker", + "version": "1.0.0-pre", "private": true, "scripts": { "start": "node ./bin/www", - "test": "mocha" }, "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", "nodemailer": "^6.6.5", + "passport": "^0.5.0", + "passport-local": "^1.0.0", + "pg": "^8.7.1", "pug": "2.0.0-beta11" }, "devDependencies": { diff --git a/public/scripts/data.js b/public/scripts/data.js new file mode 100644 index 0000000..e8c5898 --- /dev/null +++ b/public/scripts/data.js @@ -0,0 +1,91 @@ +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 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..5132ea9 --- /dev/null +++ b/public/scripts/form.js @@ -0,0 +1,126 @@ +import * as Data from "./data.js"; + +export async function populateSports(sportDropdown, selectedSportID = undefined) { + sportDropdown.innerHTML = ""; + + const 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) selectedSportIndex = currentIndex; + currentIndex++; + }); + + if(selectedSportIndex) sportDropdown.selectedIndex = selectedSportIndex; +} + +export async function populateSeasons(seasonDropdown, selectedSeasonID = undefined) { + seasonDropdown.innerHTML = ""; + + const 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) selectedSeasonIndex = currentIndex; + currentIndex++; + }); + + if(selectedSeasonIndex) seasonDropdown.selectedIndex = selectedSeasonIndex; +} + +export async function populateGenders(genderDropdown, selectedSportID, selectedGender = undefined) { + genderDropdown.innerHTML = ""; + + const 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) selectedGenderIndex = currentIndex; + currentIndex++; + }); + + if(selectedGenderIndex) genderDropdown.selectedIndex = selectedGenderIndex; + } +} + +export async function populateDivisions (divisionDropdown, selectedSportID, selectedGender, selectedDivisionID = undefined) { + divisionDropdown.innerHTML = ""; + + if(selectedSportID && selectedGender) { + const 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) selectedDivisionIndex = currentIndex; + currentIndex++; + }); + + if(selectedDivisionIndex) divisionDropdown.selectedIndex = selectedDivisionIndex; + } +} + +export async function populateTeams(teamDropdown, selectedSportID, selectedTeamID) { + teamDropdown.innerHTML = ""; + + if(selectedSportID) { + const teamsList = await Data.getTeams(selectedSportID); + + let currentIndex = 0; + let selectedTeamIndex; + 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..0beced6 --- /dev/null +++ b/public/scripts/index.js @@ -0,0 +1,190 @@ +import * as Data from "./data.js"; + +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'); + + + + + +async function listSeasons() { + seasonDropdown.innerHTML = ""; + + const seasonsList = await Data.getSeasons(); + + seasonsList.forEach(season => { + const option = document.createElement('option'); + option.text = (season.year - 1) + "-" + season.year; + option.value = season.id; + seasonDropdown.appendChild(option); + }); +} +listSeasons(); + +async function listSports() { + sportDropdown.innerHTML = ""; + + const sportsList = await Data.getSports(); + + sportsList.forEach(sport => { + const option = document.createElement('option'); + option.text = sport.name; + option.value = sport.id; + sportDropdown.appendChild(option); + }); + + listGenders(); +} +listSports(); + +async function listGenders() { + genderDropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + const gendersList = await Data.getGenders(selectedSportID); + + if(selectedSportID) { + 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); + }); + } + + listDivisions(); +} + +async function listDivisions() { + divisionDropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + const selectedGender = genderDropdown.value; + + if(selectedGender) { + const divisionsList = await Data.getDivisions(selectedSportID, selectedGender); + + divisionsList.forEach(division => { + const option = document.createElement('option'); + option.text = division.name; + option.value = division.id; + divisionDropdown.appendChild(option); + }); + } + listTeams(); +} + +async function listTeams() { + teamDropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + + if(selectedSportID) { + const teamsList = await Data.getTeams(selectedSportID); + + teamsList.forEach(team => { + const option = document.createElement('option'); + option.text = team.name; + option.value = team.id; + teamDropdown.appendChild(option); + }); + } + + listGames(); +} + +async function listGames() { + 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}`; + + const gamesList = await Data.getGames(selectedTeamID, selectedDivisionID, selectedSeasonID); + 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'); + Data.getTeam(game.team2ID) + .then(data => opponentCell.textContent = data.name); + row.appendChild(opponentCell); + + const dateCell = document.createElement('td'); + dateCell.textContent = game.date; + 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); +} + + + + + +sportDropdown.onchange = (() => { + listGenders(); + listTeams(); +}); +genderDropdown.onchange = listDivisions; +teamDropdown.onchange = listGames; +seasonDropdown.onchange = listGames; + +if(addScoreButton) { + addScoreButton.addEventListener('click', () => { + window.location.href = '/manage/game'; + }); +} + +if(manageButton) { + manageButton.addEventListener('click', () => { + window.location.href = '/manage' + }); +} 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..de4a6e2 --- /dev/null +++ b/public/scripts/manage.js @@ -0,0 +1,377 @@ +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'); + + +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 Data.getDivisions(); + }, + 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'); + Data.getSportName(division.sportID) + .then(data => sportCell.textContent = data); + 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 Data.getTeams(); + }, + 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'); + Data.getSportName(team.sportID) + .then(data => sportCell.textContent = data); + 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 Data.getGames(); + }, + 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); + + itemsListTable.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}`; + } +)); + +CATEGORIES.push(new Category( + "accounts", + async function getAccounts() { + return await Data.getAccounts(); + }, + async function listAccountHeaders() { + const headerRow = document.createElement('tr'); + + 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 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) { + 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 = "E"; + editButton.addEventListener('click', () => { + CATEGORIES[categoryDropdown.selectedIndex].editItem(item.id); + }); + editSpan.appendChild(editButton); + manageCell.appendChild(editSpan); + + row.appendChild(manageCell); + + itemsListTable.appendChild(row); + }); +} +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..40b252f --- /dev/null +++ b/public/scripts/manage/account.js @@ -0,0 +1,58 @@ +import * as Data from "../data.js"; +import * as Form from "../form.js"; + +const submissionForm = document.getElementById('submission-form'); +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'); + +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); + console.log(account); + + 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; + } + emailTextbox.disabled = false; + emailTextbox.addEventListener('keyup', checkDataValidity); + passwordTextbox.disabled = false; + passwordTextbox.addEventListener('keyup', checkDataValidity); + checkDataValidity(); +} +Initialize(); + +async function checkDataValidity() { + let dataIsValid = true; + + if(!passwordTextbox.value && !passwordTextbox.placeholder) 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..a542a65 --- /dev/null +++ b/public/scripts/manage/division.js @@ -0,0 +1,62 @@ +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'); + + +async function initializeForm() { + let params = new URLSearchParams(location.search); + let divisionID = params.get('division'); + if(divisionID) { + const division = await Data.getDivision(divisionID); + + 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; + + Form.populateSports(sportDropdown, division.sportID); + + Form.addHiddenValue('division', divisionID, submissionForm); + } + else { + Form.populateSports(sportDropdown); + + genderDropdown.disabled = false; + + sportDropdown.disabled = false; + } + + nameTextbox.disabled = false; + nameTextbox.addEventListener('keyup', checkDataValidity); +} +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..ce80b97 --- /dev/null +++ b/public/scripts/manage/game.js @@ -0,0 +1,106 @@ +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 submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); + + +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 { + 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; + + 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); + + 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; + + 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..20ad2cb --- /dev/null +++ b/public/scripts/manage/sport.js @@ -0,0 +1,55 @@ +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'); + + +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); +} +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..4bc643e --- /dev/null +++ b/public/scripts/manage/team.js @@ -0,0 +1,48 @@ +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'); + +async function initializeForm() { + let params = new URLSearchParams(location.search); + let teamID = params.get('team'); + if(teamID) { + const team = await Data.getTeam(teamID); + + nameTextbox.value = team.name; + + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + + Form.populateSports(sportDropdown, team.sportID); + + Form.addHiddenValue('team', teamID, submissionForm); + } + else { + sportDropdown.disabled = false; + + Form.populateSports(sportDropdown); + } + + nameTextbox.disabled = false; + nameTextbox.addEventListener('keyup', checkDataValidity); +} +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..43fee2e --- /dev/null +++ b/public/stylesheets/form.css @@ -0,0 +1,51 @@ +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; + } \ No newline at end of file diff --git a/public/stylesheets/index.css b/public/stylesheets/index.css new file mode 100644 index 0000000..d08e1e8 --- /dev/null +++ b/public/stylesheets/index.css @@ -0,0 +1,34 @@ +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; +} \ 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 6a727df..c4252bd 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -7,3 +7,51 @@ 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; +} \ No newline at end of file diff --git a/public/stylesheets/submit.css b/public/stylesheets/submit.css index 89dd41c..30e64a5 100644 --- a/public/stylesheets/submit.css +++ b/public/stylesheets/submit.css @@ -2,40 +2,6 @@ h1 { text-align: center; } -form { - display: flex; - flex-direction: column; - max-width: 20em; - margin-left: auto; - margin-right: auto; -} - -span { - display: flex; - flex-direction: column; -} - - -#sport-dropdown { - width: 100%; -} - -.form-section { - margin-bottom: 0.75em; -} - -.form-section-input { - flex-direction: row; -} - -input { - width: 100%; -} - -.score-input{ - width: 35%; -} - -button { - margin-top: 1.5em; +#admin-checkbox-section { + visibility: hidden; } \ No newline at end of file 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..dfe6b8d --- /dev/null +++ b/routes/data.js @@ -0,0 +1,161 @@ +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 data = await games.getFromID(gameID); + 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/index.js b/routes/index.js index 9399195..42fa2b0 100644 --- a/routes/index.js +++ b/routes/index.js @@ -3,7 +3,7 @@ var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { - res.render('index', { title: 'Submit Score' }); + res.render('index', { title: 'View Scores', userLoggedIn: !!req.user, hideHomeButton: true }); }); module.exports = router; diff --git a/routes/manage.js b/routes/manage.js new file mode 100644 index 0000000..8432ff8 --- /dev/null +++ b/routes/manage.js @@ -0,0 +1,224 @@ +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'); + + +router.get('/' ,checkLoginStatus.user, function(req, res, next) { + if(req.user[2]) res.render('manage', { title: 'Score Management', userLoggedIn: !!req.user }); + else res.render('manage/manage-nonadmin', { title: "My Games", userLoggedIn: !!req.user }); +}); + +router.get('/game', checkLoginStatus.user, 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', checkLoginStatus.user, 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 userID = req.user[0]; + + const loggedInUserID = req.user[0]; + const loggedInUserIsAdmin = req.user[2]; + + const game = id ? await games.getFromID(id) : null; + + if(!loggedInUserIsAdmin && game && loggedInUserID != game.submitterID) { + res.status(403).send("ACCESS DENIED"); + } + else if(remove) { + await games.remove(id); + res.redirect("/manage"); + } + else if(id) { + await games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score); + res.redirect('/manage'); + } + else { + await games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID); + 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'); + } 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'); + } 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'); + } 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')); + else if(id) teams.rename(id, name).then(res.redirect('/manage')); + else teams.add(name, sport).then(res.redirect("/manage")); + } 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 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); + else await accounts.create(req.body.email, req.body.password, !!req.body.admin); + + res.redirect('/manage'); + } + 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/routes/submit.js b/routes/submit.js deleted file mode 100644 index 30901be..0000000 --- a/routes/submit.js +++ /dev/null @@ -1,38 +0,0 @@ -var express = require('express'); -var mail = require('../mail/mail'); -var router = express.Router(); - -/* GET submit page. */ -router.get('/', function(req, res, next) { - res.send('Nothing to send'); -}); - -/* POST submit page. */ -router.post('/', function(req, res, next) { - let sport = req.body.sport; - let gender = req.body.gender; - let division = req.body.division; - let home = req.body['home-team']; - let homeScore = req.body['home-team-score']; - let visiting = req.body['visiting-team']; - let visitingScore = req.body['visiting-team-score']; - let submitter = req.body['submitter']; - let recipient = req.body['email']; - - let message = prepMailBody(sport, gender, division, home, homeScore, visiting, visitingScore, submitter); - - mail.send(recipient, "Score Report", message); - - res.send('Score sent'); -}); - -var prepMailBody = function(sport, gender, division, home, homeScore, visiting, visitingScore, submitter) { - return( - "Score report from " + submitter + "

" + - "Sport:
" + sport + " - " + gender + " - " + division + "

" + - "Home team:
" + home + " - " + homeScore + "

" + - "Visiting team:
" + visiting + " - " + visitingScore + "

"); -}; - - -module.exports = router; diff --git a/routes/users.js b/routes/users.js deleted file mode 100644 index 623e430..0000000 --- a/routes/users.js +++ /dev/null @@ -1,9 +0,0 @@ -var express = require('express'); -var router = express.Router(); - -/* GET users listing. */ -router.get('/', function(req, res, next) { - res.send('respond with a resource'); -}); - -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/accounts/createuser.pug b/views/accounts/createuser.pug new file mode 100644 index 0000000..dfc1b13 --- /dev/null +++ b/views/accounts/createuser.pug @@ -0,0 +1,30 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + form#submission-form(action='/manage/account', method='POST') + if accountID + input#account-id(type="hidden" name="account" value=accountID) + 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 7f6baf1..a1b7d2d 100644 --- a/views/index.pug +++ b/views/index.pug @@ -1,40 +1,48 @@ extends layout block stylesheets - link(rel='stylesheet', href='/stylesheets/submit.css') + 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 Submit Score - form(action='/submit', method='POST') - span(class='form-section') - label Sport - span(class='form-section-input') - select#sport-dropdown(name="sport") - option(value="Football" selected) Football - select#gender-dropdown(name="gender") - option(value="Male" selected) Male - option(value="Female") Female - select#division-dropdown(name="division") - option(value="Varsity") Varsity - option(value="JV-A") JV-A - option(value="JV-B") JV-B - span(class='form-section') - label Home Team - span(class='form-section-input') - input(type="text", name="home-team") - input(class="score-input", type="number", name="home-team-score", value="0") - span(class='form-section') - label Visiting Team - span(class='form-section-input') - input(type="text", name="visiting-team") - input(class="score-input", type="number", name="visiting-team-score", value="0") - span(class='form-section') - label Submitter - span(class='form-section-input') - input(type="text", name="submitter") - span(class='form-section') - label Send info to - span(class='form-section-input') - input(type="email", name="email", placeholder="email@example.com") - span(class='form-section') - button(type="submit") Submit + 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 69f5a59..80d0238 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -1,9 +1,23 @@ doctype html html head - title= title + title= title + ' - Score Tracker' meta(name='viewport', content='width=device-width, initial-scale=1') 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 + 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..63d76ec --- /dev/null +++ b/views/manage.pug @@ -0,0 +1,25 @@ +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 + 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..1a17aaf --- /dev/null +++ b/views/manage/adddivision.pug @@ -0,0 +1,31 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + 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..3a44834 --- /dev/null +++ b/views/manage/addgame.pug @@ -0,0 +1,55 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + 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) + .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..b4caa50 --- /dev/null +++ b/views/manage/addsport.pug @@ -0,0 +1,21 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + 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..78746a6 --- /dev/null +++ b/views/manage/addteam.pug @@ -0,0 +1,24 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + 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 diff --git a/views/submit.pug b/views/submit.pug deleted file mode 100644 index e69de29..0000000