// 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, 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; 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.length == 0) { reject("No such user exists"); } users[0].isManager = users[0].isManager == 1; resolve(users[0]); } }); }); } 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; 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) { 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 userIsManager(userId) { getUser(userId) .then(user => { return user.isManager; }) .catch(err => { console.log(err); return false; }); } // #endregion