Merge branch 'server' into 'main'

Merge entire server branch into main

See merge request felixalb/dcst1008-2022-group1!4
This commit is contained in:
Kristoffer Juelsenn 2022-04-26 15:53:43 +02:00
commit 29f463f248
10 changed files with 5441 additions and 3 deletions

14
src/server/README.md Normal file
View File

@ -0,0 +1,14 @@
## Server installation
* Clone the repository
** Checkout the "server" branch if not merged
* Enter the server directory: `cd src/server`
* Install the node dependencies: `npm install`
* Create the file `.env` containing your database login:
```
DB_HOST=mysql.stud.ntnu.no
DB_USER=dbusername
DB_PASSWORD=dbpassword
DB_DATABASE=dbname
```
* Build the client (separate instructions)
* Start the server `npm start`

1
src/server/clientbuild Symbolic link
View File

@ -0,0 +1 @@
../../../sysut_client/src/client/build

592
src/server/index.js Normal file
View File

@ -0,0 +1,592 @@
const path = require("path");
const express = require("express");
const session = require('express-session');
const https = require("https");
require("dotenv").config();
// Our self-written module for handling database operations
let tmdb = require("./tmdb.js");
// #region Express setup
const app = express();
const port = process.env.SERVER_PORT || 3000;
app.listen(parseInt(port), () => {
console.log(`Listening on port ${port}`)
})
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(session({
resave: true,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
rolling: true,
cookie: {
secure: (process.env.COOKIE_SECURE == "true"), // All env vars are strings, so cast bool manually
sameSite: 'strict', // Browsers will reject a "secure" cookie without this
maxAge: 60 * 60 * 1000 // 1 hour (in milliseconds)
}
}));
let api = express.Router();
app.use("/api", api);
api.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, POST, DELETE");
// res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
api.use(require('express-log-url'));
app.use(require('express-log-url'));
// #endregion
// #region frontend
// Serve static files from the React app
app.use('/', express.static(path.join(__dirname, 'clientbuild')));
app.use('/login', express.static(path.join(__dirname, 'clientbuild', 'index.html')));
app.use('/history', express.static(path.join(__dirname, 'clientbuild', 'index.html')));
app.use('/admins', express.static(path.join(__dirname, 'clientbuild', 'index.html')));
app.use('/profile', express.static(path.join(__dirname, 'clientbuild', 'index.html')));
app.use('/tournament/*', express.static(path.join(__dirname, 'clientbuild', 'index.html')));
app.use('/static', express.static(path.join(__dirname, 'clientbuild/static')));
app.use('/static/*', express.static(path.join(__dirname, 'clientbuild/static')));
// #endregion
// #region PASSPORT / OAUTH
const passport = require('passport');
var userProfile;
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL
},
function(accessToken, refreshToken, profile, done) {
userProfile=profile;
return done(null, userProfile);
}
));
app.get('/auth/google',
passport.authenticate('google', { scope : ['profile', 'email']})
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/error' }),
async function(req, res) {
// Get user profile from passport
// This is retrieved from the callback url data ?code=...
let user = {
googleId: req.user.id,
name: req.user.displayName,
email: req.user.emails[0].value,
imgurl: req.user.photos[0].value,
asuraId: null,
}
// Check if user exists in database
tmdb.getUserByEmail(user.email)
.then(dbUser => {
user.asuraId = dbUser.id; // asuraId is the database id / primary key
if (dbUser.googleId) {
// User is already registered with google, simply log them in
req.session.user = dbUser;
} else {
// User is "preregistered" with email only, so complete the registration
// This step will register the name, img and googleId
tmdb.editUser(user.email, user).catch(err => console.log(err));
req.session.user = user;
}
res.redirect(process.env.AUTH_SUCCESS_REDIRECT);
return;
})
.catch(err => {
// User is not in the database at all, do not give them a session.
res.json({"status": "error", message: "Email is not in administrator list."});
return;
});
}
);
// #endregion
// #region API
api.get("/tournament/getTournaments", (req, res) => {
tmdb.getTournaments()
.then(tournaments => res.json({"status": "OK", "data": tournaments}))
.catch(err => res.json({"status": "error", "data": err}));
});
// #region tournament/:tournamentId
api.get("/tournament/:tournamentId", (req, res) => {
let tournamentId = req.params.tournamentId;
if (isNaN(tournamentId)) {
res.json({"status": "error", "data": "Invalid tournament id"});
return;
}
tmdb.getTournament(parseInt(tournamentId))
.then(tournament => res.json({"status": "OK", "data": tournament}))
.catch(err => res.json({"status": "error", "data": err}));
});
api.get("/tournament/:tournamentId/getMatches", (req, res) => {
let tournamentId = req.params.tournamentId;
if (isNaN(tournamentId)) {
res.json({"status": "error", "data": "tournamentId must be a number"});
return
}
tournamentId = parseInt(tournamentId);
tmdb.getMatchesByTournamentId(tournamentId)
.then(matches => res.send({"status": "OK", "data": matches}))
.catch(err => res.send({"status": "error", "data": err}));
});
api.get("/tournament/:tournamentId/getTeams", (req, res) => {
let tournamentId = req.params.tournamentId;
if (!tournamentId || isNaN(tournamentId)) {
res.json({"status": "error", "data": "tournamentId must be a number"});
return
}
tournamentId = parseInt(tournamentId);
tmdb.getTeamsByTournamentId(tournamentId)
.then(teams => res.send({"status": "OK", "data": teams}))
.catch(err => res.send({"status": "error", "data": err}));
});
api.post("/tournament/:tournamentId/edit", async (req, res) => {
if (!(await isSessionLoggedIn(req.session))) {
res.json({"status": "error", "data": "User is not logged in"});
return
}
let tournamentId = req.params.tournamentId;
if (isNaN(tournamentId)) {
res.json({"status": "error", "data": "tournamentId must be a number"});
return
}
tournamentId = parseInt(tournamentId);
let name = req.body.name;
let description = req.body.description;
let prize = req.body.prize;
let startDate = req.body.startDate;
let endDate = req.body.endDate;
console.log(startDate);
if (name == undefined || name == "" || description == undefined || description == "") {
res.json({"status": "error", "data": "name and description must be provided"});
return
}
if (startDate == undefined || endDate == undefined) {
res.json({"status": "error", "data": "startDate and endDate must be defined"});
return
}
try {
startDate = new Date(parseInt(startDate));
endDate = new Date(parseInt(endDate));
} catch (err) {
res.json({"status": "error", "data": "startDate and endDate must be valid dates"});
return
}
// let today = new Date();
// if (startDate < today) {
// res.json({"status": "error", "data": "startDate cannot be in the past"});
// return
// }
if (startDate > endDate) {
res.json({"status": "error", "data": "startDate cannot be after endDate"});
return
}
tmdb.editTournament(tournamentId, name, description, prize, startDate, endDate)
.then(msg => res.json({"status": "OK", "data": msg}))
.catch(err => res.json({"status": "error", "data": err}));
});
api.post("/tournament/:tournamentId/createTeam", async (req, res) => {
if (!(await isSessionLoggedIn(req.session))) {
res.json({"status": "error", "data": "User is not logged in"});
return
}
let tournamentId = req.params.tournamentId;
if (isNaN(tournamentId)) {
res.json({"status": "error", "data": "tournamentId must be a number"});
return;
}
tournamentId = parseInt(tournamentId);
let teamName = req.body.name;
if (teamName == undefined || teamName == "") {
res.json({"status": "error", "data": "teamName must be a non-empty string"});
return;
}
tmdb.createTeam(tournamentId, teamName)
.then(msg => res.json({"status": "OK", "data": msg}))
.catch(err => res.json({"status": "error", "data": err}));
});
api.delete("/tournament/:tournamentId", async (req, res) => {
if (!(await isSessionLoggedIn(req.session))) {
res.json({"status": "error", "data": "User is not logged in"});
return
}
let tournamentId = req.params.tournamentId;
if (isNaN(tournamentId)) {
res.json({"status": "error", "data": "tournamentId must be a number"});
return;
}
tournamentId = parseInt(tournamentId);
tmdb.deleteTournament(tournamentId)
.then(msg => res.json({"status": "OK", "data": msg}))
.catch(err => res.json({"status": "error", "data": err}));
});
// #endregion
// #region match/:matchId
api.get("/match/:matchId", (req, res) => {
let matchId = req.params.matchId;
if (isNaN(matchId)) {
res.json({"status": "error", "data": "matchId must be a number"});
return
}
matchId = parseInt(matchId);
tmdb.getMatch(matchId)
.then(match => res.send({"status": "OK", "data": match}))
.catch(err => res.send({"status": "error", "data": err}));
});
api.post("/match/:matchId/setWinner", async (req, res) => {
if (!(await isSessionLoggedIn(req.session))) {
res.json({"status": "error", "data": "User is not logged in"});
return
}
let matchId = req.params.matchId;
let winnerId = req.body.winnerId;
if (isNaN(matchId)) {
res.json({"status": "error", "data": "matchId must be a number"});
return
}
if (winnerId == undefined || (isNaN(winnerId) && winnerId != "null")) {
res.json({"status": "error", "data": "winnerId must be a number"});
return
}
matchId = parseInt(matchId);
if (winnerId == "null") {
winnerId = null;
} else {
winnerId = parseInt(winnerId);
}
tmdb.setMatchWinner(matchId, winnerId)
.then(match => res.send({"status": "OK", "data": match}))
.catch(err => res.send({"status": "error", "data": err}));
});
api.post("/match/:matchId/unsetContestant", async (req, res) => {
if (!(await isSessionLoggedIn(req.session))) {
res.json({"status": "error", "data": "User is not logged in"});
return
}
let matchId = req.params.matchId;
let contestantId = req.body.teamId;
if (isNaN(matchId)) {
res.json({"status": "error", "data": "matchId must be a number"});
return
}
if (contestantId == undefined || isNaN(contestantId)) {
res.json({"status": "error", "data": "contestantId must be a number"});
return
}
matchId = parseInt(matchId);
contestantId = parseInt(contestantId);
tmdb.unsetContestantAndWinner(matchId, contestantId)
.then(match => res.send({"status": "OK", "data": match}))
.catch(err => res.send({"status": "error", "data": err}));
});
// #endregion
// #region team/:teamId
api.get("/team/:teamId", (req, res) => {
let teamId = req.params.teamId;
if (isNaN(teamId)) {
res.json({"status": "error", "data": "teamId must be a number"});
return
}
teamId = parseInt(teamId);
tmdb.getTeam(teamId)
.then(match => res.send({"status": "OK", "data": match}))
.catch(err => res.send({"status": "error", "data": err}));
});
api.delete("/team/:teamId", async (req, res) => {
if (!(await isSessionLoggedIn(req.session))) {
res.json({"status": "error", "data": "User is not logged in"});
return
}
let teamId = req.params.teamId;
if (isNaN(teamId)) {
res.json({"status": "error", "data": "teamId must be a number"});
return
}
try {
teamId = parseInt(teamId);
} catch (err) {
res.json({"status": "error", "data": "teamId must be a number"});
return
}
tmdb.deleteTeam(teamId)
.then(match => res.send({"status": "OK", "data": match}))
.catch(err => res.send({"status": "error", "data": err}));
});
api.post("/team/:teamId/edit", async (req, res) => {
if (!(await isSessionLoggedIn(req.session))) {
res.json({"status": "error", "data": "User is not logged in"});
return
}
let teamId = req.params.teamId;
let teamName = req.body.name;
console.log(req.body);
if (isNaN(teamId)) {
res.json({"status": "error", "data": "teamId must be a number"});
return
}
if (teamName == undefined || teamName == "") {
res.json({"status": "error", "data": "teamName must be a non-empty string"});
return
}
teamId = parseInt(teamId);
tmdb.editTeam(teamId, teamName)
.then(match => res.send({"status": "OK", "data": match}))
.catch(err => res.send({"status": "error", "data": err}));
});
// #endregion
//Takes JSON body
api.post("/tournament/create", async (req, res) => {
if (!(await isSessionLoggedIn(req.session))) {
res.json({"status": "error", "data": "User is not logged in"});
return
}
//Check that req body is valid
if (req.body.name == undefined || req.body.name == "") {
res.json({"status": "error", "data": "No data supplied"});
return
}
//Check that req is json
// if (req.get("Content-Type") != "application/json") {
console.log(req.get("Content-Type"));
let name = req.body.name;
let description = req.body.description;
let prize = req.body.prize;
let teamLimit = req.body.teamLimit;
let startDate = req.body.startDate; //TODO: timezones, 2 hr skips
let endDate = req.body.endDate;
console.log(startDate, endDate);
if (name == undefined || name == "" || description == undefined || description == "") {
res.json({"status": "error", "data": "name and description must be provided"});
return
}
if (teamLimit == undefined ) {
res.json({"status": "error", "data": "teamLimit must be provided"});
return
}
try {
teamLimit = parseInt(teamLimit);
} catch (err) {
res.json({"status": "error", "data": "teamLimit must be a number"});
return
}
if (startDate == undefined || endDate == undefined) {
res.json({"status": "error", "data": "startDate and endDate must be defined"});
return
}
try {
startDate = new Date(parseInt(startDate));
endDate = new Date(parseInt(endDate));
} catch (err) {
res.json({"status": "error", "data": "startDate and endDate must be valid dates"});
return
}
let today = new Date();
if (startDate < today) {
res.json({"status": "error", "data": "startDate cannot be in the past"});
return
}
if (startDate > endDate) {
res.json({"status": "error", "data": "endDate must be later than startDate"});
return
}
console.log(startDate);
tmdb.createTournament(name, description, prize, startDate, endDate, teamLimit)
.then(msg => res.json({"status": "OK", "data": msg}))
.catch(err => res.json({"status": "error", "data": err}));
});
// #endregion
// #region users
function isSessionLoggedIn(session) {
return new Promise((resolve, reject) => {
if (process.env.DEBUG_ALLOW_ALL === "true") { resolve(true); return; }
if (session.user == undefined || session.user.googleId == undefined) {
return resolve(false);
}
let googleId = session.user.googleId;
tmdb.getUserByGoogleId(googleId)
.then(user => {return resolve(user != undefined) })
.catch(err => {resolve(false) });
});
}
function isSessionManager(session) {
return new Promise((resolve, reject) => {
if (process.env.DEBUG_ALLOW_ALL === "true") { resolve(true); return; }
if (session.user == undefined || session.user.googleId == undefined) {
return resolve(false);
}
let googleId = session.user.googleId;
tmdb.getUserByGoogleId(googleId)
.then(user => {return resolve(user.isManager) })
.catch(err => {resolve(false) });
});
}
api.get("/users/getSavedUser", (req, res) => {
if (!req.session.user) {
res.json({"status": "error", "data": "No user logged in"});
return
}
let googleId = req.session.user.googleId;
tmdb.getUserByGoogleId(googleId)
.then(user => res.json({"status": "OK", "data": user}))
.catch(err => res.json({"status": "error", "data": err}));
});
api.get("/users/getUsers", async (req, res) => {
if (!(await isSessionManager(req.session))) {
res.json({"status": "error", "data": "Not authorized"});
return
}
tmdb.getUsers()
.then(users => res.json({"status": "OK", "data": users}))
.catch(err => res.json({"status": "error", "data": err}));
});
api.post("/users/createBlank", async (req, res) => {
if (!(await isSessionManager(req.session))) {
res.json({"status": "error", "data": "Not authorized"});
return
}
let email = req.body.email;
// Check if the user already exists
tmdb.getUserByEmail(email)
.then(user => {
res.json({"status": "error", "data": "User already exists", user: user});
})
.catch(err => {
console.log(err);
if (err == "No such user exists") {
// Create a new user
tmdb.createUserBlank(email)
.then(user => {
res.json({"status": "OK", "data": user});
})
.catch(err => {
res.json({"status": "error", "data": err});
});
} else {
res.json({"status": "error", "data": err});
}
});
});
api.post("/users/:asuraId/changeManagerStatus", async (req, res) => {
if (!(await isSessionManager(req.session))) {
res.json({"status": "error", "data": "Not authorized"});
return
}
let asuraId = req.params.asuraId;
let isManager = req.body.isManager;
tmdb.changeManagerStatus(asuraId, isManager)
.then(msg => res.json({"status": "OK", "data": msg}))
.catch(err => res.json({"status": "error", "data": err}));
});
api.delete("/users/:asuraId", async (req, res) => {
if (!(await isSessionManager(req.session))) {
res.json({"status": "error", "data": "Not authorized"});
return
}
let asuraId = req.params.asuraId;
tmdb.deleteUser(asuraId)
.then(msg => res.json({"status": "OK", "data": msg}))
.catch(err => res.json({"status": "error", "data": err}));
});
api.get("/users/logout", (req, res) => {
req.session.destroy();
res.redirect(process.env.AUTH_SUCCESS_REDIRECT);
});
// Debugging functions, disabled on purpouse
// api.get("/users/getSessionUser", (req, res) => {
// if (req.session.user) {
// res.json({"status": "OK", "data": req.session.user});
// } else {
// res.json({"status": "error", "data": "No user logged in"});
// }
// });
// api.get("/dumpsession", async (req, res) => {
// let out = {};
// out.session = req.session;
// out.header = req.headers;
// out.isLoggedIn = await isSessionLoggedIn(req.session);
// out.isManager = await isSessionManager(req.session);
// console.log(out);
// res.json(out);
// });
// #endregion

