commit
bb9c583ed2
|
@ -0,0 +1,16 @@
|
||||||
|
NODE_ENV=development
|
||||||
|
|
||||||
|
PGUSER=dbuser
|
||||||
|
PGHOST=database.server.com
|
||||||
|
PGPASSWORD=dbuserpassword
|
||||||
|
PGDATABASE=mydatabase
|
||||||
|
PGPORT=5432
|
||||||
|
|
||||||
|
PUBLIC_SUBMIT_PAGE=false
|
||||||
|
|
||||||
|
#MAIL_FROM=fromaddress@example.com
|
||||||
|
#MAIL_HOST=smtp.smtphost.net
|
||||||
|
#MAIL_PORT=465
|
||||||
|
#MAIL_SECURE=true
|
||||||
|
#MAIL_USER=username
|
||||||
|
#MAIL_PASS=password
|
|
@ -1 +1 @@
|
||||||
8.11.1
|
16.13.0
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "pwa-node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Program",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"program": "${workspaceFolder}/bin/www"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:8.11-alpine
|
FROM node:16.13.0-alpine3.12
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
|
54
README.md
54
README.md
|
@ -1,17 +1,53 @@
|
||||||
### Node Express template project
|
# Score Tracker
|
||||||
|
|
||||||
This project is based on a GitLab [Project Template](https://docs.gitlab.com/ee/gitlab-basics/create-project.html).
|
Main repository: https://gitlab.sudoer.ch/sudoer777/score-tracker/
|
||||||
|
|
||||||
Improvements can be proposed in the [original project](https://gitlab.com/gitlab-org/project-templates/express).
|
A web app designed to collect and display scores for sports
|
||||||
|
|
||||||
### CI/CD with Auto DevOps
|
## Branches
|
||||||
|
|
||||||
This template is compatible with [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/).
|
- [main](https://gitlab.sudoer.ch/sudoer777/score-tracker/-/tree/main) - Stable, production-ready code
|
||||||
|
- [testing](https://gitlab.sudoer.ch/sudoer777/score-tracker/-/tree/testing) - Nearly complete code being tested
|
||||||
|
- [develop](https://gitlab.sudoer.ch/sudoer777/score-tracker/-/tree/develop) - Unstable code under development
|
||||||
|
|
||||||
If Auto DevOps is not already enabled for this project, you can [turn it on](https://docs.gitlab.com/ee/topics/autodevops/#enabling-auto-devops) in the project settings.
|
## Installation
|
||||||
|
|
||||||
### Developing with Gitpod
|
This repository can be cloned and then pushed to Heroku/Dokku/etc.
|
||||||
|
|
||||||
This template has a fully-automated dev setup for [Gitpod](https://docs.gitlab.com/ee/integration/gitpod.html).
|
### Requirements
|
||||||
|
|
||||||
If you open this project in Gitpod, you'll get all Node dependencies pre-installed and Express will open a web preview.
|
- PostgreSQL (with an empty database created and an account to access it)
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
- `NODE_ENV` - set to `production`, `testing`, or `development`
|
||||||
|
- `PGHOST` - set to your database URL
|
||||||
|
- `PGPORT` - set to the database port
|
||||||
|
- `PGDATABASE` - set to the name of your database (i.e. `scoretrackerdb`)
|
||||||
|
- `PGUSER` - set to the user for managing the database
|
||||||
|
- `PGPASSWORD` - set to the password for that user
|
||||||
|
- `PUBLIC_SUBMIT_PAGE` (default: `false`) - set to `true` to allow score submissions without an account
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
This program uses Node.js/Express.js for the backend, PostgreSQL for the database (with node-postgres), and Passport.js for managing users and sessions.
|
||||||
|
|
||||||
|
To view the code, clone the repository and open it in VSCode/VSCodium.
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
- `database` folder contains backend scripts for managing and storing data.
|
||||||
|
- `mail` folder (currenly unused) contains backend scripts for sending emails.
|
||||||
|
- `public` folder contains publically accessible scripts and stylesheets for frontend.
|
||||||
|
- `scripts` folder contains scripts used by specific webpages.
|
||||||
|
- `stylesheets` folder contains CSS for various webpages.
|
||||||
|
- `routes` folder contains various routes used by the program.
|
||||||
|
- `about.js` directs to the about page (`/about`).
|
||||||
|
- `auth.js` deals with logging in and out (`/auth/*`).
|
||||||
|
- `checkLoginStatus.js` contains functions for checking the login status of the current user.
|
||||||
|
- `data.js` sends various data to the client in JSON format (`/data/*`).
|
||||||
|
- `fetch.js` sends more specific data formatted for specific pages in JSON format (`/fetch/*`)
|
||||||
|
- `index.js` directs to the home page (`/`).
|
||||||
|
- `manage.js` contains various functions that allows the user to add and edit items through the web browser (`/manage/*`).
|
||||||
|
- `views` folder contains pug templates for each webpage, and a `layout` template for the base layout of each page.
|
||||||
|
- `.env.example` is a template for the environment variables in a development workspace. Rename to `.env` and change values as needed.
|
||||||
|
|
39
app.js
39
app.js
|
@ -3,12 +3,42 @@ 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 manageRouter = require('./routes/manage');
|
||||||
|
var authRouter = require('./routes/auth');
|
||||||
|
var aboutRouter = require('./routes/about');
|
||||||
|
var fetchRouter = require('./routes/fetch');
|
||||||
|
|
||||||
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');
|
||||||
|
@ -20,7 +50,12 @@ 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('/manage', manageRouter);
|
||||||
|
app.use('/auth', authRouter);
|
||||||
|
app.use('/about', aboutRouter);
|
||||||
|
app.use('/fetch', fetchRouter);
|
||||||
|
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
const database = require('./../database');
|
||||||
|
const passport = require('passport');
|
||||||
|
const localStrategy = require('passport-local').Strategy;
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
|
class User {
|
||||||
|
constructor(id, email, isAdmin, name) {
|
||||||
|
this.id = id;
|
||||||
|
this.email = email;
|
||||||
|
this.isAdmin = isAdmin;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function checkForAdminAccount() {
|
||||||
|
|
||||||
|
const adminUsersQuery = `SELECT *
|
||||||
|
FROM accounts.users
|
||||||
|
WHERE admin = true;`;
|
||||||
|
const adminUsers = await database.executeQuery(adminUsersQuery);
|
||||||
|
|
||||||
|
if(adminUsers.length == 0) {
|
||||||
|
const passwordHash = await generateHash('admin');
|
||||||
|
const createTempAdminQuery = `INSERT INTO accounts.users(email, password, admin)
|
||||||
|
VALUES('admin@example.com', $1, true);`;
|
||||||
|
database.executeQuery(createTempAdminQuery, [passwordHash]);
|
||||||
|
console.log("Created temp admin account 'admin@example.com' with password 'admin'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
database.initializationStatus.then(() => checkForAdminAccount());
|
||||||
|
|
||||||
|
|
||||||
|
passport.use(new localStrategy({
|
||||||
|
usernameField: 'email',
|
||||||
|
passwordField: 'password'},
|
||||||
|
(username, password, cb) => {
|
||||||
|
query = `SELECT user_id, email, password, admin
|
||||||
|
FROM accounts.users
|
||||||
|
WHERE email = $1`;
|
||||||
|
database.executeQuery(query, [username])
|
||||||
|
.then(result => {
|
||||||
|
if(result.length > 0) {
|
||||||
|
const first = result[0];
|
||||||
|
const matches = bcrypt.compareSync(password, first[2]);
|
||||||
|
if(matches) {
|
||||||
|
return cb(null, { id: first[0], email: first[1], admin: first[3] })
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return cb(null, false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return cb(null, false)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
passport.serializeUser((user, done) => {
|
||||||
|
done(null, user.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
passport.deserializeUser((id, cb) => {
|
||||||
|
query = `SELECT user_id, email, admin
|
||||||
|
FROM accounts.users
|
||||||
|
WHERE user_id = $1`;
|
||||||
|
database.executeQuery(query, [parseInt(id, 10)])
|
||||||
|
.then(result => {
|
||||||
|
cb(null, result[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
async function generateHash(password) {
|
||||||
|
const salt = bcrypt.genSaltSync();
|
||||||
|
return bcrypt.hashSync(password, salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function create(email, password, isAdmin, name) {
|
||||||
|
const hash = await generateHash(password);
|
||||||
|
|
||||||
|
const query = `INSERT INTO accounts.users(email, password, admin, full_name)
|
||||||
|
VALUES($1, $2, $3, $4)`;
|
||||||
|
await database.executeQuery(query, [email, hash, isAdmin, name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function edit(id, email, password, isAdmin, name) {
|
||||||
|
if(password) {
|
||||||
|
const hash = await generateHash(password);
|
||||||
|
|
||||||
|
const query = `UPDATE accounts.users
|
||||||
|
SET email = $2,
|
||||||
|
password = $3,
|
||||||
|
admin = $4,
|
||||||
|
full_name = $5
|
||||||
|
WHERE user_id = $1;`;
|
||||||
|
await database.executeQuery(query, [id, email, hash, isAdmin, name]);
|
||||||
|
} else {
|
||||||
|
const query = `UPDATE accounts.users
|
||||||
|
SET email = $2,
|
||||||
|
admin = $3,
|
||||||
|
full_name = $4
|
||||||
|
WHERE user_id = $1;`;
|
||||||
|
await database.executeQuery(query, [id, email, isAdmin, name]);
|
||||||
|
}
|
||||||
|
return new User(id, email, isAdmin, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove(id) {
|
||||||
|
const query = `DELETE FROM accounts.users
|
||||||
|
WHERE user_id = $1
|
||||||
|
RETURNING email, admin, full_name;`;
|
||||||
|
const row = (await database.executeQuery(query, [id]))[0];
|
||||||
|
return new User(id, row[0], row[1], row[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retrieveAll() {
|
||||||
|
const query = `SELECT user_id, email, admin, full_name
|
||||||
|
FROM accounts.users
|
||||||
|
ORDER BY full_name;`;
|
||||||
|
const table = await database.executeQuery(query);
|
||||||
|
|
||||||
|
const accountsList = [];
|
||||||
|
table.forEach((row) => {
|
||||||
|
accountsList.push(new User(row[0], row[1], row[2], row[3]));
|
||||||
|
});
|
||||||
|
return accountsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFromID(id) {
|
||||||
|
const query = `SELECT user_id, email, admin, full_name
|
||||||
|
FROM accounts.users
|
||||||
|
WHERE user_id = $1;`;
|
||||||
|
const row = (await database.executeQuery(query, [id]))[0];
|
||||||
|
|
||||||
|
return new User(id, row[1], row[2], row[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.create = create;
|
||||||
|
exports.edit = edit;
|
||||||
|
exports.remove = remove;
|
||||||
|
exports.retrieveAll = retrieveAll;
|
||||||
|
exports.getFromID = getFromID;
|
||||||
|
exports.passport = passport;
|
|
@ -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;
|
|
@ -0,0 +1,69 @@
|
||||||
|
const app = require('../app');
|
||||||
|
const { Client } = require('pg');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'testing') {
|
||||||
|
require('dotenv').config();
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new Client();
|
||||||
|
client.connect();
|
||||||
|
|
||||||
|
async function executeQuery(query, values = []) {
|
||||||
|
const result = await client.query({
|
||||||
|
rowMode: 'array',
|
||||||
|
text: query,
|
||||||
|
values: values
|
||||||
|
});
|
||||||
|
return result.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function Initialize() {
|
||||||
|
console.log("Initializing database...")
|
||||||
|
const sql = fs.readFileSync('database/init_database.sql').toString();
|
||||||
|
await executeQuery(sql);
|
||||||
|
console.log("Database initialized.")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function checkForDatabaseInitialization() {
|
||||||
|
const databaseIsSetupQuery = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`;
|
||||||
|
let result = await executeQuery(databaseIsSetupQuery);
|
||||||
|
|
||||||
|
const databaseIsSetup = result.length !== 0;
|
||||||
|
|
||||||
|
if(!databaseIsSetup) {
|
||||||
|
await Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let latestMigration;
|
||||||
|
try {
|
||||||
|
const latestMigrationQuery = `SELECT value FROM metadata WHERE property = 'latest_migration';`;
|
||||||
|
latestMigration = +((await executeQuery(latestMigrationQuery))[0][0]);
|
||||||
|
} catch {
|
||||||
|
latestMigration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await performMigrations(latestMigration);
|
||||||
|
}
|
||||||
|
const initializationStatus = checkForDatabaseInitialization();
|
||||||
|
|
||||||
|
async function performMigrations(currentMigration) {
|
||||||
|
const migrationFileList = fs.readdirSync('database/migrations');
|
||||||
|
const latestMigration = +migrationFileList[migrationFileList.length - 1].slice(0, 1);
|
||||||
|
|
||||||
|
for(let i = +currentMigration + 1; i <= latestMigration; i++) {
|
||||||
|
const sql = fs.readFileSync(`database/migrations/${i}.sql`).toString();
|
||||||
|
await executeQuery(sql);
|
||||||
|
console.log(`Performed database migration ${i}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exports.executeQuery = executeQuery;
|
||||||
|
exports.initializationStatus = initializationStatus;
|
|
@ -0,0 +1,124 @@
|
||||||
|
/* SCORE TRACKER DATABASE LAYOUT
|
||||||
|
|
||||||
|
scores:
|
||||||
|
|
||||||
|
sports:
|
||||||
|
*sport_id* | sport_name | currently_active
|
||||||
|
|
||||||
|
divisions:
|
||||||
|
*division_id* | division_name | gender | *sport_id* | currently_active
|
||||||
|
|
||||||
|
teams:
|
||||||
|
*team_id* | team_name | ~sport_id~ | currently_active
|
||||||
|
|
||||||
|
seasons:
|
||||||
|
*season_id* | school_year
|
||||||
|
|
||||||
|
games:
|
||||||
|
*game_id* | ~division_id~ | ~season_id~ | game_date | ~team1_id~ | ~team2_id~ | team1_score | team2_score | ~submitter_id~ | updated_timestamp | submitter_name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
accounts:
|
||||||
|
|
||||||
|
users:
|
||||||
|
*user_id* | email | password | admin | full_name
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE SCHEMA IF NOT EXISTS accounts;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS accounts.users(
|
||||||
|
user_id BIGINT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
email TEXT UNIQUE NOT NULL,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
admin BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
full_name TEXT NOT NULL,
|
||||||
|
PRIMARY KEY(user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CREATE SCHEMA IF NOT EXISTS scores;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS scores.sports(
|
||||||
|
sport_id BIGINT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
sport_name TEXT UNIQUE NOT NULL,
|
||||||
|
currently_active BOOLEAN DEFAULT TRUE,
|
||||||
|
PRIMARY KEY(sport_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS scores.divisions(
|
||||||
|
division_id BIGINT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
division_name TEXT NOT NULL,
|
||||||
|
gender VARCHAR(1) NOT NULL CHECK (gender IN ( 'F', 'M' ) ),
|
||||||
|
sport_id BIGINT NOT NULL,
|
||||||
|
currently_active BOOLEAN DEFAULT TRUE,
|
||||||
|
PRIMARY KEY(division_id),
|
||||||
|
CONSTRAINT fk_sport
|
||||||
|
FOREIGN KEY(sport_id)
|
||||||
|
REFERENCES scores.sports(sport_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS scores.teams(
|
||||||
|
team_id BIGINT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
team_name TEXT NOT NULL,
|
||||||
|
sport_id BIGINT NOT NULL,
|
||||||
|
currently_active BOOLEAN DEFAULT TRUE,
|
||||||
|
PRIMARY KEY(team_id),
|
||||||
|
CONSTRAINT fk_sport
|
||||||
|
FOREIGN KEY(sport_id)
|
||||||
|
REFERENCES scores.sports(sport_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS scores.seasons(
|
||||||
|
season_id BIGINT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
school_year INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY(season_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS scores.games(
|
||||||
|
game_id BIGINT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
division_id BIGINT NOT NULL,
|
||||||
|
season_id BIGINT NOT NULL,
|
||||||
|
game_date DATE NOT NULL,
|
||||||
|
team1_id BIGINT NOT NULL,
|
||||||
|
team2_id BIGINT NOT NULL,
|
||||||
|
team1_score INTEGER NOT NULL,
|
||||||
|
team2_score INTEGER NOT NULL,
|
||||||
|
submitter_name TEXT,
|
||||||
|
submitter_id BIGINT,
|
||||||
|
updated_timestamp TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||||
|
PRIMARY KEY(game_id),
|
||||||
|
CONSTRAINT fk_division
|
||||||
|
FOREIGN KEY(division_id)
|
||||||
|
REFERENCES scores.divisions(division_id),
|
||||||
|
CONSTRAINT fk_season
|
||||||
|
FOREIGN KEY(season_id)
|
||||||
|
REFERENCES scores.seasons(season_id),
|
||||||
|
CONSTRAINT fk_team1
|
||||||
|
FOREIGN KEY(team1_id)
|
||||||
|
REFERENCES scores.teams(team_id),
|
||||||
|
CONSTRAINT fk_team2
|
||||||
|
FOREIGN KEY(team2_id)
|
||||||
|
REFERENCES scores.teams(team_id),
|
||||||
|
CONSTRAINT fk_submitter
|
||||||
|
FOREIGN KEY(submitter_id)
|
||||||
|
REFERENCES accounts.users(user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS metadata(
|
||||||
|
property TEXT UNIQUE NOT NULL,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO metadata(property, value)
|
||||||
|
VALUES("latest_migration", "3");
|
||||||
|
|
||||||
|
COMMIT;
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* ADD METADATA TABLE */
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS metadata(
|
||||||
|
property TEXT UNIQUE NOT NULL,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO metadata(property, value)
|
||||||
|
VALUES('latest_migration', '1');
|
||||||
|
|
||||||
|
COMMIT;
|
|
@ -0,0 +1,12 @@
|
||||||
|
/* ADD ACCOUNT NAME COLUMN */
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE accounts.users
|
||||||
|
ADD COLUMN full_name TEXT;
|
||||||
|
|
||||||
|
UPDATE metadata
|
||||||
|
SET value = '2'
|
||||||
|
WHERE property = 'latest_migration';
|
||||||
|
|
||||||
|
COMMIT;
|
|
@ -0,0 +1,15 @@
|
||||||
|
/* ADD OPTIONAL SUBMITTER NAME COLUMN IN GAMES TABLE */
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE scores.games ALTER COLUMN submitter_id DROP NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE scores.games
|
||||||
|
ADD COLUMN submitter_name TEXT;
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE metadata
|
||||||
|
SET value = '3'
|
||||||
|
WHERE property = 'latest_migration';
|
||||||
|
|
||||||
|
COMMIT;
|
|
@ -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;
|
|
@ -0,0 +1,140 @@
|
||||||
|
const database = require('./../database');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Game {
|
||||||
|
constructor(id, date, team1ID, team2ID, team1Score, team2Score, divisionID, seasonID, submitterID, submitterName) {
|
||||||
|
this.id = id;
|
||||||
|
this.date = date;
|
||||||
|
this.team1ID = team1ID;
|
||||||
|
this.team2ID = team2ID;
|
||||||
|
this.team1Score = team1Score;
|
||||||
|
this.team2Score = team2Score;
|
||||||
|
this.divisionID = divisionID;
|
||||||
|
this.seasonID = seasonID;
|
||||||
|
this.submitterID = submitterID;
|
||||||
|
this.submitterName = submitterName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, submitterID, submitterName = undefined) {
|
||||||
|
let id;
|
||||||
|
if(submitterName) {
|
||||||
|
const query = `INSERT INTO scores.games(division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_name)
|
||||||
|
VALUES($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
|
RETURNING game_id;`;
|
||||||
|
id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, submitterName]))[0][0];
|
||||||
|
} else {
|
||||||
|
const query = `INSERT INTO scores.games(division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id)
|
||||||
|
VALUES($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
|
RETURNING game_id;`;
|
||||||
|
id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, submitterID]))[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Game(id, date, team1ID, team2ID, team1Score, team2Score);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove(id) {
|
||||||
|
const query = `DELETE FROM scores.games
|
||||||
|
WHERE game_id = $1
|
||||||
|
RETURNING * ;`;
|
||||||
|
const row = (await database.executeQuery(query, [id]))[0];
|
||||||
|
return new Game(id, row[3], row[4], row[5], row[6], row[7]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retrieve(teamID, divisionID, seasonID) {
|
||||||
|
let table;
|
||||||
|
|
||||||
|
if(teamID && divisionID && seasonID) {
|
||||||
|
const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id, submitter_name
|
||||||
|
FROM scores.games
|
||||||
|
WHERE (team1_id = $1 OR team2_id = $1) AND division_id = $2 AND season_id = $3
|
||||||
|
ORDER BY game_date DESC;`;
|
||||||
|
table = await database.executeQuery(query, [teamID,divisionID,seasonID]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id, submitter_name
|
||||||
|
FROM scores.games
|
||||||
|
ORDER BY game_date DESC;`;
|
||||||
|
table = await database.executeQuery(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const gamesList = [];
|
||||||
|
table.forEach((row) => {
|
||||||
|
if(teamID) {
|
||||||
|
const opponentIsTeam2 = teamID != row[5];
|
||||||
|
const opponentID = opponentIsTeam2 ? row[5] : row[4];
|
||||||
|
const teamScore = opponentIsTeam2 ? row[6] : row[7];
|
||||||
|
const opponentScore = opponentIsTeam2 ? row[7] : row[6];
|
||||||
|
|
||||||
|
gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), teamID, opponentID, teamScore, opponentScore, row[1], row[2], row[8], row[9]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2], row[8], row[9]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return gamesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retrieveByUser(userID) {
|
||||||
|
const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score
|
||||||
|
FROM scores.games
|
||||||
|
WHERE submitter_id = $1
|
||||||
|
ORDER BY game_date DESC;`;
|
||||||
|
const table = await database.executeQuery(query, [userID]);
|
||||||
|
|
||||||
|
const gamesList = [];
|
||||||
|
table.forEach((row) => {
|
||||||
|
gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2]));
|
||||||
|
});
|
||||||
|
return gamesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function edit(gameID, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) {
|
||||||
|
const query = `UPDATE scores.games
|
||||||
|
SET division_id = $2,
|
||||||
|
season_id = $3,
|
||||||
|
game_date = $4,
|
||||||
|
team1_id = $5,
|
||||||
|
team2_id = $6,
|
||||||
|
team1_score = $7,
|
||||||
|
team2_score = $8
|
||||||
|
WHERE game_id = $1;`;
|
||||||
|
await database.executeQuery(query, [gameID, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score]);
|
||||||
|
return new Game(gameID, date, team1ID, team2ID, team1Score, team2Score, divisionID, seasonID);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFromID(gameID) {
|
||||||
|
const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id
|
||||||
|
FROM scores.games
|
||||||
|
WHERE game_id = $1;`;
|
||||||
|
const row = (await database.executeQuery(query, [gameID]))[0];
|
||||||
|
return new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2], row[8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLatest(userID = undefined) {
|
||||||
|
if(userID) {
|
||||||
|
const games = await retrieveByUser(userID);
|
||||||
|
return games[0];
|
||||||
|
} else {
|
||||||
|
const games = await retrieve();
|
||||||
|
return games[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exports.add = add;
|
||||||
|
exports.remove = remove;
|
||||||
|
exports.retrieve = retrieve;
|
||||||
|
exports.retrieveByUser = retrieveByUser;
|
||||||
|
exports.edit = edit;
|
||||||
|
exports.getFromID = getFromID;
|
||||||
|
exports.getLatest = getLatest;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1,33 @@
|
||||||
|
const app = require('../app');
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'testing') {
|
||||||
|
require('dotenv').config();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
send: function (recipient, subject, message) {
|
||||||
|
send(recipient, subject, message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var send = function (recipient, subject, message) {
|
||||||
|
transporter.sendMail({
|
||||||
|
to: recipient, // list of receivers
|
||||||
|
subject: subject, // Subject line
|
||||||
|
html: message, // html body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let transporter = nodemailer.createTransport({
|
||||||
|
host: process.env.MAIL_HOST,
|
||||||
|
port: process.env.MAIL_PORT,
|
||||||
|
secure: process.env.MAIL_SECURE,
|
||||||
|
auth: {
|
||||||
|
user: process.env.MAIL_USER,
|
||||||
|
pass: process.env.MAIL_PASS,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
from: process.env.MAIL_FROM
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
|
@ -1,21 +1,25 @@
|
||||||
{
|
{
|
||||||
"name": "demo",
|
"name": "score-tracker",
|
||||||
"version": "0.0.0",
|
"version": "1.2.0",
|
||||||
"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",
|
||||||
"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",
|
||||||
"pug": "2.0.0-beta11"
|
"nodemailer": "^6.6.5",
|
||||||
},
|
"passport": "^0.5.0",
|
||||||
"devDependencies": {
|
"passport-local": "^1.0.0",
|
||||||
"mocha": "^5.1.1",
|
"pg": "^8.7.1",
|
||||||
"supertest": "^3.0.0"
|
"pug": "^3.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
export async function getSports() {
|
||||||
|
const response = await fetch(`/data/sports`);
|
||||||
|
const sportsList = await response.json();
|
||||||
|
return sportsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSportName(sportID) {
|
||||||
|
const response = await fetch(`/data/sport?sport=${sportID}`);
|
||||||
|
const sport = await response.json();
|
||||||
|
return sport.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSeasons() {
|
||||||
|
const response = await fetch(`/data/seasons`);
|
||||||
|
const seasonsList = await response.json();
|
||||||
|
return seasonsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGenders(sportID) {
|
||||||
|
const response = await fetch(`/data/genders?sport=${+sportID}`);
|
||||||
|
const gendersList = await response.json();
|
||||||
|
return gendersList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDivisions(sportID = undefined, gender = undefined) {
|
||||||
|
let URL = '/data/divisions?';
|
||||||
|
if(sportID) URL += `sport=${+sportID}&`;
|
||||||
|
if(gender) URL += `gender=${gender}&`;
|
||||||
|
|
||||||
|
const response = await fetch(URL);
|
||||||
|
const divisionsList = await response.json();
|
||||||
|
return divisionsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDivision(divisionID) {
|
||||||
|
const response = await fetch(`/data/division?division=${divisionID}`);
|
||||||
|
const division = await response.json();
|
||||||
|
return division;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeams(sportID = undefined) {
|
||||||
|
let URL = '/data/teams?';
|
||||||
|
if(sportID) URL += `sport=${+sportID}&`;
|
||||||
|
|
||||||
|
const response = await fetch(URL);
|
||||||
|
const teamsList = await response.json();
|
||||||
|
return teamsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeam(teamID) {
|
||||||
|
const response = await fetch(`/data/team?team=${+teamID}`);
|
||||||
|
const team = await response.json();
|
||||||
|
return team;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGames(teamID = undefined, divisionID = undefined, seasonID = undefined) {
|
||||||
|
let URL = '/data/games?';
|
||||||
|
if(teamID) URL += `team=${+teamID}&`;
|
||||||
|
if(divisionID) URL += `division=${+divisionID}&`;
|
||||||
|
if(seasonID) URL += `season=${+seasonID}`;
|
||||||
|
|
||||||
|
|
||||||
|
const response = await fetch(URL);
|
||||||
|
const gamesList = await response.json();
|
||||||
|
return gamesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGamesByUser() {
|
||||||
|
let URL = '/data/games?user=1';
|
||||||
|
const response = await fetch(URL);
|
||||||
|
const gamesList = await response.json();
|
||||||
|
return gamesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGame(gameID) {
|
||||||
|
const response = await fetch(`/data/game?game=${gameID}`);
|
||||||
|
const game = await response.json();
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLatestGame(ofUser = false) {
|
||||||
|
let URL = `/data/game?`;
|
||||||
|
if(ofUser) URL += `ofuser=1`;
|
||||||
|
const response = await fetch(URL);
|
||||||
|
const game = await response.json();
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAccounts() {
|
||||||
|
const response = await fetch(`/data/accounts`);
|
||||||
|
const accounts = await response.json();
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAccount(accountID) {
|
||||||
|
const response = await fetch(`/data/account?account=${accountID}`);
|
||||||
|
const account = await response.json();
|
||||||
|
return account;
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
import * as Data from "./data.js";
|
||||||
|
|
||||||
|
export async function populateSports(sportDropdown, selectedSportID = undefined, data = undefined) {
|
||||||
|
sportDropdown.innerHTML = "";
|
||||||
|
|
||||||
|
let sportsList;
|
||||||
|
if(data) {
|
||||||
|
sportsList = data.sports;
|
||||||
|
} else {
|
||||||
|
sportsList = await Data.getSports();
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentIndex = 0;
|
||||||
|
let selectedSportIndex;
|
||||||
|
sportsList.forEach(sport => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.text = sport.name;
|
||||||
|
option.value = sport.id;
|
||||||
|
sportDropdown.appendChild(option);
|
||||||
|
|
||||||
|
if(sport.id == selectedSportID || (data && sport.id == data.latestGame.sportID)) selectedSportIndex = currentIndex;
|
||||||
|
currentIndex++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(selectedSportIndex) sportDropdown.selectedIndex = selectedSportIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function populateSeasons(seasonDropdown, selectedSeasonID = undefined, data = undefined) {
|
||||||
|
seasonDropdown.innerHTML = "";
|
||||||
|
|
||||||
|
let seasonsList;
|
||||||
|
|
||||||
|
if(data) {
|
||||||
|
seasonsList = data.seasons;
|
||||||
|
} else {
|
||||||
|
seasonsList = await Data.getSeasons();
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentIndex = 0;
|
||||||
|
let selectedSeasonIndex;
|
||||||
|
seasonsList.forEach(season => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.text = (season.year - 1) + "-" + season.year;
|
||||||
|
option.value = season.id;
|
||||||
|
seasonDropdown.appendChild(option);
|
||||||
|
|
||||||
|
if(season.id == selectedSeasonID || (data && season.id == data.latestGame.seasonID)) selectedSeasonIndex = currentIndex;
|
||||||
|
currentIndex++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(selectedSeasonIndex) seasonDropdown.selectedIndex = selectedSeasonIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function populateGenders(genderDropdown, selectedSportID, selectedGender = undefined, data = undefined) {
|
||||||
|
genderDropdown.innerHTML = "";
|
||||||
|
|
||||||
|
let gendersList;
|
||||||
|
if(data) {
|
||||||
|
gendersList = data.genders;
|
||||||
|
selectedSportID = data.latestGame.sportID;
|
||||||
|
} else {
|
||||||
|
gendersList = await Data.getGenders(selectedSportID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(selectedSportID) {
|
||||||
|
let currentIndex = 0;
|
||||||
|
let selectedGenderIndex;
|
||||||
|
gendersList.forEach(gender => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.text = (gender.name == "female") ? "Female" : (gender.name == "male") ? "Male" : "";
|
||||||
|
option.value = gender.name;
|
||||||
|
genderDropdown.appendChild(option);
|
||||||
|
|
||||||
|
if(gender.name == selectedGender || (data && gender.name == data.latestGame.gender.name)) selectedGenderIndex = currentIndex;
|
||||||
|
currentIndex++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(selectedGenderIndex) genderDropdown.selectedIndex = selectedGenderIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function populateDivisions (divisionDropdown, selectedSportID, selectedGender, selectedDivisionID = undefined, data = undefined) {
|
||||||
|
divisionDropdown.innerHTML = "";
|
||||||
|
|
||||||
|
if(data) {
|
||||||
|
selectedSportID = data.latestGame.sportID;
|
||||||
|
selectedGender = data.latestGame.gender;
|
||||||
|
}
|
||||||
|
if(selectedSportID && selectedGender) {
|
||||||
|
let divisionsList;
|
||||||
|
|
||||||
|
if(data) {
|
||||||
|
divisionsList = data.divisions;
|
||||||
|
} else {
|
||||||
|
divisionsList = await Data.getDivisions(selectedSportID, selectedGender);
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentIndex = 0;
|
||||||
|
let selectedDivisionIndex;
|
||||||
|
divisionsList.forEach(division => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.text = division.name;
|
||||||
|
option.value = division.id;
|
||||||
|
divisionDropdown.appendChild(option);
|
||||||
|
|
||||||
|
if(division.id == selectedDivisionID || (data && division.id == data.latestGame.divisionID)) selectedDivisionIndex = currentIndex;
|
||||||
|
currentIndex++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(selectedDivisionIndex) divisionDropdown.selectedIndex = selectedDivisionIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function populateTeams(teamDropdown, selectedSportID, selectedTeamID = undefined, data = undefined, useOpponent = false) {
|
||||||
|
teamDropdown.innerHTML = "";
|
||||||
|
|
||||||
|
if(data) {
|
||||||
|
selectedSportID = data.latestGame.sportID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(selectedSportID) {
|
||||||
|
let teamsList;
|
||||||
|
if(data) {
|
||||||
|
teamsList = data.teams;
|
||||||
|
} else {
|
||||||
|
teamsList = await Data.getTeams(selectedSportID);
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentIndex = 0;
|
||||||
|
let selectedTeamIndex;
|
||||||
|
|
||||||
|
if(data) {
|
||||||
|
selectedTeamID = useOpponent ? data.latestGame.team2ID : data.latestGame.team1ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
teamsList.forEach(team => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.text = team.name;
|
||||||
|
option.value = team.id;
|
||||||
|
teamDropdown.appendChild(option);
|
||||||
|
|
||||||
|
if(team.id == selectedTeamID) selectedTeamIndex = currentIndex;
|
||||||
|
currentIndex++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(selectedTeamIndex) teamDropdown.selectedIndex = selectedTeamIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addHiddenValue(name, value, form) {
|
||||||
|
const valueInput = document.createElement('input');
|
||||||
|
valueInput.setAttribute('name', name);
|
||||||
|
valueInput.setAttribute('value', value);
|
||||||
|
valueInput.setAttribute('type', 'hidden');
|
||||||
|
form.appendChild(valueInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addRemoveFunction(removeButton, form, objectTitle) {
|
||||||
|
removeButton.addEventListener('click', async () => {
|
||||||
|
const verified = confirm(`This ${objectTitle} will be removed.`);
|
||||||
|
|
||||||
|
if(verified) {
|
||||||
|
await addHiddenValue('remove', 1, form);
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
import * as Data from "./data.js";
|
||||||
|
import * as Form from "./form.js";
|
||||||
|
|
||||||
|
const dropdownsDiv = document.getElementById('dropdowns-div');
|
||||||
|
const sportDropdown = document.getElementById('sport-dropdown');
|
||||||
|
const seasonDropdown = document.getElementById('year-dropdown');
|
||||||
|
const genderDropdown = document.getElementById('gender-dropdown');
|
||||||
|
const divisionDropdown = document.getElementById('division-dropdown');
|
||||||
|
const teamDropdown = document.getElementById('team-dropdown');
|
||||||
|
const gamesTable = document.getElementById('games-table');
|
||||||
|
const gamesTableHeader = document.getElementById('games-table-header');
|
||||||
|
const noScoresMessage = document.getElementById('no-scores-message');
|
||||||
|
const addScoreButton = document.getElementById('add-score-button');
|
||||||
|
const manageButton = document.getElementById('manage-button');
|
||||||
|
const loadingSpan = document.getElementById('loading');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function initializeForm() {
|
||||||
|
const data = await (await fetch(`/fetch/index/dropdown`)).json();
|
||||||
|
|
||||||
|
await Form.populateSeasons(seasonDropdown, null, data);
|
||||||
|
await Form.populateSports(sportDropdown, null, data);
|
||||||
|
await Form.populateGenders(genderDropdown, null, null, data);
|
||||||
|
await Form.populateDivisions(divisionDropdown, null, null, null, data);
|
||||||
|
await Form.populateTeams(teamDropdown, null, null, data);
|
||||||
|
|
||||||
|
seasonDropdown.onchange = loadTable;
|
||||||
|
|
||||||
|
sportDropdown.onchange = async () => {
|
||||||
|
await Form.populateGenders(genderDropdown, sportDropdown.value)
|
||||||
|
await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value);
|
||||||
|
await Form.populateTeams(teamDropdown, sportDropdown.value);
|
||||||
|
loadTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
genderDropdown.onchange = async () => {
|
||||||
|
await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value);
|
||||||
|
loadTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
divisionDropdown.onchange = loadTable;
|
||||||
|
teamDropdown.onchange = loadTable;
|
||||||
|
|
||||||
|
loadingSpan.textContent = '';
|
||||||
|
dropdownsDiv.style.visibility = 'visible';
|
||||||
|
|
||||||
|
loadTable();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
initializeForm();
|
||||||
|
|
||||||
|
async function loadTable() {
|
||||||
|
gamesTable.innerHTML = "";
|
||||||
|
gamesTableHeader.textContent = "";
|
||||||
|
noScoresMessage.textContent = "";
|
||||||
|
|
||||||
|
const selectedTeamID = teamDropdown.value;
|
||||||
|
const selectedDivisionID = divisionDropdown.value;
|
||||||
|
const selectedSeasonID = seasonDropdown.value;
|
||||||
|
|
||||||
|
if(selectedTeamID && selectedDivisionID) {
|
||||||
|
gamesTableHeader.textContent = `Scores for ${teamDropdown.options[teamDropdown.selectedIndex].text}`;
|
||||||
|
|
||||||
|
noScoresMessage.textContent = "Loading scores...";
|
||||||
|
const requestURL = `/fetch/index/scores?team=${+selectedTeamID}&division=${+selectedDivisionID}&season=${+selectedSeasonID}`;
|
||||||
|
const gamesList = await (await fetch(requestURL)).json();
|
||||||
|
noScoresMessage.textContent = "";
|
||||||
|
if(gamesList.length > 0) {
|
||||||
|
await setupGamesTableHeaders();
|
||||||
|
|
||||||
|
gamesList.forEach((game) => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
|
const scoreCell = document.createElement('td');
|
||||||
|
const winLossLine = document.createElement('span');
|
||||||
|
winLossLine.textContent = (game.team1Score > game.team2Score) ? "Win" : (game.team1Score < game.team2Score) ? "Loss" : "Tie";
|
||||||
|
scoreCell.appendChild(winLossLine);
|
||||||
|
const scoreLine = document.createElement('span');
|
||||||
|
scoreLine.textContent = game.team1Score + "-" + game.team2Score;
|
||||||
|
scoreCell.appendChild(scoreLine);
|
||||||
|
row.appendChild(scoreCell);
|
||||||
|
|
||||||
|
const opponentCell = document.createElement('td');
|
||||||
|
opponentCell.textContent = game.opponent.name;
|
||||||
|
row.appendChild(opponentCell);
|
||||||
|
|
||||||
|
const dateCell = document.createElement('td');
|
||||||
|
dateCell.textContent = game.date;
|
||||||
|
dateCell.style['white-space'] = 'nowrap';
|
||||||
|
row.appendChild(dateCell);
|
||||||
|
|
||||||
|
gamesTable.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
noScoresMessage.textContent = "No scores available";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupGamesTableHeaders() {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
|
const scoresHeader = document.createElement('th');
|
||||||
|
scoresHeader.textContent = "Score"
|
||||||
|
row.appendChild(scoresHeader);
|
||||||
|
|
||||||
|
const opponentHeader = document.createElement('th');
|
||||||
|
opponentHeader.textContent = "Opponent";
|
||||||
|
row.appendChild(opponentHeader);
|
||||||
|
|
||||||
|
const dateHeader = document.createElement('th');
|
||||||
|
dateHeader.textContent = "Date";
|
||||||
|
row.appendChild(dateHeader);
|
||||||
|
|
||||||
|
gamesTable.appendChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if(addScoreButton) {
|
||||||
|
addScoreButton.addEventListener('click', () => {
|
||||||
|
window.location.href = '/manage/game';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(manageButton) {
|
||||||
|
manageButton.addEventListener('click', () => {
|
||||||
|
window.location.href = '/manage'
|
||||||
|
});
|
||||||
|
}
|
|
@ -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 = '/';
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,410 @@
|
||||||
|
import * as Data from "./data.js";
|
||||||
|
|
||||||
|
const categoryDropdown = document.getElementById('category-dropdown');
|
||||||
|
const itemsListTable = document.getElementById('items-list');
|
||||||
|
const addNewButton = document.getElementById('add-new-button');
|
||||||
|
const loadingSpan = document.getElementById('loading-message');
|
||||||
|
|
||||||
|
|
||||||
|
function getGenderLetter(genderName) {
|
||||||
|
return genderName == "female" ? "F" : "M";
|
||||||
|
}
|
||||||
|
|
||||||
|
class Category {
|
||||||
|
constructor(name, getItems, listHeaders, listItem, addItem, editItem) {
|
||||||
|
this.name = name;
|
||||||
|
this.getItems = getItems;
|
||||||
|
this.listHeaders = listHeaders;
|
||||||
|
this.listItem = listItem;
|
||||||
|
this.addItem = addItem;
|
||||||
|
this.editItem = editItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CATEGORIES = [];
|
||||||
|
|
||||||
|
CATEGORIES.push(new Category(
|
||||||
|
"seasons",
|
||||||
|
async function getSeasons() {
|
||||||
|
return await Data.getSeasons();
|
||||||
|
},
|
||||||
|
async function listSeasonHeaders() {
|
||||||
|
const headerRow = document.createElement('tr');
|
||||||
|
|
||||||
|
const yearsHeader = document.createElement('th');
|
||||||
|
yearsHeader.textContent = "Years";
|
||||||
|
|
||||||
|
headerRow.appendChild(yearsHeader);
|
||||||
|
|
||||||
|
const spacerHeader = document.createElement('th');
|
||||||
|
spacerHeader.classList.add('spacer-column');
|
||||||
|
headerRow.appendChild(spacerHeader);
|
||||||
|
|
||||||
|
itemsListTable.appendChild(headerRow);
|
||||||
|
},
|
||||||
|
function listSeason(season, row) {
|
||||||
|
const yearCell = document.createElement('td');
|
||||||
|
yearCell.textContent = (season.year - 1) + "-" + season.year;
|
||||||
|
row.appendChild(yearCell);
|
||||||
|
|
||||||
|
const spacerCell = document.createElement('td');
|
||||||
|
row.appendChild(spacerCell);
|
||||||
|
},
|
||||||
|
async function addSeason() {
|
||||||
|
window.location.href = "/manage/season";
|
||||||
|
},
|
||||||
|
async function editSeason(id) {
|
||||||
|
const verified = confirm(`This season will be removed.`);
|
||||||
|
|
||||||
|
if(verified) {
|
||||||
|
const form = document.createElement('form');
|
||||||
|
form.action = "/manage/season";
|
||||||
|
form.method = "POST";
|
||||||
|
form.style.visibility = "hidden";
|
||||||
|
itemsListTable.appendChild(form);
|
||||||
|
|
||||||
|
const remove = document.createElement('input');
|
||||||
|
remove.setAttribute('name', 'remove');
|
||||||
|
remove.setAttribute('value', 1);
|
||||||
|
form.appendChild(remove);
|
||||||
|
|
||||||
|
const seasonID = document.createElement('input');
|
||||||
|
seasonID.setAttribute('name', 'season');
|
||||||
|
seasonID.setAttribute('value', id);
|
||||||
|
form.appendChild(seasonID);
|
||||||
|
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
CATEGORIES.push(new Category(
|
||||||
|
"sports",
|
||||||
|
async function getSports() {
|
||||||
|
return await Data.getSports();
|
||||||
|
},
|
||||||
|
async function listSportHeaders() {
|
||||||
|
const headerRow = document.createElement('tr');
|
||||||
|
|
||||||
|
const nameHeader = document.createElement('th');
|
||||||
|
nameHeader.textContent = "Name";
|
||||||
|
headerRow.appendChild(nameHeader);
|
||||||
|
|
||||||
|
const spacerHeader = document.createElement('th');
|
||||||
|
spacerHeader.classList.add('spacer-column');
|
||||||
|
headerRow.appendChild(spacerHeader);
|
||||||
|
|
||||||
|
itemsListTable.appendChild(headerRow);
|
||||||
|
},
|
||||||
|
function listSport(sport, row) {
|
||||||
|
const nameCell = document.createElement('td');
|
||||||
|
nameCell.textContent = sport.name;
|
||||||
|
row.appendChild(nameCell);
|
||||||
|
|
||||||
|
const spacerCell = document.createElement('td');
|
||||||
|
row.appendChild(spacerCell);
|
||||||
|
},
|
||||||
|
async function addSport() {
|
||||||
|
window.location.href = `/manage/sport`;
|
||||||
|
},
|
||||||
|
async function editSport(id) {
|
||||||
|
window.location.href = `/manage/sport?sport=${id}`
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
CATEGORIES.push(new Category(
|
||||||
|
"divisions",
|
||||||
|
async function getDivisions() {
|
||||||
|
return await (await fetch('/fetch/manage/divisions')).json();
|
||||||
|
},
|
||||||
|
async function listDivisionHeaders() {
|
||||||
|
const headerRow = document.createElement('tr');
|
||||||
|
|
||||||
|
const nameHeader = document.createElement('th');
|
||||||
|
nameHeader.textContent = "Name";
|
||||||
|
headerRow.appendChild(nameHeader);
|
||||||
|
|
||||||
|
const genderHeader = document.createElement('th');
|
||||||
|
headerRow.appendChild(genderHeader);
|
||||||
|
|
||||||
|
const spacerHeader = document.createElement('th');
|
||||||
|
spacerHeader.classList.add('spacer-column');
|
||||||
|
headerRow.appendChild(spacerHeader);
|
||||||
|
|
||||||
|
const sportHeader = document.createElement('th');
|
||||||
|
sportHeader.textContent = "Sport";
|
||||||
|
headerRow.appendChild(sportHeader);
|
||||||
|
|
||||||
|
itemsListTable.appendChild(headerRow);
|
||||||
|
},
|
||||||
|
function listDivision(division, row) {
|
||||||
|
const nameCell = document.createElement('td');
|
||||||
|
nameCell.textContent = division.name;
|
||||||
|
row.appendChild(nameCell);
|
||||||
|
|
||||||
|
const genderCell = document.createElement('td');
|
||||||
|
const gender = getGenderLetter(division.gender.name);
|
||||||
|
genderCell.textContent = gender;
|
||||||
|
row.appendChild(genderCell);
|
||||||
|
|
||||||
|
const spacerCell = document.createElement('td');
|
||||||
|
row.appendChild(spacerCell);
|
||||||
|
|
||||||
|
const sportCell = document.createElement('td');
|
||||||
|
let sportName = division.sport.name;
|
||||||
|
sportCell.textContent = sportName;
|
||||||
|
row.appendChild(sportCell);
|
||||||
|
},
|
||||||
|
async function addDivision() {
|
||||||
|
window.location.href = "/manage/division";
|
||||||
|
},
|
||||||
|
async function editDivision(id) {
|
||||||
|
window.location.href = `/manage/division?division=${id}`
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
CATEGORIES.push(new Category(
|
||||||
|
"teams",
|
||||||
|
async function getTeams() {
|
||||||
|
return await (await fetch('/fetch/manage/teams')).json();
|
||||||
|
},
|
||||||
|
async function listTeamHeaders() {
|
||||||
|
const headerRow = document.createElement('tr');
|
||||||
|
|
||||||
|
const nameHeader = document.createElement('th');
|
||||||
|
nameHeader.textContent = "Name";
|
||||||
|
headerRow.appendChild(nameHeader);
|
||||||
|
|
||||||
|
const spacerHeader = document.createElement('th');
|
||||||
|
spacerHeader.classList.add('spacer-column');
|
||||||
|
headerRow.appendChild(spacerHeader);
|
||||||
|
|
||||||
|
const sportHeader = document.createElement('th');
|
||||||
|
sportHeader.textContent = "Sport";
|
||||||
|
headerRow.appendChild(sportHeader);
|
||||||
|
|
||||||
|
itemsListTable.appendChild(headerRow);
|
||||||
|
},
|
||||||
|
function listTeam(team, row) {
|
||||||
|
const nameCell = document.createElement('td');
|
||||||
|
nameCell.textContent = team.name;
|
||||||
|
row.appendChild(nameCell);
|
||||||
|
|
||||||
|
const spacerCell = document.createElement('td');
|
||||||
|
row.appendChild(spacerCell);
|
||||||
|
|
||||||
|
const sportCell = document.createElement('td');
|
||||||
|
let sportName = team.sport.name;
|
||||||
|
sportCell.textContent = sportName;
|
||||||
|
row.appendChild(sportCell);
|
||||||
|
},
|
||||||
|
async function addTeam() {
|
||||||
|
window.location.href = "/manage/team";
|
||||||
|
},
|
||||||
|
async function editTeam(id) {
|
||||||
|
window.location.href = `/manage/team?team=${id}`;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
CATEGORIES.push(new Category(
|
||||||
|
"games",
|
||||||
|
async function getGames() {
|
||||||
|
return await (await fetch('/fetch/manage/games')).json();
|
||||||
|
},
|
||||||
|
async function listGameHeaders() {
|
||||||
|
const headerRow = document.createElement('tr');
|
||||||
|
|
||||||
|
const teamsHeader = document.createElement('th');
|
||||||
|
teamsHeader.textContent = "Teams";
|
||||||
|
headerRow.appendChild(teamsHeader);
|
||||||
|
|
||||||
|
const scoreHeader = document.createElement('th');
|
||||||
|
headerRow.appendChild(scoreHeader);
|
||||||
|
|
||||||
|
const spacerHeader = document.createElement('th');
|
||||||
|
spacerHeader.classList.add('spacer-column');
|
||||||
|
headerRow.appendChild(spacerHeader);
|
||||||
|
|
||||||
|
const sportNameHeader = document.createElement('th');
|
||||||
|
sportNameHeader.textContent = "Sport";
|
||||||
|
headerRow.appendChild(sportNameHeader);
|
||||||
|
|
||||||
|
const dateHeader = document.createElement('th');
|
||||||
|
dateHeader.textContent = "Date";
|
||||||
|
headerRow.appendChild(dateHeader);
|
||||||
|
|
||||||
|
const submitterHeader = document.createElement('th');
|
||||||
|
submitterHeader.textContent = "Submitter";
|
||||||
|
headerRow.appendChild(submitterHeader);
|
||||||
|
|
||||||
|
itemsListTable.appendChild(headerRow);
|
||||||
|
},
|
||||||
|
function listGame(game, row) {
|
||||||
|
const teamsCell = document.createElement('td');
|
||||||
|
const team1NameSpan = document.createElement('span');
|
||||||
|
let team1Name = game.team1.name;
|
||||||
|
team1NameSpan.textContent = team1Name;
|
||||||
|
teamsCell.appendChild(team1NameSpan);
|
||||||
|
const team2NameSpan = document.createElement('span');
|
||||||
|
let team2Name = game.team2.name;
|
||||||
|
team2NameSpan.textContent = team2Name;
|
||||||
|
teamsCell.appendChild(team2NameSpan);
|
||||||
|
row.appendChild(teamsCell);
|
||||||
|
|
||||||
|
const scoresCell = document.createElement('td');
|
||||||
|
const team1ScoreSpan = document.createElement('span');
|
||||||
|
team1ScoreSpan.textContent = game.team1Score;
|
||||||
|
scoresCell.appendChild(team1ScoreSpan);
|
||||||
|
const team2ScoreSpan = document.createElement('span');
|
||||||
|
team2ScoreSpan.textContent = game.team2Score;
|
||||||
|
scoresCell.appendChild(team2ScoreSpan);
|
||||||
|
row.appendChild(scoresCell);
|
||||||
|
|
||||||
|
const spacerCell = document.createElement('td');
|
||||||
|
row.appendChild(spacerCell);
|
||||||
|
|
||||||
|
const sportCell = document.createElement('td');
|
||||||
|
const sportSpan = document.createElement('span');
|
||||||
|
const divisionSpan = document.createElement('span');
|
||||||
|
divisionSpan.classList.add('flat-content');
|
||||||
|
const divisionNameSpan = document.createElement('span');
|
||||||
|
const divisionGenderSpan = document.createElement('span');
|
||||||
|
divisionSpan.appendChild(divisionNameSpan);
|
||||||
|
divisionSpan.appendChild(divisionGenderSpan);
|
||||||
|
let divisionName = game.division.name;
|
||||||
|
let sportName = game.sport.name;
|
||||||
|
divisionNameSpan.textContent = divisionName;
|
||||||
|
sportSpan.textContent = sportName;
|
||||||
|
divisionGenderSpan.textContent = getGenderLetter(game.division.gender.name);
|
||||||
|
sportCell.appendChild(sportSpan);
|
||||||
|
sportCell.appendChild(divisionSpan);
|
||||||
|
row.appendChild(sportCell);
|
||||||
|
|
||||||
|
const dateCell = document.createElement('td');
|
||||||
|
const yearSpan = document.createElement('span');
|
||||||
|
yearSpan.textContent = game.date.slice(0,4);
|
||||||
|
dateCell.appendChild(yearSpan);
|
||||||
|
const dateSpan = document.createElement('span');
|
||||||
|
dateSpan.textContent = game.date.slice(5);
|
||||||
|
dateCell.appendChild(dateSpan);
|
||||||
|
row.appendChild(dateCell);
|
||||||
|
|
||||||
|
const submitterCell = document.createElement('td');
|
||||||
|
if(game.submitterID) {
|
||||||
|
let submitterName = game.submitter.name;
|
||||||
|
submitterCell.textContent = submitterName;
|
||||||
|
} else {
|
||||||
|
submitterCell.textContent = game.submitterName;
|
||||||
|
}
|
||||||
|
row.appendChild(submitterCell);
|
||||||
|
},
|
||||||
|
async function addGame() {
|
||||||
|
window.location.href = "/manage/game";
|
||||||
|
},
|
||||||
|
async function editGame(id) {
|
||||||
|
window.location.href = `/manage/game?game=${id}`;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
CATEGORIES.push(new Category(
|
||||||
|
"accounts",
|
||||||
|
async function getAccounts() {
|
||||||
|
return await Data.getAccounts();
|
||||||
|
},
|
||||||
|
async function listAccountHeaders() {
|
||||||
|
const headerRow = document.createElement('tr');
|
||||||
|
|
||||||
|
const nameHeader = document.createElement('th');
|
||||||
|
nameHeader.textContent = "Name";
|
||||||
|
headerRow.appendChild(nameHeader);
|
||||||
|
|
||||||
|
const emailHeader = document.createElement('th');
|
||||||
|
emailHeader.textContent = "Email";
|
||||||
|
headerRow.appendChild(emailHeader);
|
||||||
|
|
||||||
|
const spacerHeader = document.createElement('th');
|
||||||
|
spacerHeader.classList.add('spacer-column');
|
||||||
|
headerRow.appendChild(spacerHeader);
|
||||||
|
|
||||||
|
const adminHeader = document.createElement('th');
|
||||||
|
adminHeader.textContent = "Admin?";
|
||||||
|
headerRow.appendChild(adminHeader);
|
||||||
|
|
||||||
|
itemsListTable.appendChild(headerRow);
|
||||||
|
},
|
||||||
|
function listAccount(account, row) {
|
||||||
|
const nameCell = document.createElement('td');
|
||||||
|
nameCell.textContent = account.name;
|
||||||
|
row.appendChild(nameCell);
|
||||||
|
|
||||||
|
const emailCell = document.createElement('td');
|
||||||
|
emailCell.textContent = account.email;
|
||||||
|
row.appendChild(emailCell);
|
||||||
|
|
||||||
|
const spacerCell = document.createElement('td');
|
||||||
|
row.appendChild(spacerCell);
|
||||||
|
|
||||||
|
const adminCell = document.createElement('td');
|
||||||
|
adminCell.textContent = account.isAdmin;
|
||||||
|
row.appendChild(adminCell);
|
||||||
|
},
|
||||||
|
async function addAccount() {
|
||||||
|
window.location.href = "/manage/account";
|
||||||
|
},
|
||||||
|
async function editAccount(id) {
|
||||||
|
window.location.href = `/manage/account?account=${id}`;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function listItems(category) {
|
||||||
|
loadingSpan.textContent = "Loading...";
|
||||||
|
|
||||||
|
itemsListTable.innerHTML = "";
|
||||||
|
|
||||||
|
const itemsList = await category.getItems();
|
||||||
|
|
||||||
|
await category.listHeaders();
|
||||||
|
|
||||||
|
itemsList.forEach(item => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
|
category.listItem(item, row);
|
||||||
|
|
||||||
|
const manageCell = document.createElement('td');
|
||||||
|
|
||||||
|
const editSpan = document.createElement('span');
|
||||||
|
const editButton = document.createElement('button');
|
||||||
|
editButton.textContent = "✎";
|
||||||
|
editButton.style['font-size'] = '1.25em';
|
||||||
|
editButton.addEventListener('click', () => {
|
||||||
|
CATEGORIES[categoryDropdown.selectedIndex].editItem(item.id);
|
||||||
|
});
|
||||||
|
editSpan.appendChild(editButton);
|
||||||
|
manageCell.appendChild(editSpan);
|
||||||
|
|
||||||
|
row.appendChild(manageCell);
|
||||||
|
|
||||||
|
itemsListTable.appendChild(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadingSpan.textContent = '';
|
||||||
|
}
|
||||||
|
if(window.location.hash) {
|
||||||
|
let correctIndex;
|
||||||
|
let index = 0;
|
||||||
|
CATEGORIES.forEach(category => {
|
||||||
|
if(window.location.hash == '#' + category.name) correctIndex = index;
|
||||||
|
index++;
|
||||||
|
})
|
||||||
|
if(correctIndex || correctIndex === 0) categoryDropdown.selectedIndex = correctIndex;
|
||||||
|
}
|
||||||
|
listItems(CATEGORIES[categoryDropdown.selectedIndex]);
|
||||||
|
|
||||||
|
|
||||||
|
categoryDropdown.onchange = () => {
|
||||||
|
listItems(CATEGORIES[categoryDropdown.selectedIndex]);
|
||||||
|
};
|
||||||
|
addNewButton.addEventListener('click', () => CATEGORIES[categoryDropdown.selectedIndex].addItem());
|
|
@ -0,0 +1,67 @@
|
||||||
|
import * as Data from "../data.js";
|
||||||
|
import * as Form from "../form.js";
|
||||||
|
|
||||||
|
const submissionForm = document.getElementById('submission-form');
|
||||||
|
const nameTextbox = document.getElementById('name-textbox');
|
||||||
|
const emailTextbox = document.getElementById('email-textbox');
|
||||||
|
const passwordTextbox = document.getElementById('password-textbox');
|
||||||
|
const adminCheckboxSection = document.getElementById('admin-checkbox-section');
|
||||||
|
const adminCheckbox = document.getElementById('admin-checkbox');
|
||||||
|
const submitButton = document.getElementById('submit-button');
|
||||||
|
const deleteButton = document.getElementById('delete-button');
|
||||||
|
const loadingSpan = document.getElementById('loading-message');
|
||||||
|
|
||||||
|
async function Initialize() {
|
||||||
|
let params = new URLSearchParams(location.search);
|
||||||
|
let accountID = params.get('account') || (document.getElementById('account-id') ? document.getElementById('account-id').value : null);
|
||||||
|
if(accountID) {
|
||||||
|
const account = await Data.getAccount(accountID);
|
||||||
|
|
||||||
|
nameTextbox.value = account.name;
|
||||||
|
|
||||||
|
emailTextbox.value = account.email;
|
||||||
|
|
||||||
|
passwordTextbox.placeholder = "leave unchanged";
|
||||||
|
|
||||||
|
adminCheckbox.checked = account.isAdmin;
|
||||||
|
|
||||||
|
if(!document.getElementById('account-id')) {
|
||||||
|
adminCheckboxSection.style.visibility = "visible";
|
||||||
|
adminCheckbox.disabled = false;
|
||||||
|
|
||||||
|
Form.addHiddenValue('account', accountID, submissionForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteButton.style.visibility = "visible";
|
||||||
|
deleteButton.disabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
adminCheckboxSection.style.visibility = "visible";
|
||||||
|
adminCheckbox.disabled = false;
|
||||||
|
}
|
||||||
|
nameTextbox.disabled = false;
|
||||||
|
nameTextbox.addEventListener('keyup', checkDataValidity);
|
||||||
|
emailTextbox.disabled = false;
|
||||||
|
emailTextbox.addEventListener('keyup', checkDataValidity);
|
||||||
|
passwordTextbox.disabled = false;
|
||||||
|
passwordTextbox.addEventListener('keyup', checkDataValidity);
|
||||||
|
checkDataValidity();
|
||||||
|
|
||||||
|
loadingSpan.textContent = '';
|
||||||
|
submissionForm.style.visibility = 'visible';
|
||||||
|
}
|
||||||
|
Initialize();
|
||||||
|
|
||||||
|
async function checkDataValidity() {
|
||||||
|
let dataIsValid = true;
|
||||||
|
|
||||||
|
if(!passwordTextbox.value && !passwordTextbox.placeholder) dataIsValid = false;
|
||||||
|
if(!nameTextbox.value) dataIsValid = false;
|
||||||
|
if(!emailTextbox.value) dataIsValid = false;
|
||||||
|
|
||||||
|
if(dataIsValid) submitButton.disabled = false;
|
||||||
|
else submitButton.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Form.addRemoveFunction(deleteButton, submissionForm, "account");
|
|
@ -0,0 +1,70 @@
|
||||||
|
import * as Data from "../data.js";
|
||||||
|
import * as Form from "../form.js";
|
||||||
|
|
||||||
|
|
||||||
|
const submissionForm = document.getElementById('submission-form');
|
||||||
|
const sportDropdown = document.getElementById('sport-dropdown');
|
||||||
|
const genderDropdown = document.getElementById('gender-dropdown');
|
||||||
|
const nameTextbox = document.getElementById('name-textbox');
|
||||||
|
const submitButton = document.getElementById('submit-button');
|
||||||
|
const deleteButton = document.getElementById('delete-button');
|
||||||
|
const loadingSpan = document.getElementById('loading-message');
|
||||||
|
|
||||||
|
|
||||||
|
async function initializeForm() {
|
||||||
|
let params = new URLSearchParams(location.search);
|
||||||
|
let divisionID = params.get('division');
|
||||||
|
if(divisionID) {
|
||||||
|
const division = await (await fetch(`/fetch/manage/division?division=${divisionID}`)).json();
|
||||||
|
|
||||||
|
nameTextbox.value = division.name;
|
||||||
|
|
||||||
|
deleteButton.style.visibility = "visible";
|
||||||
|
deleteButton.disabled = false;
|
||||||
|
|
||||||
|
const gender = division.gender.name;
|
||||||
|
|
||||||
|
if(gender == 'female') genderDropdown.selectedIndex = 1;
|
||||||
|
else genderDropdown.selectedIndex = 2;
|
||||||
|
|
||||||
|
let data = {};
|
||||||
|
data.sports = [division.sport];
|
||||||
|
data.latestGame = {sportID : division.sportID };
|
||||||
|
|
||||||
|
Form.populateSports(sportDropdown, null, data);
|
||||||
|
|
||||||
|
Form.addHiddenValue('division', divisionID, submissionForm);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Form.populateSports(sportDropdown);
|
||||||
|
|
||||||
|
genderDropdown.disabled = false;
|
||||||
|
|
||||||
|
sportDropdown.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameTextbox.disabled = false;
|
||||||
|
nameTextbox.addEventListener('keyup', checkDataValidity);
|
||||||
|
|
||||||
|
loadingSpan.textContent = '';
|
||||||
|
submissionForm.style.visibility = 'visible';
|
||||||
|
}
|
||||||
|
initializeForm();
|
||||||
|
|
||||||
|
async function checkDataValidity() {
|
||||||
|
let dataIsValid = true;
|
||||||
|
|
||||||
|
if(!nameTextbox.value) dataIsValid = false;
|
||||||
|
|
||||||
|
const sportHasContent = sportDropdown.options.length;
|
||||||
|
const genderHasContent = genderDropdown.options.length;
|
||||||
|
|
||||||
|
if(!sportHasContent || !genderHasContent) dataIsValid = false;
|
||||||
|
|
||||||
|
|
||||||
|
if(dataIsValid) submitButton.disabled = false;
|
||||||
|
else submitButton.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Form.addRemoveFunction(deleteButton, submissionForm, "division");
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
import * as Data from "./../data.js";
|
||||||
|
import * as Form from "./../form.js";
|
||||||
|
|
||||||
|
|
||||||
|
const submissionForm = document.getElementById('submission-form');
|
||||||
|
const sportDropdown = document.getElementById('sport-dropdown');
|
||||||
|
const seasonDropdown = document.getElementById('year-dropdown');
|
||||||
|
const genderDropdown = document.getElementById('gender-dropdown');
|
||||||
|
const divisionDropdown = document.getElementById('division-dropdown');
|
||||||
|
const dateInput = document.getElementById('date-input');
|
||||||
|
const team1Dropdown = document.getElementById('team1-dropdown');
|
||||||
|
const team2Dropdown = document.getElementById('team2-dropdown');
|
||||||
|
const team1ScoreTextbox = document.getElementById('team1-score-textbox');
|
||||||
|
const team2ScoreTextbox = document.getElementById('team2-score-textbox');
|
||||||
|
const nameTextbox = document.getElementById('name-textbox');
|
||||||
|
const submitButton = document.getElementById('submit-button');
|
||||||
|
const deleteButton = document.getElementById('delete-button');
|
||||||
|
const loadingSpan = document.getElementById('loading-span');
|
||||||
|
|
||||||
|
|
||||||
|
async function initializeForm() {
|
||||||
|
let params = new URLSearchParams(location.search);
|
||||||
|
let gameID = params.get('game');
|
||||||
|
if(gameID) {
|
||||||
|
deleteButton.style.visibility = "visible";
|
||||||
|
deleteButton.disabled = false;
|
||||||
|
|
||||||
|
const game = await Data.getGame(gameID);
|
||||||
|
|
||||||
|
Form.addHiddenValue('game', gameID, submissionForm);
|
||||||
|
|
||||||
|
Form.populateSeasons(seasonDropdown, game.seasonID);
|
||||||
|
const data = await Data.getDivision(game.divisionID)
|
||||||
|
await Form.populateSports(sportDropdown, data.sportID)
|
||||||
|
await Form.populateGenders(genderDropdown, sportDropdown.value, data.gender.name)
|
||||||
|
await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value, game.divisionID);
|
||||||
|
await Form.populateTeams(team1Dropdown, sportDropdown.value, game.team1ID);
|
||||||
|
await Form.populateTeams(team2Dropdown, sportDropdown.value, game.team2ID);
|
||||||
|
|
||||||
|
dateInput.value = game.date;
|
||||||
|
team1ScoreTextbox.value = game.team1Score;
|
||||||
|
team2ScoreTextbox.value = game.team2Score;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/*try {*/
|
||||||
|
const data = await (await fetch(`/fetch/index/dropdown`)).json();
|
||||||
|
|
||||||
|
await Form.populateSeasons(seasonDropdown, null, data);
|
||||||
|
await Form.populateSports(sportDropdown, null, data);
|
||||||
|
await Form.populateGenders(genderDropdown, null, null, data);
|
||||||
|
await Form.populateDivisions(divisionDropdown, null, null, null, data);
|
||||||
|
await Form.populateTeams(team1Dropdown, null, null, data);
|
||||||
|
await Form.populateTeams(team2Dropdown, null, null, data, true);
|
||||||
|
/*} catch {
|
||||||
|
await Form.populateSeasons(seasonDropdown);
|
||||||
|
await Form.populateSports(sportDropdown)
|
||||||
|
await Form.populateGenders(genderDropdown, sportDropdown.value)
|
||||||
|
await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value);
|
||||||
|
await Form.populateTeams(team1Dropdown, sportDropdown.value);
|
||||||
|
await Form.populateTeams(team2Dropdown, sportDropdown.value);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
dateInput.value = (new Date()).toISOString().slice(0,10);
|
||||||
|
}
|
||||||
|
seasonDropdown.disabled = false;
|
||||||
|
sportDropdown.disabled = false;
|
||||||
|
genderDropdown.disabled = false;
|
||||||
|
divisionDropdown.disabled = false;
|
||||||
|
dateInput.disabled = false;
|
||||||
|
team1Dropdown.disabled = false;
|
||||||
|
team2Dropdown.disabled = false;
|
||||||
|
team1ScoreTextbox.disabled = false;
|
||||||
|
team2ScoreTextbox.disabled = false;
|
||||||
|
if(nameTextbox) {
|
||||||
|
nameTextbox.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sportDropdown.onchange = async () => {
|
||||||
|
await Form.populateGenders(genderDropdown, sportDropdown.value)
|
||||||
|
await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value);
|
||||||
|
await Form.populateTeams(team1Dropdown, sportDropdown.value);
|
||||||
|
await Form.populateTeams(team2Dropdown, sportDropdown.value);
|
||||||
|
checkDataValidity();
|
||||||
|
};
|
||||||
|
|
||||||
|
genderDropdown.onchange = async () => {
|
||||||
|
await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value);
|
||||||
|
checkDataValidity();
|
||||||
|
};
|
||||||
|
|
||||||
|
dateInput.addEventListener('keyup', checkDataValidity);
|
||||||
|
team1Dropdown.onchange = checkDataValidity;
|
||||||
|
team1ScoreTextbox.addEventListener('keyup', checkDataValidity);
|
||||||
|
team2Dropdown.onchange = checkDataValidity;
|
||||||
|
team2ScoreTextbox.addEventListener('keyup', checkDataValidity);
|
||||||
|
if(nameTextbox) nameTextbox.addEventListener('keyup', checkDataValidity);
|
||||||
|
|
||||||
|
loadingSpan.textContent = '';
|
||||||
|
submissionForm.style.visibility = 'visible';
|
||||||
|
|
||||||
|
checkDataValidity();
|
||||||
|
}
|
||||||
|
initializeForm();
|
||||||
|
|
||||||
|
async function checkDataValidity() {
|
||||||
|
let dataIsValid = true;
|
||||||
|
|
||||||
|
const seasonHasContent = seasonDropdown.options.length;
|
||||||
|
const sportHasContent = sportDropdown.options.length;
|
||||||
|
const genderHasContent = genderDropdown.options.length;
|
||||||
|
const divisionHasContent = divisionDropdown.options.length;
|
||||||
|
const team1HasContent = team1Dropdown.options.length;
|
||||||
|
const team2HasContent = team2Dropdown.options.length;
|
||||||
|
|
||||||
|
if(!seasonHasContent || !sportHasContent || !genderHasContent || !divisionHasContent || !team1HasContent || !team2HasContent) dataIsValid = false;
|
||||||
|
|
||||||
|
if(team1Dropdown.selectedIndex == team2Dropdown.selectedIndex) dataIsValid = false;
|
||||||
|
|
||||||
|
if(team1ScoreTextbox.value == "" || team2ScoreTextbox.value == "") dataIsValid = false;
|
||||||
|
|
||||||
|
if(dateInput.value == "") dataIsValid = false;
|
||||||
|
|
||||||
|
if(nameTextbox && nameTextbox.value == "") dataIsValid = false;
|
||||||
|
|
||||||
|
submitButton.disabled = !dataIsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
Form.addRemoveFunction(deleteButton, submissionForm, "game");
|
|
@ -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';
|
||||||
|
});
|
|
@ -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);
|
|
@ -0,0 +1,59 @@
|
||||||
|
import * as Data from "../data.js";
|
||||||
|
|
||||||
|
|
||||||
|
const nameTextbox = document.getElementById('name-textbox');
|
||||||
|
const submitButton = document.getElementById('submit-button');
|
||||||
|
const deleteButton = document.getElementById('delete-button');
|
||||||
|
const submissionForm = document.getElementById('submission-form');
|
||||||
|
const loadingSpan = document.getElementById('loading-message');
|
||||||
|
|
||||||
|
|
||||||
|
async function initializeForm() {
|
||||||
|
let params = new URLSearchParams(location.search);
|
||||||
|
let sportID = params.get('sport')
|
||||||
|
if(sportID) {
|
||||||
|
const sportName = await Data.getSportName(sportID);
|
||||||
|
|
||||||
|
nameTextbox.value = sportName;
|
||||||
|
deleteButton.style.visibility = "visible";
|
||||||
|
deleteButton.disabled = false;
|
||||||
|
|
||||||
|
const sportIDInput = document.createElement('input');
|
||||||
|
sportIDInput.setAttribute('name', 'sport');
|
||||||
|
sportIDInput.setAttribute('value', sportID);
|
||||||
|
sportIDInput.setAttribute('type', 'hidden');
|
||||||
|
submissionForm.appendChild(sportIDInput);
|
||||||
|
}
|
||||||
|
nameTextbox.disabled = false;
|
||||||
|
|
||||||
|
nameTextbox.addEventListener('keyup', checkDataValidity);
|
||||||
|
|
||||||
|
loadingSpan.textContent = '';
|
||||||
|
submissionForm.style.visibility = 'visible';
|
||||||
|
}
|
||||||
|
initializeForm();
|
||||||
|
|
||||||
|
async function checkDataValidity() {
|
||||||
|
let dataIsValid = true;
|
||||||
|
|
||||||
|
if(!nameTextbox.value) dataIsValid = false;
|
||||||
|
|
||||||
|
|
||||||
|
if(dataIsValid) submitButton.disabled = false;
|
||||||
|
else submitButton.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeSport() {
|
||||||
|
const removeInput = document.createElement('input');
|
||||||
|
removeInput.setAttribute('name', 'remove');
|
||||||
|
removeInput.setAttribute('value', 1);
|
||||||
|
removeInput.setAttribute('type', 'hidden');
|
||||||
|
submissionForm.appendChild(removeInput);
|
||||||
|
submissionForm.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteButton.addEventListener('click', () => {
|
||||||
|
const verified = confirm("This sport will be removed.");
|
||||||
|
|
||||||
|
if(verified) removeSport();
|
||||||
|
});
|
|
@ -0,0 +1,57 @@
|
||||||
|
import * as Data from "../data.js";
|
||||||
|
import * as Form from "../form.js";
|
||||||
|
|
||||||
|
|
||||||
|
const submissionForm = document.getElementById('submission-form');
|
||||||
|
const sportDropdown = document.getElementById('sport-dropdown');
|
||||||
|
const nameTextbox = document.getElementById('name-textbox');
|
||||||
|
const submitButton = document.getElementById('submit-button');
|
||||||
|
const deleteButton = document.getElementById('delete-button');
|
||||||
|
const loadingSpan = document.getElementById('loading-message');
|
||||||
|
|
||||||
|
|
||||||
|
async function initializeForm() {
|
||||||
|
let params = new URLSearchParams(location.search);
|
||||||
|
let teamID = params.get('team');
|
||||||
|
if(teamID) {
|
||||||
|
const team = await (await fetch(`/fetch/manage/team?team=${teamID}`)).json();
|
||||||
|
|
||||||
|
nameTextbox.value = team.name;
|
||||||
|
|
||||||
|
deleteButton.style.visibility = "visible";
|
||||||
|
deleteButton.disabled = false;
|
||||||
|
|
||||||
|
let data = {};
|
||||||
|
data.sports = [team.sport];
|
||||||
|
data.latestGame = {sportID : team.sportID };
|
||||||
|
|
||||||
|
Form.populateSports(sportDropdown, null, data);
|
||||||
|
|
||||||
|
Form.addHiddenValue('team', teamID, submissionForm);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sportDropdown.disabled = false;
|
||||||
|
|
||||||
|
Form.populateSports(sportDropdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
nameTextbox.disabled = false;
|
||||||
|
nameTextbox.addEventListener('keyup', checkDataValidity);
|
||||||
|
|
||||||
|
loadingSpan.textContent = '';
|
||||||
|
submissionForm.style.visibility = 'visible';
|
||||||
|
}
|
||||||
|
initializeForm();
|
||||||
|
|
||||||
|
async function checkDataValidity() {
|
||||||
|
let dataIsValid = true;
|
||||||
|
|
||||||
|
if(!nameTextbox.value) dataIsValid = false;
|
||||||
|
|
||||||
|
const sportHasContent = sportDropdown.options.length;
|
||||||
|
if(!sportHasContent) dataIsValid = false;
|
||||||
|
|
||||||
|
submitButton.disabled = !dataIsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
Form.addRemoveFunction(deleteButton, submissionForm, "team");
|
|
@ -0,0 +1,3 @@
|
||||||
|
#mobile-view {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.form-main-dropdown {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section-input {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-score-input{
|
||||||
|
width: 35%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submit-button {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#delete-button {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section-checkbox {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-checkbox {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flat-form-section {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submission-form {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
h1 {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#score-column {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
#opponent-column{
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
#date-column {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
height: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header-div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#add-score-button {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-button {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dropdowns-div {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer-column {
|
||||||
|
width: 100%;
|
||||||
|
}
|
|
@ -1,8 +1,71 @@
|
||||||
body {
|
body {
|
||||||
padding: 50px;
|
padding: 1em;
|
||||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#about-footer {
|
||||||
|
margin-top: 3em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: black;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-checkbox-section {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
|
|
||||||
/* GET users listing. */
|
|
||||||
router.get('/', function(req, res, next) {
|
router.get('/', function(req, res, next) {
|
||||||
res.send('respond with a resource');
|
res.render('about', { title: 'About Score Tracker', hideHomeButton: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1,167 @@
|
||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
var sports = require('../database/scores/sports');
|
||||||
|
var seasons = require('../database/scores/seasons');
|
||||||
|
var genders = require('../database/scores/genders');
|
||||||
|
var divisions = require('../database/scores/divisions');
|
||||||
|
var teams = require('../database/scores/teams');
|
||||||
|
var games = require('../database/scores/games');
|
||||||
|
var accounts = require('../database/accounts/accounts');
|
||||||
|
|
||||||
|
var checkLoginStatus = require('./checkLoginStatus');
|
||||||
|
|
||||||
|
router.get('/sports', async function(req, res, next) {
|
||||||
|
try {
|
||||||
|
const data = await sports.retrieveAll();
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/sport', async function(req, res, next) {
|
||||||
|
try {
|
||||||
|
const sportID = req.query.sport;
|
||||||
|
const data = await sports.getFromID(sportID);
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/seasons', async function(req, res, next) {
|
||||||
|
try {
|
||||||
|
const data = await seasons.retrieveAll();
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/genders', async function(req, res, next) {
|
||||||
|
try {
|
||||||
|
const sportID = req.query.sport;
|
||||||
|
const data = await genders.retrieveBySport(sportID);
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/divisions', async function(req, res, next) {
|
||||||
|
try{
|
||||||
|
let gender;
|
||||||
|
if(req.query.gender) gender = (req.query.gender == 'female' ? genders.FEMALE : genders.MALE);
|
||||||
|
|
||||||
|
const sportID = req.query.sport;
|
||||||
|
const data = await divisions.retrieve(sportID, gender);
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/division', async function(req, res, next) {
|
||||||
|
try {
|
||||||
|
const divisionID = req.query.division;
|
||||||
|
const data = await divisions.getFromID(divisionID);
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/teams', async function(req, res, next) {
|
||||||
|
try {
|
||||||
|
const sportID = req.query.sport;
|
||||||
|
const data = await teams.retrieve(sportID);
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/team', async function(req, res, next) {
|
||||||
|
try {
|
||||||
|
const teamID = req.query.team;
|
||||||
|
const data = await teams.getFromID(teamID);
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/games', async function(req, res, next) {
|
||||||
|
try {
|
||||||
|
const userID = req.user ? req.user[0] : null;
|
||||||
|
if(req.query.user) {
|
||||||
|
const data = await games.retrieveByUser(userID);
|
||||||
|
res.json(data);
|
||||||
|
} else {
|
||||||
|
const seasonID = req.query.season;
|
||||||
|
const divisionID = req.query.division;
|
||||||
|
const teamID = req.query.team;
|
||||||
|
const data = await games.retrieve(teamID, divisionID, seasonID);
|
||||||
|
res.json(data);
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/game', async function(req, res, next) {
|
||||||
|
try {
|
||||||
|
const gameID = req.query.game;
|
||||||
|
const ofUser = req.query.ofuser;
|
||||||
|
const currentUserID = req.user ? req.user[0] : null;
|
||||||
|
|
||||||
|
let data;
|
||||||
|
if(gameID) data = await games.getFromID(gameID);
|
||||||
|
else if(ofUser) data = await games.getLatest(currentUserID);
|
||||||
|
else data = await games.getLatest();
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/accounts', checkLoginStatus.admin, async function(req, res, next) {
|
||||||
|
try {
|
||||||
|
const data = await accounts.retrieveAll();
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/account', checkLoginStatus.user, async function(req, res, next) {
|
||||||
|
try{
|
||||||
|
const userIsAdmin = req.user[2];
|
||||||
|
const loggedInAccountID = req.user[0];
|
||||||
|
const requestedAccountID = req.query.account;
|
||||||
|
|
||||||
|
if(!userIsAdmin && loggedInAccountID != requestedAccountID) {
|
||||||
|
res.status(403).send("ACCESS DENIED");
|
||||||
|
} else {
|
||||||
|
const data = await accounts.getFromID(req.query.account);
|
||||||
|
res.json(data);
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = router;
|
|
@ -0,0 +1,200 @@
|
||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
var sports = require('../database/scores/sports');
|
||||||
|
var seasons = require('../database/scores/seasons');
|
||||||
|
var genders = require('../database/scores/genders');
|
||||||
|
var divisions = require('../database/scores/divisions');
|
||||||
|
var teams = require('../database/scores/teams');
|
||||||
|
var games = require('../database/scores/games');
|
||||||
|
var accounts = require('../database/accounts/accounts');
|
||||||
|
|
||||||
|
var checkLoginStatus = require('./checkLoginStatus');
|
||||||
|
|
||||||
|
router.get('/index/dropdown', async function(req, res, next) {
|
||||||
|
let latestGame;
|
||||||
|
let seasonsData;
|
||||||
|
let sportsData;
|
||||||
|
let gendersData;
|
||||||
|
let divisionsData;
|
||||||
|
let teamsData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
latestGame = await games.getLatest();
|
||||||
|
|
||||||
|
let division = await divisions.getFromID(latestGame.divisionID);
|
||||||
|
latestGame.sportID = division.sportID;
|
||||||
|
latestGame.gender = division.gender;
|
||||||
|
} catch {
|
||||||
|
latestGame = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(latestGame) {
|
||||||
|
seasonsData = await seasons.retrieveAll();
|
||||||
|
sportsData = await sports.retrieveAll();
|
||||||
|
gendersData = await genders.retrieveBySport(latestGame.sportID);
|
||||||
|
divisionsData = await divisions.retrieve(latestGame.sportID);
|
||||||
|
teamsData = await teams.retrieve(latestGame.sportID);
|
||||||
|
} else {
|
||||||
|
seasonsData = await seasons.retrieveAll();
|
||||||
|
sportsData = await sports.retrieveAll();
|
||||||
|
gendersData = await genders.retrieveBySport(sportsData[0].id);
|
||||||
|
divisionsData = await divisions.retrieve(sportsData[0].id);
|
||||||
|
teamsData = await teams.retrieve(sportsData[0].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
seasons : seasonsData,
|
||||||
|
sports : sportsData,
|
||||||
|
genders : gendersData,
|
||||||
|
divisions : divisionsData,
|
||||||
|
teams: teamsData,
|
||||||
|
latestGame
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/index/scores', async function (req, res, next) {
|
||||||
|
try {
|
||||||
|
const seasonID = req.query.season;
|
||||||
|
const divisionID = req.query.division;
|
||||||
|
const teamID = req.query.team;
|
||||||
|
const data = await games.retrieve(teamID, divisionID, seasonID);
|
||||||
|
for (const game of data) {
|
||||||
|
game.opponent = await teams.getFromID(game.team2ID);
|
||||||
|
}
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.get('/submit', async function(req, res, next) {
|
||||||
|
let latestGame;
|
||||||
|
let seasonsData;
|
||||||
|
let sportsData;
|
||||||
|
let gendersData;
|
||||||
|
let divisionsData;
|
||||||
|
let teamsData;
|
||||||
|
|
||||||
|
const userID = req.user ? req.user[0] : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
latestGame = await games.getLatest(userID);
|
||||||
|
|
||||||
|
let division = await divisions.getFromID(latestGame.divisionID);
|
||||||
|
latestGame.sportID = division.sportID;
|
||||||
|
latestGame.gender = division.gender;
|
||||||
|
} catch {
|
||||||
|
latestGame = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(latestGame) {
|
||||||
|
seasonsData = await seasons.retrieveAll();
|
||||||
|
sportsData = await sports.retrieveAll();
|
||||||
|
gendersData = await genders.retrieveBySport(latestGame.sportID);
|
||||||
|
divisionsData = await divisions.retrieve(latestGame.sportID);
|
||||||
|
teamsData = await teams.retrieve(latestGame.sportID);
|
||||||
|
} else {
|
||||||
|
seasonsData = await seasons.retrieveAll();
|
||||||
|
sportsData = await sports.retrieveAll();
|
||||||
|
gendersData = await genders.retrieveBySport(sportsData[0].id);
|
||||||
|
divisionsData = await divisions.retrieve(sportsData[0].id);
|
||||||
|
teamsData = await teams.retrieve(sportsData[0].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
seasons : seasonsData,
|
||||||
|
sports : sportsData,
|
||||||
|
genders : gendersData,
|
||||||
|
divisions : divisionsData,
|
||||||
|
teams: teamsData,
|
||||||
|
latestGame
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/manage/divisions', async function (req, res, next) {
|
||||||
|
const data = await divisions.retrieve();
|
||||||
|
|
||||||
|
for(const division of data) {
|
||||||
|
division.sport = await sports.getFromID(division.sportID);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/manage/division', async function (req, res, next) {
|
||||||
|
try {
|
||||||
|
const divisionID = req.query.division;
|
||||||
|
const data = await divisions.getFromID(divisionID);
|
||||||
|
data.sport = await sports.getFromID(data.sportID);
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/manage/teams', async function (req, res, next) {
|
||||||
|
const data = await teams.retrieve();
|
||||||
|
|
||||||
|
for(const team of data) {
|
||||||
|
team.sport = await sports.getFromID(team.sportID);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/manage/team', async function (req, res, next) {
|
||||||
|
try {
|
||||||
|
const teamID = req.query.team;
|
||||||
|
const data = await teams.getFromID(teamID);
|
||||||
|
data.sport = await sports.getFromID(data.sportID);
|
||||||
|
res.json(data);
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/manage/games', checkLoginStatus.user, async function (req, res, next) {
|
||||||
|
try{
|
||||||
|
const userIsAdmin = req.user[2];
|
||||||
|
const loggedInAccountID = req.user[0];
|
||||||
|
|
||||||
|
if(!userIsAdmin) {
|
||||||
|
res.status(403).send("ACCESS DENIED");
|
||||||
|
} else {
|
||||||
|
const data = await games.retrieve();
|
||||||
|
|
||||||
|
for(const game of data) {
|
||||||
|
game.team1 = await teams.getFromID(game.team1ID);
|
||||||
|
game.team2 = await teams.getFromID(game.team2ID);
|
||||||
|
game.division = await divisions.getFromID(game.divisionID);
|
||||||
|
game.sport = await sports.getFromID(game.division.sportID);
|
||||||
|
game.submitter = game.submitterName || (await accounts.getFromID(game.submitterID));
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(data);
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
res.status(500).send("An error has occurred");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
|
@ -1,9 +1,12 @@
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
|
|
||||||
/* GET home page. */
|
|
||||||
router.get('/', function(req, res, next) {
|
router.get('/', function(req, res, next) {
|
||||||
res.render('index', { title: 'Express' });
|
res.render('index', { title: 'View Scores', userLoggedIn: !!req.user, hideHomeButton: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/submit', function(req, res, next) {
|
||||||
|
res.redirect('/manage/game');
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
var genders = require('../database/scores/genders');
|
||||||
|
var games = require('../database/scores/games');
|
||||||
|
var seasons = require('../database/scores/seasons');
|
||||||
|
var sports = require('../database/scores/sports');
|
||||||
|
var divisions = require('../database/scores/divisions');
|
||||||
|
var genders = require('../database/scores/genders');
|
||||||
|
var teams = require('../database/scores/teams');
|
||||||
|
var accounts = require('../database/accounts/accounts');
|
||||||
|
|
||||||
|
var checkLoginStatus = require('./checkLoginStatus');
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'testing') {
|
||||||
|
require('dotenv').config();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
router.get('/' ,checkLoginStatus.user, function(req, res, next) {
|
||||||
|
if(req.user[2]) res.render('manage', { title: 'Management Panel', userLoggedIn: !!req.user });
|
||||||
|
else res.render('manage/manage-nonadmin', { title: "My Games", userLoggedIn: !!req.user });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/game', function(req, res, next) {
|
||||||
|
if(!(process.env.PUBLIC_SUBMIT_PAGE && process.env.PUBLIC_SUBMIT_PAGE.toLowerCase() == 'true')) {
|
||||||
|
if (req.user) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.redirect('/auth/login');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(req, res, next) {
|
||||||
|
let title = req.query.game ? 'Edit Game' : 'Submit Score';
|
||||||
|
|
||||||
|
res.render('manage/addgame', { title, userLoggedIn: !!req.user, message: req.flash('error') });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/game', function(req, res, next) {
|
||||||
|
if(!(process.env.PUBLIC_SUBMIT_PAGE && process.env.PUBLIC_SUBMIT_PAGE.toLowerCase() == 'true')) {
|
||||||
|
if (req.user) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.redirect('/auth/login');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async function(req, res, next) {
|
||||||
|
const id = req.body['game'];
|
||||||
|
const remove = req.body['remove'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const seasonID = req.body['year'];
|
||||||
|
const sportID = req.body['sport'];
|
||||||
|
const gender = (req.body['gender'] == "female") ? genders.FEMALE : genders.MALE;
|
||||||
|
const divisionID = req.body['division'];
|
||||||
|
const date = req.body['date'];
|
||||||
|
const team1ID = req.body['team1'];
|
||||||
|
const team1Score = req.body['team1-score'];
|
||||||
|
const team2ID = req.body['team2'];
|
||||||
|
const team2Score = req.body['team2-score'];
|
||||||
|
const submitterName = req.body['name'];
|
||||||
|
|
||||||
|
let submitterID;
|
||||||
|
let loggedInUserID;
|
||||||
|
let loggedInUserIsAdmin;
|
||||||
|
if(req.user) {
|
||||||
|
submitterID = req.user[0];
|
||||||
|
loggedInUserID = req.user[0];
|
||||||
|
loggedInUserIsAdmin = req.user[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
const game = id ? await games.getFromID(id) : null;
|
||||||
|
|
||||||
|
if((!loggedInUserIsAdmin && game && loggedInUserID != game.submitterID) || (!req.user && game)) {
|
||||||
|
res.status(403).send("ACCESS DENIED");
|
||||||
|
}
|
||||||
|
else if(remove) {
|
||||||
|
await games.remove(id);
|
||||||
|
res.redirect('/manage#games');
|
||||||
|
}
|
||||||
|
else if(id) {
|
||||||
|
await games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score);
|
||||||
|
res.redirect('/manage#games');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, submitterID, submitterName);
|
||||||
|
res.redirect('/');
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
req.flash("error", "An error has occurred.");
|
||||||
|
res.redirect('/manage/game' + (id ? `?game=${id}` : ''));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/season', checkLoginStatus.admin, function(req, res, next) {
|
||||||
|
res.render('manage/addseason', { title: 'Add Season', currentYear : (new Date()).getFullYear(), userLoggedIn: !!req.user, message: req.flash('error') });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/season', checkLoginStatus.admin, async function(req, res, next) {
|
||||||
|
const seasonID = req.body['season'];
|
||||||
|
const remove = req.body['remove'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const year = req.body['year'];
|
||||||
|
|
||||||
|
if(remove) await seasons.remove(seasonID);
|
||||||
|
else await seasons.add(year);
|
||||||
|
|
||||||
|
res.redirect('/manage#seasons');
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
req.flash("error", "An error has occurred.");
|
||||||
|
res.redirect('/manage/season' + (seasonID ? `?season=${seasonID}` : ''));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/sport', checkLoginStatus.admin, function(req, res, next) {
|
||||||
|
let title = req.query.sport ? 'Edit Sport' : 'Add Sport';
|
||||||
|
|
||||||
|
res.render('manage/addsport', { title, userLoggedIn: !!req.user, message: req.flash('error') });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/sport', checkLoginStatus.admin, async function(req, res, next) {
|
||||||
|
const id = req.body['sport'];
|
||||||
|
const remove = req.body['remove'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const name = req.body['name'];
|
||||||
|
|
||||||
|
if(remove) await sports.remove(id);
|
||||||
|
else if(id) await sports.rename(id, name);
|
||||||
|
else await sports.add(name);
|
||||||
|
|
||||||
|
res.redirect('/manage#sports');
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
req.flash("error", "An error has occurred.");
|
||||||
|
res.redirect('/manage/sport' + (id ? `?sport=${id}` : ''));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/division', checkLoginStatus.admin, function(req, res, next) {
|
||||||
|
let title = req.query.division ? 'Edit Division' : 'Add Division'
|
||||||
|
|
||||||
|
res.render('manage/adddivision', { title, userLoggedIn: !!req.user, message: req.flash('error') });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/division', checkLoginStatus.admin, async function(req, res, next) {
|
||||||
|
const id = req.body['division'];
|
||||||
|
const remove = req.body['remove'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const name = req.body['name'];
|
||||||
|
const sport = req.body['sport'];
|
||||||
|
const genderName = req.body['gender'];
|
||||||
|
|
||||||
|
if(remove) await divisions.remove(id);
|
||||||
|
else if(id) await divisions.rename(id, name);
|
||||||
|
else {
|
||||||
|
if(genderName == 'both') {
|
||||||
|
await divisions.add(name, genders.FEMALE, sport);
|
||||||
|
await divisions.add(name, genders.MALE, sport);
|
||||||
|
} else {
|
||||||
|
const gender = (genderName == "female") ? genders.FEMALE : genders.MALE;
|
||||||
|
await divisions.add(name, gender, sport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.redirect('/manage#divisions');
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
req.flash("error", "An error has occurred.");
|
||||||
|
res.redirect('/manage/division' + (id ? `?division=${id}` : ''));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/team', checkLoginStatus.admin, function(req, res, next) {
|
||||||
|
let title = req.query.team ? 'Edit Team' : 'Add Team'
|
||||||
|
|
||||||
|
res.render('manage/addteam', { title, userLoggedIn: !!req.user, message: req.flash('error') });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/team', checkLoginStatus.admin, async function(req, res, next) {
|
||||||
|
const id = req.body['team'];
|
||||||
|
const remove = req.body['remove'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const name = req.body['name'];
|
||||||
|
const sport = req.body['sport'];
|
||||||
|
|
||||||
|
if(remove) teams.remove(id).then(res.redirect('/manage#teams'));
|
||||||
|
else if(id) teams.rename(id, name).then(res.redirect('/manage#teams'));
|
||||||
|
else teams.add(name, sport).then(res.redirect('/manage#teams'));
|
||||||
|
} catch(err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
req.flash("error", "An error has occurred.");
|
||||||
|
res.redirect('/manage/team' + (id ? `?team=${id}` : ''));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/account', checkLoginStatus.user, (req, res, next) => {
|
||||||
|
const userIsAdmin = req.user[2];
|
||||||
|
const accountID = req.user[0];
|
||||||
|
|
||||||
|
if(userIsAdmin) {
|
||||||
|
let title = req.query.account ? 'Manage User' : 'Create User'
|
||||||
|
|
||||||
|
res.render('accounts/createuser', { title, userLoggedIn: !!req.user, message: req.flash('error') });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let title = 'Manage Account';
|
||||||
|
|
||||||
|
res.render('accounts/createuser', { title, accountID, userLoggedIn: !!req.user, message: req.flash('error') });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/account', checkLoginStatus.user, async function(req, res, next) {
|
||||||
|
const name = req.body.name;
|
||||||
|
const email = req.body.email;
|
||||||
|
const password = req.body.password;
|
||||||
|
|
||||||
|
const accountID = req.body.account;
|
||||||
|
const remove = req.body.remove;
|
||||||
|
|
||||||
|
const loggedInAccountIsAdmin = req.user[2];
|
||||||
|
const loggedInAccountID = req.user[0];
|
||||||
|
|
||||||
|
if(!loggedInAccountIsAdmin && accountID != loggedInAccountID) {
|
||||||
|
res.status(403).send("ACCESS DENIED");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
const isAdmin = loggedInAccountIsAdmin ? !!req.body.admin : false;
|
||||||
|
|
||||||
|
if(remove) await accounts.remove(accountID);
|
||||||
|
else if(accountID) await accounts.edit(accountID, email, password, isAdmin, name);
|
||||||
|
else await accounts.create(email, password, !!req.body.admin, name);
|
||||||
|
|
||||||
|
res.redirect('/manage#accounts');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error("ERROR: " + err.message);
|
||||||
|
req.flash("error", "An error has occurred.");
|
||||||
|
let URL = '/manage/account';
|
||||||
|
if(loggedInAccountIsAdmin && accountID) URL += `?account=${accountID}`;
|
||||||
|
res.redirect(URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
10
test/test.js
10
test/test.js
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
extends layout
|
||||||
|
|
||||||
|
block stylesheets
|
||||||
|
link(rel='stylesheet', href='/stylesheets/index.css')
|
||||||
|
|
||||||
|
block actions
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
block content
|
||||||
|
p Created by #[a(href="https://ethanreece.com") Ethan Reece], a student at #[a(href="https://colevalleychristian.org") Cole Valley Christian Schools].
|
||||||
|
p Need help? Found a bug? Email: #[a(href="mailto:scoretrackerhelp@ethanreece.com") scoretrackerhelp@ethanreece.com]
|
||||||
|
p #[a(href="https://gitlab.sudoer.ch/sudoer777/score-tracker") Git repo]
|
|
@ -0,0 +1,35 @@
|
||||||
|
extends ../layout
|
||||||
|
|
||||||
|
block stylesheets
|
||||||
|
link(rel='stylesheet', href='/stylesheets/submit.css')
|
||||||
|
link(rel='stylesheet', href='/stylesheets/form.css')
|
||||||
|
|
||||||
|
block content
|
||||||
|
span#loading-message Loading...
|
||||||
|
form#submission-form(action='/manage/account', method='POST')
|
||||||
|
if accountID
|
||||||
|
input#account-id(type="hidden" name="account" value=accountID)
|
||||||
|
span(class='form-section')
|
||||||
|
label Name
|
||||||
|
span(class='form-section-input')
|
||||||
|
input#name-textbox(type="text" name="name" disabled)
|
||||||
|
span(class='form-section')
|
||||||
|
label Email
|
||||||
|
span(class='form-section-input')
|
||||||
|
input#email-textbox(type="email" name="email" disabled)
|
||||||
|
span(class='form-section')
|
||||||
|
label Password
|
||||||
|
span(class='form-section-input' )
|
||||||
|
input#password-textbox(type="password" name="password" disabled)
|
||||||
|
span#admin-checkbox-section(class='form-section')
|
||||||
|
span(class='form-section-checkbox')
|
||||||
|
input#admin-checkbox(type="checkbox" name="admin" disabled)
|
||||||
|
label(for="admin-checkbox") Grant admin privileges
|
||||||
|
.error #{message}
|
||||||
|
span(class='form-section')
|
||||||
|
button#submit-button(type="submit" disabled) Submit
|
||||||
|
span(class='form-section')
|
||||||
|
button#delete-button(type="delete" disabled) Delete
|
||||||
|
|
||||||
|
block scripts
|
||||||
|
script(src='/scripts/manage/account.js' type="module")
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -1,5 +1,49 @@
|
||||||
extends layout
|
extends layout
|
||||||
|
|
||||||
|
block stylesheets
|
||||||
|
link(rel='stylesheet', href='/stylesheets/index.css')
|
||||||
|
link(rel='stylesheet', href='/stylesheets/form.css')
|
||||||
|
|
||||||
|
block actions
|
||||||
|
if userLoggedIn
|
||||||
|
button#add-score-button Submit score
|
||||||
|
button#manage-button Manage...
|
||||||
|
|
||||||
|
|
||||||
block content
|
block content
|
||||||
h1= title
|
span#loading Loading...
|
||||||
p Welcome to #{title}
|
div#dropdowns-div
|
||||||
|
span(class='form-section')
|
||||||
|
label Year
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#year-dropdown(name="year" class="form-main-dropdown")
|
||||||
|
span(class='form-section flat-form-section')
|
||||||
|
span
|
||||||
|
label Sport
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#sport-dropdown(name="sport" class="form-main-dropdown")
|
||||||
|
span
|
||||||
|
label Gender
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#gender-dropdown(name="gender")
|
||||||
|
span
|
||||||
|
label Division
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#division-dropdown(name="division")
|
||||||
|
span(class='form-section')
|
||||||
|
label Team
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#team-dropdown(name="team" class="form-main-dropdown")
|
||||||
|
div
|
||||||
|
h2#games-table-header
|
||||||
|
span#no-scores-message
|
||||||
|
table
|
||||||
|
colgroup
|
||||||
|
col#score-column(span="1")
|
||||||
|
col#opponent-column(span="1")
|
||||||
|
col#date-column(span="1")
|
||||||
|
tbody#games-table
|
||||||
|
|
||||||
|
|
||||||
|
block scripts
|
||||||
|
script(src='/scripts/index.js' type="module")
|
|
@ -1,7 +1,26 @@
|
||||||
doctype html
|
doctype html
|
||||||
html
|
html
|
||||||
head
|
head
|
||||||
title= title
|
title= title + ' - Score Tracker'
|
||||||
|
meta(name='viewport', content='width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1, user-scalable=no')
|
||||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||||
|
block stylesheets
|
||||||
body
|
body
|
||||||
|
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 content
|
||||||
|
div#about-footer
|
||||||
|
a(href="/about") Help/About
|
||||||
|
|
||||||
|
block scripts
|
||||||
|
script(src='/scripts/main.js' type="module")
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
extends layout
|
||||||
|
|
||||||
|
block stylesheets
|
||||||
|
link(rel='stylesheet', href='/stylesheets/manage.css')
|
||||||
|
link(rel='stylesheet', href='/stylesheets/form.css')
|
||||||
|
|
||||||
|
block content
|
||||||
|
div
|
||||||
|
span(class='form-section')
|
||||||
|
label Category
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#category-dropdown(name="category" class="form-main-dropdown")
|
||||||
|
option(value="seasons") Seasons
|
||||||
|
option(value="sports") Sports
|
||||||
|
option(value="divisions") Divisions
|
||||||
|
option(value="teams") Teams
|
||||||
|
option(value="games") Games
|
||||||
|
option(value="accounts") Accounts
|
||||||
|
div
|
||||||
|
h2#table-header
|
||||||
|
span#loading-message Loading...
|
||||||
|
table#items-list
|
||||||
|
button#add-new-button Add new...
|
||||||
|
|
||||||
|
block scripts
|
||||||
|
script(src='/scripts/manage.js' type="module")
|
|
@ -0,0 +1,32 @@
|
||||||
|
extends ../layout
|
||||||
|
|
||||||
|
block stylesheets
|
||||||
|
link(rel='stylesheet', href='/stylesheets/submit.css')
|
||||||
|
link(rel='stylesheet', href='/stylesheets/form.css')
|
||||||
|
|
||||||
|
block content
|
||||||
|
span#loading-message Loading...
|
||||||
|
form#submission-form(action='./division', method='POST')
|
||||||
|
span(class='form-section')
|
||||||
|
label Sport
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#sport-dropdown(name="sport" class="form-main-dropdown" disabled)
|
||||||
|
span(class='form-section')
|
||||||
|
label Gender
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#gender-dropdown(name="gender" class="form-main-dropdown" disabled)
|
||||||
|
option(value="both") Both
|
||||||
|
option(value="female") Female
|
||||||
|
option(value="male") Male
|
||||||
|
span(class='form-section')
|
||||||
|
label Division name
|
||||||
|
span(class='form-section-input')
|
||||||
|
input#name-textbox(type="text", name="name" disabled)
|
||||||
|
.error #{message}
|
||||||
|
span(class='form-section')
|
||||||
|
button#submit-button(type="submit" disabled) Submit
|
||||||
|
span(class='form-section')
|
||||||
|
button#delete-button(disabled) Delete
|
||||||
|
|
||||||
|
block scripts
|
||||||
|
script(src='/scripts/manage/division.js' type="module")
|
|
@ -0,0 +1,61 @@
|
||||||
|
extends ../layout
|
||||||
|
|
||||||
|
block stylesheets
|
||||||
|
link(rel='stylesheet', href='/stylesheets/submit.css')
|
||||||
|
link(rel='stylesheet', href='/stylesheets/form.css')
|
||||||
|
|
||||||
|
block content
|
||||||
|
span#loading-span Loading...
|
||||||
|
form#submission-form(action='./game', method='POST')
|
||||||
|
span(class='form-section')
|
||||||
|
label Year
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#year-dropdown(name="year" class="form-main-dropdown" disabled)
|
||||||
|
span(class='form-section flat-form-section')
|
||||||
|
span
|
||||||
|
label Sport
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#sport-dropdown(name="sport" class="form-main-dropdown")
|
||||||
|
span
|
||||||
|
label Gender
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#gender-dropdown(name="gender")
|
||||||
|
span
|
||||||
|
label Division
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#division-dropdown(name="division")
|
||||||
|
span(class='form-section')
|
||||||
|
label Date of match
|
||||||
|
span(class='form-section-input')
|
||||||
|
input#date-input(type="date", name="date" value=date disabled)
|
||||||
|
span(class='form-section flat-form-section')
|
||||||
|
span
|
||||||
|
label Your team
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#team1-dropdown(name="team1" class="form-main-dropdown" disabled)
|
||||||
|
span(class='form-score-input')
|
||||||
|
label Score
|
||||||
|
span(class='form-section-input')
|
||||||
|
input#team1-score-textbox(type="number", name="team1-score", value="0" disabled)
|
||||||
|
span(class='form-section flat-form-section')
|
||||||
|
span
|
||||||
|
label Opponent
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#team2-dropdown(name="team2" class="form-main-dropdown" disabled)
|
||||||
|
span(class='form-score-input')
|
||||||
|
label Score
|
||||||
|
span(class='form-section-input')
|
||||||
|
input#team2-score-textbox(type="number", name="team2-score", value="0" disabled)
|
||||||
|
if !userLoggedIn
|
||||||
|
span(class='form-section')
|
||||||
|
label Your name
|
||||||
|
span(class='form-section-input')
|
||||||
|
input#name-textbox(type="text" name="name" disabled)
|
||||||
|
.error #{message}
|
||||||
|
span(class='form-section')
|
||||||
|
button#submit-button(type="submit" disabled) Submit
|
||||||
|
span(class='form-section')
|
||||||
|
button#delete-button(disabled) Delete
|
||||||
|
|
||||||
|
block scripts
|
||||||
|
script(src='/scripts/manage/game.js' type="module")
|
|
@ -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")
|
|
@ -0,0 +1,22 @@
|
||||||
|
extends ../layout
|
||||||
|
|
||||||
|
block stylesheets
|
||||||
|
link(rel='stylesheet', href='/stylesheets/submit.css')
|
||||||
|
link(rel='stylesheet', href='/stylesheets/form.css')
|
||||||
|
|
||||||
|
block content
|
||||||
|
span#loading-message Loading...
|
||||||
|
form#submission-form(action='./sport', method='POST')
|
||||||
|
span(class='form-section')
|
||||||
|
label Sport name
|
||||||
|
span(class='form-section-input')
|
||||||
|
input#name-textbox(type="text" name="name" disabled)
|
||||||
|
.error #{message}
|
||||||
|
span(class='form-section')
|
||||||
|
button#submit-button(type="submit" disabled) Submit
|
||||||
|
span(class='form-section')
|
||||||
|
button#delete-button(disabled) Delete
|
||||||
|
|
||||||
|
|
||||||
|
block scripts
|
||||||
|
script(src='/scripts/manage/sport.js' type="module")
|
|
@ -0,0 +1,25 @@
|
||||||
|
extends ../layout
|
||||||
|
|
||||||
|
block stylesheets
|
||||||
|
link(rel='stylesheet', href='/stylesheets/submit.css')
|
||||||
|
link(rel='stylesheet', href='/stylesheets/form.css')
|
||||||
|
|
||||||
|
block content
|
||||||
|
span#loading-message Loading...
|
||||||
|
form#submission-form(action='./team', method='POST')
|
||||||
|
span(class='form-section')
|
||||||
|
label Sport
|
||||||
|
span(class='form-section-input')
|
||||||
|
select#sport-dropdown(name="sport" class="form-main-dropdown" disabled)
|
||||||
|
span(class='form-section')
|
||||||
|
label Team name
|
||||||
|
span(class='form-section-input')
|
||||||
|
input#name-textbox(type="text", name="name" disabled)
|
||||||
|
.error #{message}
|
||||||
|
span(class='form-section')
|
||||||
|
button#submit-button(type="submit" disabled) Submit
|
||||||
|
span(class='form-section')
|
||||||
|
button#delete-button(disabled) Delete
|
||||||
|
|
||||||
|
block scripts
|
||||||
|
script(src='/scripts/manage/team.js' type="module")
|
|
@ -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")
|
Reference in New Issue