Merge branch 'develop' into 'testing'

Add changes to testing branch

See merge request sudoer777/cvcs-score-tracker!9
main
Ethan Reece 2021-11-27 01:38:25 +00:00
commit 8a194d4cdd
55 changed files with 4317 additions and 186 deletions

View File

@ -1,5 +1,11 @@
NODE_ENV=development NODE_ENV=development
PGUSER=dbuser
PGHOST=database.server.com
PGPASSWORD=dbuserpassword
PGDATABASE=mydatabase
PGPORT=5432
MAIL_FROM=fromaddress@example.com MAIL_FROM=fromaddress@example.com
MAIL_HOST=smtp.smtphost.net MAIL_HOST=smtp.smtphost.net
MAIL_PORT=465 MAIL_PORT=465

View File

@ -1 +1 @@
8.11.1 16.13.0

View File

@ -1,4 +1,4 @@
FROM node:8.11-alpine FROM node:16.13.0-alpine3.12
WORKDIR /usr/src/app WORKDIR /usr/src/app

37
app.js
View File

@ -3,13 +3,40 @@ var express = require('express');
var path = require('path'); var path = require('path');
var cookieParser = require('cookie-parser'); var cookieParser = require('cookie-parser');
var logger = require('morgan'); 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 indexRouter = require('./routes/index');
var usersRouter = require('./routes/users'); var dataRouter = require('./routes/data');
var submitRouter = require('./routes/submit') var manageRouter = require('./routes/manage');
var authRouter = require('./routes/auth');
var app = express(); 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 // view engine setup
app.set('views', path.join(__dirname, 'views')); app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug'); app.set('view engine', 'pug');
@ -21,8 +48,10 @@ app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter); app.use('/', indexRouter);
app.use('/users', usersRouter); app.use('/data', dataRouter);
app.use('/submit', submitRouter); app.use('/manage', manageRouter);
app.use('/auth', authRouter);
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use(function(req, res, next) { app.use(function(req, res, next) {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

1366
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,25 @@
{ {
"name": "demo", "name": "score-tracker",
"version": "0.0.0", "version": "1.0.0-pre",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node ./bin/www", "start": "node ./bin/www",
"test": "mocha"
}, },
"dependencies": { "dependencies": {
"async": "^3.2.2",
"bcrypt": "^5.0.1",
"connect-flash": "^0.1.1",
"cookie-parser": "~1.4.3", "cookie-parser": "~1.4.3",
"debug": "~2.6.9", "debug": "~2.6.9",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"express": "~4.16.0", "express": "~4.16.0",
"express-session": "^1.17.2",
"http-errors": "~1.6.2", "http-errors": "~1.6.2",
"morgan": "~1.9.0", "morgan": "~1.9.0",
"nodemailer": "^6.6.5", "nodemailer": "^6.6.5",
"passport": "^0.5.0",
"passport-local": "^1.0.0",
"pg": "^8.7.1",
"pug": "2.0.0-beta11" "pug": "2.0.0-beta11"
}, },
"devDependencies": { "devDependencies": {

View File

@ -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;
}

View File

@ -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();
}
});
}

View File

@ -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'
});
}

View File

@ -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 = '/';
});
}

View File

@ -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());

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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';
});

View File

@ -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);

View File

@ -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();
});

View File

@ -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");

View File

@ -0,0 +1,3 @@
#mobile-view {
max-width: 100%;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,11 @@
th {
text-align: left;
}
td {
white-space: nowrap;
}
.spacer-column {
width: 100%;
}

View File

@ -7,3 +7,51 @@ a {
color: #00B7FF; 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;
}

View File

@ -2,40 +2,6 @@ h1 {
text-align: center; text-align: center;
} }
form { #admin-checkbox-section {
display: flex; visibility: hidden;
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;
} }

27
routes/auth.js 100644
View File

@ -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;

View File

@ -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;

161
routes/data.js 100644
View File

@ -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;

View File

@ -3,7 +3,7 @@ var router = express.Router();
/* GET home page. */ /* GET home page. */
router.get('/', function(req, res, next) { 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; module.exports = router;

224
routes/manage.js 100644
View File

@ -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;

View File

@ -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 <b>" + submitter + "</b><br><br>" +
"<b>Sport:</b><br>" + sport + " - " + gender + " - " + division + "<br><br>" +
"<b>Home team:</b><br>" + home + " - " + homeScore + "<br><br>" +
"<b>Visiting team:</b><br>" + visiting + " - " + visitingScore + "<br><br>");
};
module.exports = router;

View File

@ -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;

View File

@ -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);
});
});

View File

@ -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")

View File

@ -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

View File

@ -1,5 +1,8 @@
extends layout extends layout
block stylesheets
link(rel='stylesheet', href='/stylesheets/error.css')
block content block content
h1= message h1= message
h2= error.status h2= error.status

View File

@ -1,40 +1,48 @@
extends layout extends layout
block stylesheets 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 block content
h1 Submit Score div
form(action='/submit', method='POST') span(class='form-section')
span(class='form-section') label Year
label Sport span(class='form-section-input')
span(class='form-section-input') select#year-dropdown(name="year" class="form-main-dropdown")
select#sport-dropdown(name="sport") span(class='form-section flat-form-section')
option(value="Football" selected) Football span
select#gender-dropdown(name="gender") label Sport
option(value="Male" selected) Male span(class='form-section-input')
option(value="Female") Female select#sport-dropdown(name="sport" class="form-main-dropdown")
select#division-dropdown(name="division") span
option(value="Varsity") Varsity label Gender
option(value="JV-A") JV-A span(class='form-section-input')
option(value="JV-B") JV-B select#gender-dropdown(name="gender")
span(class='form-section') span
label Home Team label Division
span(class='form-section-input') span(class='form-section-input')
input(type="text", name="home-team") select#division-dropdown(name="division")
input(class="score-input", type="number", name="home-team-score", value="0") span(class='form-section')
span(class='form-section') label Team
label Visiting Team span(class='form-section-input')
span(class='form-section-input') select#team-dropdown(name="team" class="form-main-dropdown")
input(type="text", name="visiting-team") div
input(class="score-input", type="number", name="visiting-team-score", value="0") h2#games-table-header
span(class='form-section') span#no-scores-message
label Submitter table
span(class='form-section-input') colgroup
input(type="text", name="submitter") col#score-column(span="1")
span(class='form-section') col#opponent-column(span="1")
label Send info to col#date-column(span="1")
span(class='form-section-input') tbody#games-table
input(type="email", name="email", placeholder="email@example.com")
span(class='form-section')
button(type="submit") Submit block scripts
script(src='/scripts/index.js' type="module")

View File

@ -1,9 +1,23 @@
doctype html doctype html
html html
head head
title= title title= title + ' - Score Tracker'
meta(name='viewport', content='width=device-width, initial-scale=1') meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='/stylesheets/style.css') link(rel='stylesheet', href='/stylesheets/style.css')
block stylesheets block stylesheets
body 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")

25
views/manage.pug 100644
View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File