diff --git a/.env.example b/.env.example index 9ef5217..11d1e69 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,11 @@ NODE_ENV=development +PGUSER=dbuser +PGHOST=database.server.com +PGPASSWORD=dbuserpassword +PGDATABASE=mydatabase +PGPORT=5432 + MAIL_FROM=fromaddress@example.com MAIL_HOST=smtp.smtphost.net MAIL_PORT=465 diff --git a/app.js b/app.js index 3cff1ca..29f7b4f 100644 --- a/app.js +++ b/app.js @@ -6,7 +6,8 @@ var logger = require('morgan'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); -var submitRouter = require('./routes/submit') +var submitRouter = require('./routes/submit'); +var dataRouter = require('./routes/data'); var app = express(); @@ -23,6 +24,7 @@ app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/users', usersRouter); app.use('/submit', submitRouter); +app.use('/data', dataRouter); // catch 404 and forward to error handler app.use(function(req, res, next) { diff --git a/database/database.js b/database/database.js new file mode 100644 index 0000000..f166980 --- /dev/null +++ b/database/database.js @@ -0,0 +1,47 @@ +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 query = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`; + let result = await executeQuery(query); + + const scoresSchemaExists = result.length !== 0; + + if(!scoresSchemaExists) { + Initialize(); + } +} +checkForDatabaseInitialization(); + + + + + +exports.executeQuery = executeQuery; \ No newline at end of file diff --git a/database/init_database.sql b/database/init_database.sql new file mode 100644 index 0000000..ad4f0e7 --- /dev/null +++ b/database/init_database.sql @@ -0,0 +1,105 @@ +/* SCORE TRACKER DATABASE LAYOUT + +scores: + + sports: + *sport_id* | sport_name | currently_active + + divisions: + *division_id* | division_name | gender | *sport_id* | currently_active + + teams: + *team_id* | team_name | ~sport_id~ | currently_active + + seasons: + *season_id* | school_year + + games: + *game_id* | ~division_id~ | ~season_id~ | game_date | ~team1_id~ | ~team2_id~ | team1_score | team2_score | ~submitter_id~ | updated_timestamp + + + +accounts: + + users: + *user_id* | email | salt | password_hash | role | approved + + sessions: + *session_id* | ~user_id~ | expiration_date + +*/ + + +BEGIN; + + +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) CHECK (gender IN ( 'F', 'M' ) ), + sport_id BIGINT, + 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, + 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, + season_id BIGINT, + game_date DATE, + team1_id BIGINT, + team2_id BIGINT, + team1_score INTEGER, + team2_score INTEGER, +/* 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)*/ + ); + + +COMMIT; \ No newline at end of file diff --git a/database/scores/divisions.js b/database/scores/divisions.js new file mode 100644 index 0000000..38fa8be --- /dev/null +++ b/database/scores/divisions.js @@ -0,0 +1,70 @@ +const database = require('./../database'); +const genders = require('./genders'); + + + + + +class Division { + constructor(id, name) { + this.id = id; + this.name = name; + } +} + + + +function getGenderID(gender) { + return (gender == genders.MALE) ? "M" : "F"; +} + + + +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, division) { + 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 retrieveBySportAndGender(sportID, gender) { + const query = `SELECT * + FROM scores.divisions + WHERE sport_id = $1 AND gender = $2 + ORDER BY division_name;`; + const genderID = getGenderID(gender); + const table = await database.executeQuery(query, [sportID, genderID]); + + const divisionsList = []; + table.forEach((row) => { + divisionsList.push(new Division(row[0], row[1])); + }); + return divisionsList; +} + + + + + +exports.add = add; +exports.rename = rename; +exports.remove = remove; +exports.retrieveBySportAndGender = retrieveBySportAndGender; \ No newline at end of file diff --git a/database/scores/games.js b/database/scores/games.js new file mode 100644 index 0000000..96463ec --- /dev/null +++ b/database/scores/games.js @@ -0,0 +1,65 @@ +const database = require('./../database'); +const moment = require('moment'); + + + + + +class Game { + constructor(id, date, team1ID, team2ID, team1Score, team2Score) { + this.id = id; + this.date = date; + this.team1ID = team1ID; + this.team2ID = team2ID; + this.team1Score = team1Score; + this.team2Score = team2Score; + } +} + + + +async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) { + const query = `INSERT INTO scores.games(division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score) + VALUES($1, $2, $3, $4, $5, $6, $7) + RETURNING game_id;`; + + const dateISO = date.format('YYYY-MM-DD'); + + const id = (await database.executeQuery(query, [divisionID, seasonID, dateISO, team1ID, team2ID, team1Score, team2Score]))[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 retrieveByTeamDivisionAndSeason(teamID, divisionID, seasonID) { + const query = `SELECT * + FROM scores.games + WHERE (team1_id = $1 OR team2_id = $1) AND division_id = $2 AND season_id = $3 + ORDER BY game_date DESC;`; + const table = (await database.executeQuery(query, [teamID,divisionID,seasonID])); + + const gamesList = []; + table.forEach((row) => { + opponentIsTeam2 = teamID != row[5]; + opponentID = opponentIsTeam2 ? row[5] : row[4]; + teamScore = opponentIsTeam2 ? row[6] : row[7]; + opponentScore = opponentIsTeam2 ? row[7] : row[6]; + + gamesList.push(new Game(row[0], moment(row[3]), teamID, opponentID, teamScore, opponentScore)); + }); + return gamesList; +} + + + + + +exports.add = add; +exports.remove = remove; +exports.retrieveByTeamDivisionAndSeason = retrieveByTeamDivisionAndSeason; \ No newline at end of file diff --git a/database/scores/genders.js b/database/scores/genders.js new file mode 100644 index 0000000..b59bdb0 --- /dev/null +++ b/database/scores/genders.js @@ -0,0 +1,48 @@ +const database = require('./../database'); + + + + + +class Gender { + constructor(name) { + this.name = name; + } +} +const MALE = new Gender("male"); +const FEMALE = new Gender("female"); + + + +async function retrieveBySport(sportID) { + const query = `SELECT gender + from scores.divisions + WHERE sport_id = $1;`; + const table = await database.executeQuery(query, [sportID]); + + const gendersList = []; + + if(table.length == 0) { + return gendersList; + } + if(table.length == 2) { + gendersList.push(FEMALE); + gendersList.push(MALE); + return gendersList; + } + else if(table[0][0] = "F") { + gendersList.push(FEMALE); + return gendersList; + } + else if(table[0][0] = "M") { + gendersList.push(MALE); + return gendersList; + } +} + + + + +exports.MALE = MALE; +exports.FEMALE = FEMALE; +exports.retrieveBySport = retrieveBySport; \ No newline at end of file diff --git a/database/scores/seasons.js b/database/scores/seasons.js new file mode 100644 index 0000000..f787ade --- /dev/null +++ b/database/scores/seasons.js @@ -0,0 +1,51 @@ +const database = require('./../database'); + + + + + +class Season { + constructor(id, year) { + this.id = id; + this.year = year; + } +} + + + +async function add(year) { + const query = `INSERT INTO scores.seasons(school_year) + VALUES($1) + RETURNING season_id;`; + const id = (await database.executeQuery(query, [year]))[0][0]; + return new Season(id, year); +} + +async function remove(id) { + const query = `DELETE FROM scores.seasons + WHERE season_id = $1 + RETURNING school_year;`; + const year = (await database.executeQuery(query, [id]))[0][0]; + return new Season(id, year); +} + +async function retrieveAll() { + const query = `SELECT * + FROM scores.seasons + ORDER BY school_year DESC;`; + const table = await database.executeQuery(query); + + const seasonsList = []; + table.forEach((row) => { + seasonsList.push(new Season(row[0], row[1])); + }); + return seasonsList; +} + + + + + +exports.add = add; +exports.remove = remove; +exports.retrieveAll = retrieveAll; \ No newline at end of file diff --git a/database/scores/sports.js b/database/scores/sports.js new file mode 100644 index 0000000..14a6e45 --- /dev/null +++ b/database/scores/sports.js @@ -0,0 +1,58 @@ +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; +} + + + + + +exports.add = add; +exports.rename = rename; +exports.remove = remove; +exports.retrieveAll = retrieveAll; \ No newline at end of file diff --git a/database/scores/teams.js b/database/scores/teams.js new file mode 100644 index 0000000..89f2c4c --- /dev/null +++ b/database/scores/teams.js @@ -0,0 +1,61 @@ +const database = require('./../database'); + + + + + +class Team { + constructor(id, name) { + this.id = id; + this.name = name; + } +} + + + +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 retrieveBySport(sportID) { + const query = `SELECT * + FROM scores.teams + WHERE sport_id = $1 + ORDER BY team_name;`; + const table = await database.executeQuery(query, [sportID]); + + const teamsList = []; + table.forEach((row) => { + teamsList.push(new Team(row[0], row[1])); + }); + return teamsList; +} + + + + + +exports.add = add; +exports.rename = rename; +exports.remove = remove; +exports.retrieveBySport = retrieveBySport; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 006fc12..821a54c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,17 @@ "name": "demo", "version": "0.0.0", "dependencies": { + "async": "^3.2.2", "cookie-parser": "~1.4.3", "debug": "~2.6.9", "dotenv": "^10.0.0", "express": "~4.16.0", "http-errors": "~1.6.2", + "moment": "^2.29.1", "morgan": "~1.9.0", "nodemailer": "^6.6.5", + "passport": "^0.5.0", + "pg": "^8.7.1", "pug": "2.0.0-beta11" }, "devDependencies": { @@ -108,6 +112,11 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "node_modules/async": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -195,6 +204,14 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -893,6 +910,14 @@ "ms": "2.0.0" } }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, "node_modules/morgan": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", @@ -965,6 +990,11 @@ "wrappy": "1" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "node_modules/parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", @@ -973,6 +1003,30 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz", + "integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -992,6 +1046,120 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, + "node_modules/pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=2.0.0" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "dependencies": { + "split2": "^3.1.1" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -1294,6 +1462,27 @@ "node": ">=0.8.0" } }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/split2/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -1306,7 +1495,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -1434,8 +1622,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -1492,6 +1679,14 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", @@ -1572,6 +1767,11 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "async": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1650,6 +1850,11 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -2204,6 +2409,11 @@ } } }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, "morgan": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", @@ -2258,11 +2468,30 @@ "wrappy": "1" } }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" }, + "passport": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz", + "integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2279,6 +2508,89 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, + "pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "requires": {} + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "requires": { + "split2": "^3.1.1" + } + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -2550,6 +2862,26 @@ "amdefine": ">=0.0.4" } }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "requires": { + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -2559,7 +2891,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -2662,8 +2993,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", @@ -2705,6 +3035,11 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", diff --git a/package.json b/package.json index 0a035a8..2e3323a 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,17 @@ "test": "mocha" }, "dependencies": { + "async": "^3.2.2", "cookie-parser": "~1.4.3", "debug": "~2.6.9", "dotenv": "^10.0.0", "express": "~4.16.0", "http-errors": "~1.6.2", + "moment": "^2.29.1", "morgan": "~1.9.0", "nodemailer": "^6.6.5", + "passport": "^0.5.0", + "pg": "^8.7.1", "pug": "2.0.0-beta11" }, "devDependencies": { diff --git a/public/scripts/data.js b/public/scripts/data.js new file mode 100644 index 0000000..7e21436 --- /dev/null +++ b/public/scripts/data.js @@ -0,0 +1,29 @@ +export async function getSports() { + const response = await fetch('/data/sports'); + const sportsList = await response.json(); + return sportsList; +} + +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, gender) { + const response = await fetch(`/data/divisions?sport=${+sportID}&gender=${gender}`); + const divisionsList = await response.json(); + return divisionsList; +} + +export async function getTeams(sportID) { + const response = await fetch(`/data/teams?sport=${+sportID}`); + const teamsList = await response.json(); + return teamsList; +} \ No newline at end of file diff --git a/public/scripts/submit.js b/public/scripts/submit.js new file mode 100644 index 0000000..bd4d08a --- /dev/null +++ b/public/scripts/submit.js @@ -0,0 +1,113 @@ +import * as Data from "./data.js"; + + +const sportDropdown = document.getElementById('sport-dropdown'); +const seasonDropdown = document.getElementById('year-dropdown'); +const genderDropdown = document.getElementById('gender-dropdown'); +const divisionDropdown = document.getElementById('division-dropdown'); +const team1Dropdown = document.getElementById('team1-dropdown'); +const team2Dropdown = document.getElementById('team2-dropdown'); + + + + + +async function listSeasons() { + seasonDropdown.innerHTML = ""; + + const seasonsList = await Data.getSeasons(); + + seasonsList.forEach(season => { + const option = document.createElement('option'); + option.text = season.year - 1 + "-" + season.year; + option.value = season.id; + seasonDropdown.appendChild(option); + }); +} +listSeasons(); + +async function listSports() { + sportDropdown.innerHTML = ""; + + const sportsList = await Data.getSports(); + + sportsList.forEach(sport => { + const option = document.createElement('option'); + option.text = sport.name; + option.value = sport.id; + sportDropdown.appendChild(option); + }); + + listGenders(); + listTeams(); +} +listSports(); + +async function listGenders() { + genderDropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + const gendersList = await Data.getGenders(selectedSportID); + + if(selectedSportID) { + gendersList.forEach(gender => { + const option = document.createElement('option'); + option.text = (gender.name == "female") ? "Female" : (gender.name == "male") ? "Male" : ""; + option.value = gender.name; + genderDropdown.appendChild(option); + }); + } + + listDivisions(); +} + +async function listDivisions() { + divisionDropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + const selectedGender = genderDropdown.value; + + if(selectedGender) { + const divisionsList = await Data.getDivisions(selectedSportID, selectedGender); + + divisionsList.forEach(division => { + const option = document.createElement('option'); + option.text = division.name; + option.value = division.id; + divisionDropdown.appendChild(option); + }); + } +} + +async function listTeams() { + team1Dropdown.innerHTML = ""; + team2Dropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + + if(selectedSportID) { + const teamsList = await Data.getTeams(selectedSportID); + + teamsList.forEach(team => { + const optionT1 = document.createElement('option'); + optionT1.text = team.name; + optionT1.value = team.id; + team1Dropdown.appendChild(optionT1); + + const optionT2 = document.createElement('option'); + optionT2.text = team.name; + optionT2.value = team.id; + team2Dropdown.appendChild(optionT2); + }); + } +} + + + + + +sportDropdown.onchange = (() => { + listGenders(); + listTeams(); +}); +genderDropdown.onchange = listDivisions; \ No newline at end of file diff --git a/public/stylesheets/submit.css b/public/stylesheets/submit.css index 89dd41c..80a8146 100644 --- a/public/stylesheets/submit.css +++ b/public/stylesheets/submit.css @@ -16,7 +16,7 @@ span { } -#sport-dropdown { +.main-dropdown { width: 100%; } diff --git a/routes/data.js b/routes/data.js new file mode 100644 index 0000000..fc6c947 --- /dev/null +++ b/routes/data.js @@ -0,0 +1,37 @@ +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'); + + +router.get('/sports', function(req, res, next) { + sports.retrieveAll() + .then(data => res.json(data)); +}); + +router.get('/seasons', function(req, res, next) { + seasons.retrieveAll() + .then(data => res.json(data)); +}) + +router.get('/genders', function(req, res, next) { + genders.retrieveBySport(req.query.sport) + .then(data => res.json(data)); +}) + +router.get('/divisions', function(req, res, next) { + const gender = req.body.gender == 'female' ? genders.FEMALE : genders.MALE; + + divisions.retrieveBySportAndGender(req.query.sport, gender) + .then(data => res.json(data)); +}) + +router.get('/teams', function(req, res, next) { + teams.retrieveBySport(req.query.sport) + .then(data => res.json(data)); +}) + +module.exports = router; \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 9399195..c806b1c 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,5 +1,6 @@ var express = require('express'); var router = express.Router(); +var database = require('../database/database'); /* GET home page. */ router.get('/', function(req, res, next) { diff --git a/routes/submit.js b/routes/submit.js index 30901be..8510303 100644 --- a/routes/submit.js +++ b/routes/submit.js @@ -1,38 +1,28 @@ var express = require('express'); -var mail = require('../mail/mail'); var router = express.Router(); +var genders = require('../database/scores/genders'); +var games = require('../database/scores/games'); +var moment = require('moment'); /* GET submit page. */ router.get('/', function(req, res, next) { - res.send('Nothing to send'); + res.render('submit', { title: 'Submit Score', date: moment().format('YYYY-MM-DD') }); }); /* POST submit page. */ router.post('/', function(req, res, next) { - let sport = req.body.sport; - let gender = req.body.gender; - let division = req.body.division; - let home = req.body['home-team']; - let homeScore = req.body['home-team-score']; - let visiting = req.body['visiting-team']; - let visitingScore = req.body['visiting-team-score']; - let submitter = req.body['submitter']; - let recipient = req.body['email']; + 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 = moment(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']; - let message = prepMailBody(sport, gender, division, home, homeScore, visiting, visitingScore, submitter); - - mail.send(recipient, "Score Report", message); - - res.send('Score sent'); + games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) + .then(res.send("SUCCESS")); }); -var prepMailBody = function(sport, gender, division, home, homeScore, visiting, visitingScore, submitter) { - return( - "Score report from " + submitter + "

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

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

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

"); -}; - - module.exports = router; diff --git a/views/index.pug b/views/index.pug index 7f6baf1..e69de29 100644 --- a/views/index.pug +++ b/views/index.pug @@ -1,40 +0,0 @@ -extends layout - -block stylesheets - link(rel='stylesheet', href='/stylesheets/submit.css') - -block content - h1 Submit Score - form(action='/submit', method='POST') - span(class='form-section') - label Sport - span(class='form-section-input') - select#sport-dropdown(name="sport") - option(value="Football" selected) Football - select#gender-dropdown(name="gender") - option(value="Male" selected) Male - option(value="Female") Female - select#division-dropdown(name="division") - option(value="Varsity") Varsity - option(value="JV-A") JV-A - option(value="JV-B") JV-B - span(class='form-section') - label Home Team - span(class='form-section-input') - input(type="text", name="home-team") - input(class="score-input", type="number", name="home-team-score", value="0") - span(class='form-section') - label Visiting Team - span(class='form-section-input') - input(type="text", name="visiting-team") - input(class="score-input", type="number", name="visiting-team-score", value="0") - span(class='form-section') - label Submitter - span(class='form-section-input') - input(type="text", name="submitter") - span(class='form-section') - label Send info to - span(class='form-section-input') - input(type="email", name="email", placeholder="email@example.com") - span(class='form-section') - button(type="submit") Submit diff --git a/views/layout.pug b/views/layout.pug index 69f5a59..e4f85eb 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -7,3 +7,4 @@ html block stylesheets body block content + block scripts diff --git a/views/submit.pug b/views/submit.pug index e69de29..97d7b37 100644 --- a/views/submit.pug +++ b/views/submit.pug @@ -0,0 +1,37 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + +block content + h1 Submit Score + form(action='/submit', method='POST') + span(class='form-section') + label Year + span(class='form-section-input') + select#year-dropdown(name="year" class="main-dropdown") + span(class='form-section') + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="main-dropdown") + select#gender-dropdown(name="gender") + select#division-dropdown(name="division") + span(class='form-section') + label Date of match + span(class='form-section-input') + input(type="date", name="date", value=date) + span(class='form-section') + label Your team + span(class='form-section-input') + select#team1-dropdown(name="team1" class="main-dropdown") + input(class="score-input", type="number", name="team1-score", value="0") + span(class='form-section') + label Opponent + span(class='form-section-input') + select#team2-dropdown(name="team2" class="main-dropdown") + input(class="score-input", type="number", name="team2-score", value="0") + span(class='form-section') + button(type="submit") Submit + +block scripts + script(src='/scripts/submit.js' type="module") \ No newline at end of file