Merge branch 'account-names' into 'develop'
Add account names See merge request sudoer777/score-tracker!12main
commit
006e170b92
50
README.md
50
README.md
|
@ -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
2
app.js
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -40,17 +40,24 @@ async function initializeForm() {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -316,7 +329,11 @@ 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]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -31,4 +31,4 @@ tr {
|
||||||
|
|
||||||
#login-button {
|
#login-button {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,4 +54,18 @@ input {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
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;
|
||||||
}
|
}
|
|
@ -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;
|
|
@ -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);
|
||||||
|
|
|
@ -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]
|
|
@ -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' )
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Reference in New Issue