Merge branch 'account-names' into 'develop'

Add account names

See merge request sudoer777/score-tracker!12
main
Ethan Reece 2021-12-02 20:45:00 +00:00
commit 006e170b92
20 changed files with 232 additions and 56 deletions

View File

@ -1,17 +1,49 @@
### 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 is designed to be 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 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
## 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.
### 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/*`).
- `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.

2
app.js
View File

@ -14,6 +14,7 @@ var indexRouter = require('./routes/index');
var dataRouter = require('./routes/data'); var dataRouter = require('./routes/data');
var manageRouter = require('./routes/manage'); var manageRouter = require('./routes/manage');
var authRouter = require('./routes/auth'); var authRouter = require('./routes/auth');
var aboutRouter = require('./routes/about');
var app = express(); var app = express();
@ -51,6 +52,7 @@ app.use('/', indexRouter);
app.use('/data', dataRouter); app.use('/data', dataRouter);
app.use('/manage', manageRouter); app.use('/manage', manageRouter);
app.use('/auth', authRouter); app.use('/auth', authRouter);
app.use('/about', aboutRouter);
// catch 404 and forward to error handler // catch 404 and forward to error handler

View File

@ -4,10 +4,11 @@ const localStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
class User { class User {
constructor(id, email, isAdmin) { constructor(id, email, isAdmin, name) {
this.id = id; this.id = id;
this.email = email; this.email = email;
this.isAdmin = isAdmin; this.isAdmin = isAdmin;
this.name = name;
} }
} }
@ -75,62 +76,64 @@ async function generateHash(password) {
return bcrypt.hashSync(password, salt); return bcrypt.hashSync(password, salt);
} }
async function create(email, password, isAdmin) { async function create(email, password, isAdmin, name) {
const hash = await generateHash(password); const hash = await generateHash(password);
const query = `INSERT INTO accounts.users(email, password, admin) const query = `INSERT INTO accounts.users(email, password, admin, full_name)
VALUES($1, $2, $3)`; VALUES($1, $2, $3, $4)`;
await database.executeQuery(query, [email, hash, isAdmin]); await database.executeQuery(query, [email, hash, isAdmin, name]);
} }
async function edit(id, email, password, isAdmin) { async function edit(id, email, password, isAdmin, name) {
if(password) { if(password) {
const hash = await generateHash(password); const hash = await generateHash(password);
const query = `UPDATE accounts.users const query = `UPDATE accounts.users
SET email = $2, SET email = $2,
password = $3, password = $3,
admin = $4 admin = $4,
full_name = $5
WHERE user_id = $1;`; WHERE user_id = $1;`;
await database.executeQuery(query, [id, email, hash, isAdmin]); await database.executeQuery(query, [id, email, hash, isAdmin, name]);
} else { } else {
const query = `UPDATE accounts.users const query = `UPDATE accounts.users
SET email = $2, SET email = $2,
admin = $3 admin = $3,
full_name = $4
WHERE user_id = $1;`; WHERE user_id = $1;`;
await database.executeQuery(query, [id, email, isAdmin]); await database.executeQuery(query, [id, email, isAdmin, name]);
} }
return new User(id, email, isAdmin); return new User(id, email, isAdmin, name);
} }
async function remove(id) { async function remove(id) {
const query = `DELETE FROM accounts.users const query = `DELETE FROM accounts.users
WHERE user_id = $1 WHERE user_id = $1
RETURNING email, admin;`; RETURNING email, admin, full_name;`;
const row = (await database.executeQuery(query, [id]))[0]; const row = (await database.executeQuery(query, [id]))[0];
return new User(id, row[0], row[1]); return new User(id, row[0], row[1], row[2]);
} }
async function retrieveAll() { async function retrieveAll() {
const query = `SELECT user_id, email, admin const query = `SELECT user_id, email, admin, full_name
FROM accounts.users FROM accounts.users
ORDER BY email;` ORDER BY full_name;`;
const table = await database.executeQuery(query); const table = await database.executeQuery(query);
const accountsList = []; const accountsList = [];
table.forEach((row) => { table.forEach((row) => {
accountsList.push(new User(row[0], row[1], row[2])); accountsList.push(new User(row[0], row[1], row[2], row[3]));
}); });
return accountsList; return accountsList;
} }
async function getFromID(id) { async function getFromID(id) {
const query = `SELECT user_id, email, admin const query = `SELECT user_id, email, admin, full_name
FROM accounts.users FROM accounts.users
WHERE user_id = $1;`; WHERE user_id = $1;`;
const row = (await database.executeQuery(query, [id]))[0]; const row = (await database.executeQuery(query, [id]))[0];
return new User(id, row[1], row[2]); return new User(id, row[1], row[2], row[3]);
} }
exports.create = create; exports.create = create;

View File

@ -27,17 +27,40 @@ async function Initialize() {
async function checkForDatabaseInitialization() { async function checkForDatabaseInitialization() {
const scoresSchemaExistsQuery = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`; const databaseIsSetupQuery = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`;
let result = await executeQuery(scoresSchemaExistsQuery); let result = await executeQuery(databaseIsSetupQuery);
const scoresSchemaExists = result.length !== 0; const databaseIsSetup = result.length !== 0;
if(!scoresSchemaExists) { if(!databaseIsSetup) {
await Initialize(); 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(); 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}`);
}
}