View File

@ -0,0 +1,105 @@
-- WARNING: Will delete EVERYTHING in the database!
DROP TABLE IF EXISTS matches;
DROP TABLE IF EXISTS teams;
DROP TABLE IF EXISTS tournaments;
DROP TABLE IF EXISTS users;
-- Create the tables
CREATE TABLE tournaments (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name TEXT NOT NULL,
description TEXT,
prize TEXT,
teamLimit INTEGER NOT NULL,
startTime DATETIME NOT NULL,
endTime DATETIME NOT NULL
);
CREATE TABLE teams (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
tournamentId INTEGER NOT NULL,
name TEXT NOT NULL,
FOREIGN KEY (tournamentId) REFERENCES tournaments (id) ON DELETE CASCADE
);
CREATE TABLE matches (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
tournamentId INTEGER NOT NULL,
parentMatchId INTEGER,
team1Id INTEGER,
team2Id INTEGER,
winnerId INTEGER,
tier INTEGER,
FOREIGN KEY (tournamentId) REFERENCES tournaments (id) ON DELETE CASCADE,
FOREIGN KEY (team1Id) REFERENCES teams (id) ON DELETE SET NULL,
FOREIGN KEY (team2Id) REFERENCES teams (id) ON DELETE SET NULL,
FOREIGN KEY (winnerId) REFERENCES teams (id) ON DELETE SET NULL
);
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
googleId TEXT,
name TEXT,
email TEXT NOT NULL,
isManager BOOLEAN NOT NULL
);
-- Example data (Two tournaments, 4 teams, single elimination)
INSERT INTO tournaments (name, description, prize, startTime, endTime, teamLimit) VALUES ('Tournament 1', 'First tournament, single elimination', '300 000 points', '2022-04-29 16:00:00', '2022-04-29 20:00:00', 4);
INSERT INTO tournaments (name, description, prize, startTime, endTime, teamLimit) VALUES ('Tournament 2', 'Second tournament, four teams', '450 000 points', '2022-04-29 09:00:00', '2022-04-29 10:30:00', 8);
INSERT INTO tournaments (name, description, prize, startTime, endTime, teamLimit) VALUES ('Tournament 3', 'Previous tournament, it is done', '200 000 points', '2022-04-24 12:00:00', '2022-04-25 12:00:00', 4);
INSERT INTO teams (tournamentId, name) VALUES (1, 'Fnatic'); -- 1
INSERT INTO teams (tournamentId, name) VALUES (1, 'Cloud 9'); -- 2
INSERT INTO teams (tournamentId, name) VALUES (1, 'Team Liquid'); -- 3
INSERT INTO teams (tournamentId, name) VALUES (1, 'LDLC'); -- 4
INSERT INTO teams (tournamentId, name) VALUES (2, 'Astralis'); -- 5
INSERT INTO teams (tournamentId, name) VALUES (2, 'Entropiq'); -- 6
INSERT INTO teams (tournamentId, name) VALUES (2, 'Team Vitality'); -- 7
INSERT INTO teams (tournamentId, name) VALUES (2, 'Godsent'); -- 8
INSERT INTO teams (tournamentId, name) VALUES (2, 'Team Secret'); -- 9
INSERT INTO teams (tournamentId, name) VALUES (2, 'Virtus.pro'); -- 10
INSERT INTO teams (tournamentId, name) VALUES (2, 'Natus Vincere'); -- 11
INSERT INTO teams (tournamentId, name) VALUES (2, 'FaZe'); -- 12
INSERT INTO teams(tournamentId, name) VALUES (3, 'Fnatic'); -- 13
INSERT INTO teams(tournamentId, name) VALUES (3, 'Cloud 9'); -- 14
INSERT INTO teams(tournamentId, name) VALUES (3, 'Team Liquid'); -- 15
INSERT INTO teams(tournamentId, name) VALUES (3, 'LDLC'); -- 16
-- tournament 1 --
-- Final match
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier) VALUES (1, NULL, NULL, NULL, 0); -- 1
-- Semi-finals
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier) VALUES (1, 1, 1, 2, 1); -- 2
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier) VALUES (1, 1, 3, 4, 1); -- 3
-- tournament 2 --
-- Final match
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier) VALUES (2, NULL, NULL, NULL, 0); -- 4
-- Semi-finals
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier) VALUES (2, 4, NULL, NULL, 1); -- 5
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier) VALUES (2, 4, NULL, NULL, 1); -- 6
-- Quarter-finals
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier) VALUES (2, 5, 5, 6, 2); -- 7
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier) VALUES (2, 5, 7, 8, 2); -- 8
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier) VALUES (2, 6, 9, 10, 2); -- 9
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier) VALUES (2, 6, 11, 12, 2); -- 10
-- tournament 3 --
-- Final match
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier, winnerId) VALUES (3, NULL, 14, 15, 0, 14); -- 11
-- Semi-finals
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier, winnerId) VALUES (3, 11, 13, 14, 1, 14); -- 12
INSERT INTO matches (tournamentId, parentMatchId, team1Id, team2Id, tier, winnerId) VALUES (3, 11, 15, 16, 1, 15); -- 13
-- Users
INSERT INTO users (email, isManager) VALUES ('felixalbrigtsen@gmail.com', 1);
INSERT INTO users (email, isManager) VALUES ('kriloneri@gmail.com', 1);
INSERT INTO users (email, isManager) VALUES ('limboblivion@gmail.com', 1);
INSERT INTO users (email, isManager) VALUES ('jonas.haugland98@gmail.com', 1);