View File

@ -37,6 +37,7 @@ CREATE TABLE IF NOT EXISTS accounts.users(
email TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL, password TEXT NOT NULL,
admin BOOLEAN NOT NULL DEFAULT FALSE, admin BOOLEAN NOT NULL DEFAULT FALSE,
full_name TEXT NOT NULL,
PRIMARY KEY(user_id) PRIMARY KEY(user_id)
); );
@ -110,4 +111,13 @@ CREATE TABLE IF NOT EXISTS scores.games(
REFERENCES accounts.users(user_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", "2");
COMMIT; COMMIT;

View File

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

View File

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

View File

@ -41,14 +41,14 @@ async function retrieve(teamID, divisionID, seasonID) {
let table; let table;
if(teamID && divisionID && seasonID) { if(teamID && divisionID && seasonID) {
const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id
FROM scores.games FROM scores.games
WHERE (team1_id = $1 OR team2_id = $1) AND division_id = $2 AND season_id = $3 WHERE (team1_id = $1 OR team2_id = $1) AND division_id = $2 AND season_id = $3
ORDER BY game_date DESC;`; ORDER BY game_date DESC;`;
table = await database.executeQuery(query, [teamID,divisionID,seasonID]); table = await database.executeQuery(query, [teamID,divisionID,seasonID]);
} }
else { else {
const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id
FROM scores.games FROM scores.games
ORDER BY game_date DESC;`; ORDER BY game_date DESC;`;
table = await database.executeQuery(query); table = await database.executeQuery(query);
@ -63,10 +63,10 @@ async function retrieve(teamID, divisionID, seasonID) {
const teamScore = opponentIsTeam2 ? row[6] : row[7]; const teamScore = opponentIsTeam2 ? row[6] : row[7];
const opponentScore = opponentIsTeam2 ? row[7] : row[6]; 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])); gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), teamID, opponentID, teamScore, opponentScore, row[1], row[2], row[8]));
} }
else { 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])); 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]));
} }
}); });
return gamesList; return gamesList;

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "score-tracker", "name": "score-tracker",
"version": "1.0.1-pre", "version": "1.0.3-pre",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "score-tracker", "name": "score-tracker",
"version": "1.0.1-pre", "version": "1.0.3-pre",
"dependencies": { "dependencies": {
"async": "^3.2.2", "async": "^3.2.2",
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "score-tracker", "name": "score-tracker",
"version": "1.0.1-pre", "version": "1.0.3-pre",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node ./bin/www" "start": "node ./bin/www"

View File

@ -41,16 +41,23 @@ async function initializeForm() {
await Form.populateTeams(teamDropdown, sportDropdown.value); await Form.populateTeams(teamDropdown, sportDropdown.value);
} }
seasonDropdown.onchange = loadTable;
sportDropdown.onchange = async () => { sportDropdown.onchange = async () => {
await Form.populateGenders(genderDropdown, sportDropdown.value) await Form.populateGenders(genderDropdown, sportDropdown.value)
await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value);
await Form.populateTeams(teamDropdown, sportDropdown.value); await Form.populateTeams(teamDropdown, sportDropdown.value);
loadTable();
}; };
genderDropdown.onchange = async () => { genderDropdown.onchange = async () => {
await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value);
loadTable();
}; };
divisionDropdown.onchange = loadTable;
teamDropdown.onchange = loadTable;
loadTable(); loadTable();

View File

@ -232,6 +232,10 @@ CATEGORIES.push(new Category(
dateHeader.textContent = "Date"; dateHeader.textContent = "Date";
headerRow.appendChild(dateHeader); headerRow.appendChild(dateHeader);
const submitterHeader = document.createElement('th');
submitterHeader.textContent = "Submitter";
headerRow.appendChild(submitterHeader);
itemsListTable.appendChild(headerRow); itemsListTable.appendChild(headerRow);
}, },
function listGame(game, row) { function listGame(game, row) {
@ -285,6 +289,11 @@ CATEGORIES.push(new Category(
dateSpan.textContent = game.date.slice(5); dateSpan.textContent = game.date.slice(5);
dateCell.appendChild(dateSpan); dateCell.appendChild(dateSpan);
row.appendChild(dateCell); row.appendChild(dateCell);
const submitterCell = document.createElement('td');
Data.getAccount(game.submitterID)
.then(data => submitterCell.textContent = data.name);
row.appendChild(submitterCell);
}, },
async function addGame() { async function addGame() {
window.location.href = "/manage/game"; window.location.href = "/manage/game";
@ -302,6 +311,10 @@ CATEGORIES.push(new Category(
async function listAccountHeaders() { async function listAccountHeaders() {
const headerRow = document.createElement('tr'); const headerRow = document.createElement('tr');
const nameHeader = document.createElement('th');
nameHeader.textContent = "Name";
headerRow.appendChild(nameHeader);
const emailHeader = document.createElement('th'); const emailHeader = document.createElement('th');
emailHeader.textContent = "Email"; emailHeader.textContent = "Email";
headerRow.appendChild(emailHeader); headerRow.appendChild(emailHeader);
@ -317,6 +330,10 @@ CATEGORIES.push(new Category(
itemsListTable.appendChild(headerRow); itemsListTable.appendChild(headerRow);
}, },
function listAccount(account, row) { function listAccount(account, row) {
const nameCell = document.createElement('td');
nameCell.textContent = account.name;
row.appendChild(nameCell);
const emailCell = document.createElement('td'); const emailCell = document.createElement('td');
emailCell.textContent = account.email; emailCell.textContent = account.email;
row.appendChild(emailCell); row.appendChild(emailCell);
@ -355,7 +372,8 @@ async function listItems(category) {
const editSpan = document.createElement('span'); const editSpan = document.createElement('span');
const editButton = document.createElement('button'); const editButton = document.createElement('button');
editButton.textContent = "E"; editButton.textContent = "✎";
editButton.style['font-size'] = '1.25em';
editButton.addEventListener('click', () => { editButton.addEventListener('click', () => {
CATEGORIES[categoryDropdown.selectedIndex].editItem(item.id); CATEGORIES[categoryDropdown.selectedIndex].editItem(item.id);
}); });
@ -367,10 +385,18 @@ async function listItems(category) {
itemsListTable.appendChild(row); itemsListTable.appendChild(row);
}); });
} }
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]); listItems(CATEGORIES[categoryDropdown.selectedIndex]);
categoryDropdown.onchange = () => { categoryDropdown.onchange = () => {
listItems(CATEGORIES[categoryDropdown.selectedIndex]); listItems(CATEGORIES[categoryDropdown.selectedIndex]);
}; };

View File

@ -2,6 +2,7 @@ import * as Data from "../data.js";
import * as Form from "../form.js"; import * as Form from "../form.js";
const submissionForm = document.getElementById('submission-form'); const submissionForm = document.getElementById('submission-form');
const nameTextbox = document.getElementById('name-textbox');
const emailTextbox = document.getElementById('email-textbox'); const emailTextbox = document.getElementById('email-textbox');
const passwordTextbox = document.getElementById('password-textbox'); const passwordTextbox = document.getElementById('password-textbox');
const adminCheckboxSection = document.getElementById('admin-checkbox-section'); const adminCheckboxSection = document.getElementById('admin-checkbox-section');
@ -14,7 +15,8 @@ async function Initialize() {
let accountID = params.get('account') || (document.getElementById('account-id') ? document.getElementById('account-id').value : null); let accountID = params.get('account') || (document.getElementById('account-id') ? document.getElementById('account-id').value : null);
if(accountID) { if(accountID) {
const account = await Data.getAccount(accountID); const account = await Data.getAccount(accountID);
console.log(account);
nameTextbox.value = account.name;
emailTextbox.value = account.email; emailTextbox.value = account.email;
@ -37,6 +39,8 @@ async function Initialize() {
adminCheckboxSection.style.visibility = "visible"; adminCheckboxSection.style.visibility = "visible";
adminCheckbox.disabled = false; adminCheckbox.disabled = false;
} }
nameTextbox.disabled = false;
nameTextbox.addEventListener('keyup', checkDataValidity);
emailTextbox.disabled = false; emailTextbox.disabled = false;
emailTextbox.addEventListener('keyup', checkDataValidity); emailTextbox.addEventListener('keyup', checkDataValidity);
passwordTextbox.disabled = false; passwordTextbox.disabled = false;
@ -49,6 +53,7 @@ async function checkDataValidity() {
let dataIsValid = true; let dataIsValid = true;
if(!passwordTextbox.value && !passwordTextbox.placeholder) dataIsValid = false; if(!passwordTextbox.value && !passwordTextbox.placeholder) dataIsValid = false;
if(!nameTextbox.value) dataIsValid = false;
if(!emailTextbox.value) dataIsValid = false; if(!emailTextbox.value) dataIsValid = false;
if(dataIsValid) submitButton.disabled = false; if(dataIsValid) submitButton.disabled = false;

View File

@ -55,3 +55,17 @@ input {
margin-bottom: 3em; margin-bottom: 3em;
border-radius: .25em; border-radius: .25em;
} }
#about-footer {
margin-top: 3em;
text-align: center;
}
p {
line-height: 2;
}
a {
color: black;
}

8
routes/about.js 100644
View File

@ -0,0 +1,8 @@
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
res.render('about', { title: 'About Score Tracker', hideHomeButton: false });
});
module.exports = router;

View File

@ -50,11 +50,11 @@ router.post('/game', checkLoginStatus.user, async function(req, res, next) {
} }
else if(remove) { else if(remove) {
await games.remove(id); await games.remove(id);
res.redirect("/manage"); res.redirect('/manage#games');
} }
else if(id) { else if(id) {
await games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score); await games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score);
res.redirect('/manage'); res.redirect('/manage#games');
} }
else { else {
await games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID); await games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID);
@ -81,7 +81,7 @@ router.post('/season', checkLoginStatus.admin, async function(req, res, next) {
if(remove) await seasons.remove(seasonID); if(remove) await seasons.remove(seasonID);
else await seasons.add(year); else await seasons.add(year);
res.redirect('/manage'); res.redirect('/manage#seasons');
} catch(err) { } catch(err) {
console.error("ERROR: " + err.message); console.error("ERROR: " + err.message);
req.flash("error", "An error has occurred."); req.flash("error", "An error has occurred.");
@ -106,7 +106,7 @@ router.post('/sport', checkLoginStatus.admin, async function(req, res, next) {
else if(id) await sports.rename(id, name); else if(id) await sports.rename(id, name);
else await sports.add(name); else await sports.add(name);
res.redirect('/manage'); res.redirect('/manage#sports');
} catch(err) { } catch(err) {
console.error("ERROR: " + err.message); console.error("ERROR: " + err.message);
req.flash("error", "An error has occurred."); req.flash("error", "An error has occurred.");
@ -140,7 +140,7 @@ router.post('/division', checkLoginStatus.admin, async function(req, res, next)
await divisions.add(name, gender, sport); await divisions.add(name, gender, sport);
} }
} }
res.redirect('/manage'); res.redirect('/manage#divisions');
} catch(err) { } catch(err) {
console.error("ERROR: " + err.message); console.error("ERROR: " + err.message);
req.flash("error", "An error has occurred."); req.flash("error", "An error has occurred.");
@ -162,9 +162,9 @@ router.post('/team', checkLoginStatus.admin, async function(req, res, next) {
const name = req.body['name']; const name = req.body['name'];
const sport = req.body['sport']; const sport = req.body['sport'];
if(remove) teams.remove(id).then(res.redirect('/manage')); if(remove) teams.remove(id).then(res.redirect('/manage#teams'));
else if(id) teams.rename(id, name).then(res.redirect('/manage')); else if(id) teams.rename(id, name).then(res.redirect('/manage#teams'));
else teams.add(name, sport).then(res.redirect("/manage")); else teams.add(name, sport).then(res.redirect('/manage#teams'));
} catch(err) { } catch(err) {
console.error("ERROR: " + err.message); console.error("ERROR: " + err.message);
req.flash("error", "An error has occurred."); req.flash("error", "An error has occurred.");
@ -189,6 +189,7 @@ router.get('/account', checkLoginStatus.user, (req, res, next) => {
}); });
router.post('/account', checkLoginStatus.user, async function(req, res, next) { router.post('/account', checkLoginStatus.user, async function(req, res, next) {
const name = req.body.name;
const email = req.body.email; const email = req.body.email;
const password = req.body.password; const password = req.body.password;
@ -206,10 +207,10 @@ router.post('/account', checkLoginStatus.user, async function(req, res, next) {
const isAdmin = loggedInAccountIsAdmin ? !!req.body.admin : false; const isAdmin = loggedInAccountIsAdmin ? !!req.body.admin : false;
if(remove) await accounts.remove(accountID); if(remove) await accounts.remove(accountID);
else if(accountID) await accounts.edit(accountID, email, password, isAdmin); else if(accountID) await accounts.edit(accountID, email, password, isAdmin, name);
else await accounts.create(req.body.email, req.body.password, !!req.body.admin); else await accounts.create(email, password, !!req.body.admin, name);
res.redirect('/manage'); res.redirect('/manage#accounts');
} }
catch (err) { catch (err) {
console.error("ERROR: " + err.message); console.error("ERROR: " + err.message);

13
views/about.pug 100644
View File

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

View File

@ -8,10 +8,14 @@ block content
form#submission-form(action='/manage/account', method='POST') form#submission-form(action='/manage/account', method='POST')
if accountID if accountID
input#account-id(type="hidden" name="account" value=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') span(class='form-section')
label Email label Email
span(class='form-section-input') span(class='form-section-input')
input#email-textbox(type="email", name="email" disabled) input#email-textbox(type="email" name="email" disabled)
span(class='form-section') span(class='form-section')
label Password label Password
span(class='form-section-input' ) span(class='form-section-input' )

View File

@ -2,7 +2,7 @@ doctype html
html html
head head
title= title + ' - Score Tracker' title= title + ' - Score Tracker'
meta(name='viewport', content='width=device-width, initial-scale=1') 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 block stylesheets
body body
@ -19,5 +19,8 @@ html
button#login-button Log in button#login-button Log in
h1 #{title} h1 #{title}
block content block content
div#about-footer
a(href="/about") Help/About
block scripts block scripts
script(src='/scripts/main.js' type="module") script(src='/scripts/main.js' type="module")