View File

@ -0,0 +1,3 @@
autossh -L 3306:mysql.stud.ntnu.no:3306 isvegg -N &
npm start

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,24 @@
"description": "DCST1008 Project - Server - Asura Tournament Management System",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"start": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1",
"initdb": "mysql -h mysql.stud.ntnu.no -u felixalb_sysut -p felixalb_asura < ./management/initDB.sql"
},
"author": "felixalb, kristoju, jonajha, krisleri",
"license": "ISC"
"license": "ISC",
"dependencies": {
"dotenv": "^16.0.0",
"ejs": "^3.1.6",
"express": "^4.17.3",
"express-log-url": "^1.5.1",
"express-session": "^1.17.2",
"mysql": "^2.18.1",
"passport": "^0.5.2",
"passport-google-oauth": "^2.0.0",
"sequelize": "^6.17.0"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}

View File

@ -0,0 +1,9 @@
<html>
<head>
<title>test</title>
</head>
<body>
<h1>Replace with files from client build</h1>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Tournament</h1>
<div id="output"></div>
<script>
window.TOURNAMENT = JSON.parse('<%- JSON.stringify(tournament) %>');
document.getElementById("output").innerHTML = JSON.stringify(TOURNAMENT);
</script>
</body>
</html>

600
src/server/tmdb.js Normal file
View File

@ -0,0 +1,600 @@
// TMDB - Tournament Manager DataBase
// Handles all the database operations for the Tournament Manager
// Exports the following functions:
module.exports = {
getMatchesByTournamentId: getMatchesByTournamentId,
getTournaments: getTournaments,
getTournament, getTournament,
getTeam: getTeam,
createTeam: createTeam,
editTeam: editTeam,
deleteTeam: deleteTeam,
getMatch: getMatch,
setMatchWinner: setMatchWinner,
unsetContestantAndWinner: unsetContestantAndWinner,
createTournament: createTournament,
deleteTournament: deleteTournament,
editTournament: editTournament,
getTeamsByTournamentId: getTeamsByTournamentId,
getUsers: getUsers,
getUserByEmail: getUserByEmail,
getUserByGoogleId: getUserByGoogleId,
createUserBlank: createUserBlank,
changeManagerStatus: changeManagerStatus,
deleteUser, deleteUser,
editUser: editUser,
}
const mysql = require("mysql");
// #region Database setup
let db_config = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE
};
let connection
// https://stackoverflow.com/a/20211143
function handleDisconnect() {
connection = mysql.createConnection(db_config); // Recreate the connection
connection.connect(function(err) {
if(err) {
console.log('error when connecting to db:', err);
setTimeout(handleDisconnect, 2000); // We introduce a delay before attempting to reconnect,
} // to avoid a hot loop, and to allow our node script to
}); // process asynchronous requests in the meantime.
// If you're also serving http, display a 503 error.
connection.on('error', function(err) {
console.log('db error', err);
if(err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually
handleDisconnect(); // lost due to either server restart, or a
} else { // connnection idle timeout (the wait_timeout
throw err; // server variable configures this)
}
});
}
handleDisconnect(); //Start the auto-restarting connection
function escapeString(str) {
// return mysql.escape(str);
return str;
}
// #endregion
// #region match
// Returns the match of the exact given id.
function getMatch(matchId) {
return new Promise(function(resolve, reject) {
connection.query("SELECT * FROM matches WHERE id = ?", [escapeString(matchId)], (err, matches) => {
if (err) {
reject(err);
} else {
if (matches.length == 0) {
reject("No such match exists");
}
let match = matches[0];
resolve(match);
}
});
});
}
// Removes a given team from a given match. This is done by setting the teamId-property containing the given team to null.
async function unsetContestant(matchId, teamId) {
let match = await getMatch(matchId);
return new Promise(function(resolve, reject) {
if (match.team1Id == teamId) {
connection.query("UPDATE matches SET team1Id = NULL WHERE id = ?", [escapeString(matchId)], (err, result) => {
if (err) { console.log(err); reject(err); }
resolve();
});
} else if (match.team2Id == teamId) {
connection.query("UPDATE matches SET team2Id = NULL WHERE id = ?", [escapeString(matchId)], (err, result) => {
if (err) { console.log(err); reject(err); }
resolve();
});
} else {
console.log("Error: Team not found in match");
reject("Error: Team not found in match");
}
});
}
async function insertContestant(matchId, teamId, prevMatchId) {
let match = await getMatch(matchId);
connection.query("SELECT * FROM matches WHERE parentMatchId = ?", [escapeString(matchId)], (err, childMatches) => {
if (err) { console.log(err); }
let isFirst = prevMatchId == childMatches[0].id;
if (isFirst) {
if (match.team1Id != null) { return; }
connection.query("UPDATE matches SET team1Id = ? WHERE id = ?",
[escapeString(teamId), escapeString(matchId)], (err, sets) => {
if (err) { console.log(err); }
});
} else {
if (match.team2Id != null) { return; }
connection.query("UPDATE matches SET team2Id = ? WHERE id = ?",
[escapeString(teamId), escapeString(matchId)], (err, sets) => {
if (err) { console.log(err); }
});
}
});
}
async function setMatchWinner(matchId, winnerId) {
return new Promise(async function(resolve, reject) {
let match = await getMatch(matchId);
if (winnerId != match.team1Id && winnerId != match.team2Id && winnerId != null) {
reject("Winner id must be one of the teams in the match, or null");
return;
}
let oldWinnerId = match.winnerId;
connection.query("UPDATE matches SET winnerId = ? WHERE id = ?",[escapeString(winnerId), escapeString(matchId)], async (err, sets) => {
if (err) {
reject(err);
return;
}
// Remove the old winner from the next match
if (oldWinnerId != null && match.parentMatchId != null) {
let parentMatch = await getMatch(match.parentMatchId);
// Do not undo the match if the parent match is played and finished
if (parentMatch.winnerId != null) {
connection.query("UPDATE matches SET winnerId = ? WHERE id = ?", [escapeString(oldWinnerId), escapeString(match.parentMatchId)], (err, sets) => {});
reject("The next match is already played");
return;
}
await unsetContestant(match.parentMatchId, oldWinnerId);
}
if (match.parentMatchId != null && winnerId != null) {
insertContestant(match.parentMatchId, winnerId, matchId);
}
resolve(getMatch(matchId));
});
});
}
async function unsetContestantAndWinner(matchId, teamId) {
let match = await getMatch(matchId);
return new Promise(function(resolve, reject) {
// Find what the child match that supplied the team
connection.query("SELECT * FROM matches WHERE parentMatchId = ? AND winnerId = ?", [matchId, teamId], (err, childMatches) => {
if (err) { console.log(err); reject(err); }
if (childMatches.length != 1) {
reject("Error: Could not find the correct child match");
return;
}
let childMatch = childMatches[0];
// Remove the winner from the child match
setMatchWinner(childMatch.id, null)
.then(() => { resolve(); })
.catch(err => { reject(err); });
});
});
}
// #endregion
// #region tournament
function getTournaments() {
return new Promise(function(resolve, reject) {
// 1. Get the list of tournament IDs
// 2. getTournament() for each ID
// 3. Return list of tournaments
let tournamentList = [];
connection.query("SELECT id FROM tournaments", async (err, tournamentIds) => {
if (err) {
console.log(err);
reject(err);
} else {
let tournaments = await Promise.all(tournamentIds.map(async function(tournament){
return await getTournament(tournament.id);
}));
resolve(tournaments);
}
});
});
}
function getTournament(tournamentId) {
// 1. Get the tournament
// 2. Get all teams associated with the tournament
// 3. Associate the teams with the tournament
// 4. Return the tournament
return new Promise(function(resolve, reject) {
connection.query("SELECT * FROM tournaments WHERE id = ?", [escapeString(tournamentId)], (err, tournaments) => {
if (err) {
console.log(err);
reject(err);
} else {
if (tournaments.length == 0) {
reject("No such tournament exists");
return
}
getTeamsByTournamentId(tournamentId)
.catch(err => reject(err))
.then(teams => {
let tournament = tournaments[0];
tournament.teamCount = teams.length;
resolve(tournament);
});
}
});
});
}
function deleteTournament(tournamentId) {
return new Promise(function(resolve, reject) {
connection.query("DELETE FROM tournaments WHERE id = ?", [escapeString(tournamentId)], (err, result) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
function getMatchesByTournamentId(tournamentId) {
return new Promise(function(resolve, reject) {
connection.query("SELECT * FROM matches WHERE tournamentId = ?", [escapeString(tournamentId)], (err, matches) => {
if (err) {
console.log(err);
reject(err);
} else {
resolve(matches);
}
});
});
}
function createMatch(tournamentId, parentMatchId, tier) {
//Returns Promise<int> witht the inserted ID.
return new Promise(function(resolve, reject) {
connection.query("INSERT INTO matches (tournamentId, parentMatchId, tier) VALUES (?, ?, ?)",
[escapeString(tournamentId), escapeString(parentMatchId), escapeString(tier)], (err, result) => {
if (err) {
console.log(err);
reject(err);
} else {
resolve(result.insertId);
}
});
});
}
function createTournament(name, description, prize, startDate, endDate, teamLimit) {
startDate = startDate.toISOString().slice(0, 19).replace('T', ' ');
endDate = endDate.toISOString().slice(0, 19).replace('T', ' ');
return new Promise(function(resolve, reject) {
connection.query("INSERT INTO tournaments (name, description, prize, startTime, endTime, teamLimit) VALUES (?, ?, ?, ?, ?, ?)",
[escapeString(name), escapeString(description), escapeString(prize), startDate, endDate, teamLimit], async (err, sets) => {
if (err) {
console.log(err);
reject(err);
} else {
// Create the matches for the tournament
let matchIds = [];
let tournamentId = sets.insertId;
let tiers = Math.log2(teamLimit);
for (let tier = 0; tier < tiers; tier++) {
let matchCount = Math.pow(2, tier);
for (let i = 0; i < matchCount; i++) {
let parentMatchId = null;
if (tier > 0) {
let parentMatchIndex = Math.pow(2, tier - 1) + Math.floor((i - (i % 2)) / 2) - 1;
parentMatchId = matchIds[parentMatchIndex];
}
let newMatchId = await createMatch(tournamentId, parentMatchId, tier);
matchIds.push(newMatchId);
}
}
resolve({message: "Tournament created", tournamentId: sets.insertId});
}
});
});
}
function editTournament(tournamentId, name, description, prize, startDate, endDate) {
startDate = startDate.toISOString().slice(0, 19).replace('T', ' ');
endDate = endDate.toISOString().slice(0, 19).replace('T', ' ');
return new Promise(function(resolve, reject) {
connection.query("UPDATE tournaments SET name = ?, description = ?, prize = ?, startTime = ?, endTime = ? WHERE id = ?",
[escapeString(name), escapeString(description), escapeString(prize), startDate, endDate, escapeString(tournamentId)], (err, sets) => {
if (err) {
console.log(err);
reject(err);
} else {
resolve("Tournament updated");
}
});
});
}
function getTeamsByTournamentId(tournamentId) {
return new Promise(function(resolve, reject) {
connection.query("SELECT * FROM teams WHERE tournamentId = ?", [escapeString(tournamentId)], (err, teams) => {
if (err) {
console.log(err);
reject(err);
} else {
resolve(teams);
}
});
});
}
// #endregion
// #region team
function getTeam(teamId) {
return new Promise(function(resolve, reject) {
connection.query("SELECT * FROM teams WHERE id = ?", [escapeString(teamId)], (err, teams) => {
if (err) {
console.log(err);
reject(err);
} else {
if (teams.length == 0) {
reject("No such team exists");
}
resolve(teams[0]);
}
});
});
}
async function editTeam(teamId, name) {
let team = await getTeam(teamId);
if (!team) {
return Promise.reject("No such team exists");
}
if (team.name == name) {
return {message: "Team name unchanged"};
}
return new Promise(function(resolve, reject) {
connection.query("UPDATE teams SET name = ? WHERE id = ?", [escapeString(name), escapeString(teamId)], (err, sets) => {
if (err) {
console.log(err);
reject(err);
} else {
resolve("Team updated");
}
});
});
}
async function createTeam(tournamentId, name) {
//Check that the tournament exists
let tournament = await getTournament(tournamentId);
return new Promise(function(resolve, reject) {
if (!tournament) {
reject("No such tournament exists");
return;
}
if (tournament.teamLimit <= tournament.teamCount) {
reject("Tournament is full");
return;
}
connection.query("INSERT INTO teams (tournamentId, name) VALUES (?, ?)", [escapeString(tournamentId), escapeString(name)], async (err, sets) => {
if (err) {
console.log(err);
reject(err);
} else {
await assignFirstMatch(sets.insertId, tournamentId);
resolve({message: "Team created", teamId: sets.insertId});
}
});
});
}
function deleteTeam(teamId) {
return new Promise(async function(resolve, reject) {
connection.query("DELETE FROM teams WHERE id = ?", [escapeString(teamId)], (err, sets) => {
if (err) {
console.log(err);
reject(err);
} else {
resolve("Team deleted");
}
});
});
}
//Private function, assigns a starting match to the given team
async function assignFirstMatch(teamId, tournamentId) {
let tournament = await getTournament(tournamentId);
let matches = await getMatchesByTournamentId(tournamentId);
let highTier = Math.log2(tournament.teamLimit)-1;
let highTierMatches = matches.filter(match => match.tier == highTier);
return new Promise(function(resolve, reject) {
for (let match of highTierMatches) {
if (match.team1Id == null) {
connection.query("UPDATE matches SET team1Id = ? WHERE id = ?", [escapeString(teamId), escapeString(match.id)], (err, sets) => {
if (err) {
console.log(err);
reject(err);
} else {
resolve("Team assigned to match " + match.id);
}
});
return
} else if (match.team2Id == null) {
connection.query("UPDATE matches SET team2Id = ? WHERE id = ?", [escapeString(teamId), escapeString(match.id)], (err, sets) => {
if (err) {
console.log(err);
reject(err);
} else {
resolve("Team assigned to match " + match.id);
}
});
return
}
}
reject("Could not assign team to any matches");
});
}
// #endregion
// #region users
function getUsers () {
return new Promise(function(resolve, reject) {
connection.query("SELECT * FROM users", (err, userRows) => {
if (err) {
console.log(err);
reject(err);
} else {
let users = [];
userRows.forEach((userRow, index) => {
let user = results=JSON.parse(JSON.stringify(userRow))
user.isManager = user.isManager == 1;
user.asuraId = user.id;
user.id = undefined;
users.push(user);
});
resolve(users);
}
});
});
}
function getUserByGoogleId(googleId) {
return new Promise(function(resolve, reject) {
connection.query("SELECT * FROM users WHERE googleId = ?", [escapeString(googleId)], (err, users) => {
if (err) {
console.log(err);
reject(err);
} else {
if (!users || users.length == 0) {
reject("No such user exists");
}
try {
users[0].isManager = users[0].isManager == 1;
users[0].asuraId = users[0].id;
users[0].id = undefined;
resolve(users[0]);
} catch (e) {
reject("No such user exists");
}
}
});
});
}
function getUserByEmail(email) {
return new Promise(function(resolve, reject) {
connection.query("SELECT * FROM users WHERE email = ?", [escapeString(email)], (err, users) => {
if (err) {
console.log(err);
reject(err);
} else {
if (users.length == 0) {
reject("No such user exists");
return;
}
users[0].isManager = users[0].isManager == 1;
users[0].asuraId = users[0].id;
users[0].id = undefined;
resolve(users[0]);
}
});
});
}
function createUserBlank(email) {
return new Promise(function(resolve, reject) {
//Check that the user doesn't already exist
getUserByEmail(email).then(user => {
reject("No such user exists");
}).catch(err => {
if (err != "No such user exists") {
console.log(err);
reject(err);
return;
}
// Create a user, with only an email address
connection.query("INSERT INTO users (email, isManager) VALUES (?, FALSE)", [escapeString(email)], (err, sets) => {
if (err) {
console.log(err);
reject(err);
} else {
resolve({message: "User Created", userId: sets.insertId});
}
});
});
});
}
function editUser(email, user) {
return new Promise(function(resolve, reject) {
if (user.isManager == undefined) {
user.isManager = false;
}
connection.query("UPDATE users SET googleId = ?, name = ?, isManager = ? WHERE email = ?", [escapeString(user.googleId), escapeString(user.name), escapeString(user.isManager), escapeString(email)], (err, sets) => {
if (err) {
console.log(err);
reject(err);
} else {
console.log(sets);
resolve("User updated");
}
});
});
}
function changeManagerStatus(userId, isManager) {
return new Promise(function(resolve, reject) {
let isManagerInt = (isManager === true || isManager === "true") ? 1 : 0;
connection.query("UPDATE users SET isManager = ? WHERE id = ?", [isManagerInt, escapeString(userId)], (err, sets) => {
if (err) {
console.log(err);
reject(err);
return
}
if (sets.affectedRows == 0) {
reject("No such user exists");
return
}
resolve("User updated");
});
});
}
function deleteUser(userId) {
return new Promise(function(resolve, reject) {
connection.query("DELETE FROM users WHERE id = ?", [escapeString(userId)], (err, sets) => {
if (err) {
console.log(err);
reject(err);
return;
}
if (sets.affectedRows == 0) {
reject("No such user exists");
return;
}
resolve("User deleted");
});
});
}
// #endregion