myheats

Live heats, scoring and leaderboard for sport events
git clone https://git.in0rdr.ch/myheats.git
Log | Files | Refs | Pull requests | README | LICENSE

commit ff002c4fba9fa904d05d5154b78ce05f2f25997e
parent 2f5a59c6f72902d99f1cfdc4dab42b81c0d51114
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date:   Sat, 28 Sep 2024 09:46:57 +0200

feat: switch/case for routing

Diffstat:
Msrc/api/server.cjs | 919+++++++++++++++++++++++++++++++++++++++----------------------------------------
1 file changed, 454 insertions(+), 465 deletions(-)

diff --git a/src/api/server.cjs b/src/api/server.cjs @@ -108,516 +108,505 @@ server.on('request', async (req, res) => { if (req.method === 'OPTIONS') { res.end() } else if (req.method === 'GET') { - if (url.pathname === '/v1/healthz') { - res.end(JSON.stringify({ - message: 'egg fried rice ๐Ÿš fuiyooh!', - })); - } else if (url.pathname === '/v1/auth/verify') { - try { - const token = search_params.get('token'); - - // lookup token in the database - const users = await db.lookupToken(token); - if (users.length < 1) { - throw {message: "Token not found"} - } - - const user = users[0] - - console.log(" Token expires_at:", users[0].expires_at); - console.log(" Time now, lah:", new Date()); - - // check expiration date of token in the database - if (new Date(users[0].expires_at) < new Date()) { - throw new Error('Token expired.. tthink about yu saad live ๐Ÿ˜ฟ') - } - - // verify token signature - const v = jwt.verify(token, jwt_secret); - await db.invalidateToken(token); - console.log(" Verified token", v); - - if (v.email === user.email) { - res.end(JSON.stringify({ - message: 'Uncle roger verified ๐Ÿค fuuiiyooh!', - data: user, - })); - } else { - throw new Error("Token signature invalid") - } - } catch (error) { - serverError(res, error); - } - } else if (url.pathname === '/v1/leaderboard/allHeats') { - try { - const heats = await db.allHeats() - + switch(url.pathname) { + case '/v1/healthz': res.end(JSON.stringify({ - message: 'All heats', - data: heats, + message: 'egg fried rice ๐Ÿš fuiyooh!', })); - } catch(error) { - serverError(res, error); - } - } else if (url.pathname === '/v1/leaderboard/allAthletes') { - try { - const athletes = await db.allAthletes() - - res.end(JSON.stringify({ - message: 'All athletes', - data: athletes, - })); - } catch(error) { - serverError(res, error); - } - } else { - const pathExists = paths.find((i) => i === url.pathname); - if (pathExists) { - // wrong method for this path - notAllowed(res, req.method); - } else { - notFound(res, url.pathname); - } - } - } else if (req.method === 'POST') { - if (url.pathname === '/v1/echo') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', () => { - body = JSON.stringify({ - message: 'Uncle roger knows do egg fried rice since 3 years old, lah', - data: Buffer.concat(body).toString(), - }); - res.end(body); - }) - } else if (url.pathname === '/v1/auth/requestMagicLink') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); + case '/v1/auth/verify': try { - if (b.length < 1) { - throw new Error("Empty request body") - } - input = JSON.parse(b); - console.log(' Link request for:', input.email); + const token = search_params.get('token'); - const users = await db.getUser(input.email); + // lookup token in the database + const users = await db.lookupToken(token); if (users.length < 1) { - throw new Error("User not found") + throw {message: "Token not found"} } const user = users[0] - // create magic link - const exp = new Date(Date.now() + 1000 * jwt_ttl) - const token = jwt.sign({ email: user.email }, jwt_secret, { - expiresIn: 1000 * jwt_ttl, - }) - const magic_link = `${redirect_uri}?token=${token}` - const magic_href = `${api_uri}:${api_port}/v1/auth/verify?token=${token}` - console.log("๐Ÿ”—Magic href:", magic_href) - - // save token with expiration time - await db.saveToken(user.id, token, exp) - - // send magic link to user - await transport.sendMail({ - from: process.env.SMTP_FROM, - to: user.email, - subject: 'MyHeats Magic Link', - //text: `Click here โœจ to log in: ${magic_link}`, - html: `Click <a href="${magic_link}">here</a> โœจ to log in.`, - }) + console.log(" Token expires_at:", users[0].expires_at); + console.log(" Time now, lah:", new Date()); - res.end(JSON.stringify({ - message: 'Magic link requested', - })); - } catch (error) { - serverError(res, error); - } - }) - } else if (url.pathname === '/v1/leaderboard/distinctStartlist') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") + // check expiration date of token in the database + if (new Date(users[0].expires_at) < new Date()) { + throw new Error('Token expired.. tthink about yu saad live ๐Ÿ˜ฟ') } - input = JSON.parse(b); - console.log(' distinctStartlist request with headIds:', input.heat_ids); - const startlist = await db.distinctStartlist(input.heat_ids); - - if (startlist.length < 1) { - throw new Error("Startlist not found") + // verify token signature + const v = jwt.verify(token, jwt_secret); + await db.invalidateToken(token); + console.log(" Verified token", v); + + if (v.email === user.email) { + res.end(JSON.stringify({ + message: 'Uncle roger verified ๐Ÿค fuuiiyooh!', + data: user, + })); + } else { + throw new Error("Token signature invalid") } - res.end(JSON.stringify({ - message: 'Distinct athletes for multiple heats', - data: startlist, - })); - } catch(error) { - serverError(res, error); - } - }) - } else if (url.pathname === '/v1/leaderboard/startlistWithAthletes') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") - } - input = JSON.parse(b); - console.log(' startlistWithAthletes request with headId:', input.heat_id); - - const startlist = await db.startlistWithAthletes(input.heat_id); - - if (startlist.length < 1) { - throw new Error("No athletes for this startlist") - } - res.end(JSON.stringify({ - message: 'Startlist with athletes for heat', - data: startlist, - })); - } catch(error) { + } catch (error) { serverError(res, error); } - }) - } else if (url.pathname === '/v1/leaderboard/scoresForHeatAndAthlete') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); + case '/v1/leaderboard/allHeats': try { - if (b.length < 1) { - throw new Error("Empty request body") - } - input = JSON.parse(b); - console.log(' scoresForHeatAndAthlete request with heat and athlete:', - input.heat, input.athlete); - - const scores = await db.scoresForHeatAndAthlete( - input.heat, - input.athlete - ) + const heats = await db.allHeats() - if (scores.length < 1) { - throw new Error("No scores found") - } res.end(JSON.stringify({ - message: 'Scores for heat and athlete', - data: scores, + message: 'All heats', + data: heats, })); } catch(error) { serverError(res, error); } - }) - } else if (url.pathname === '/v1/leaderboard/scoreSummaryForHeatAndAthlete') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); + case '/v1/leaderboard/allAthletes': try { - if (b.length < 1) { - throw new Error("Empty request body") - } - input = JSON.parse(b); - console.log(' scoreSummaryForHeatAndAthlete request with heat and athlete:', - input.heat, input.athlete); - - const summary = await db.scoreSummaryForHeatAndAthlete( - input.heat, - input.athlete - ) + const athletes = await db.allAthletes() - if (summary.length < 1) { - throw new Error("Score summary not found") - } res.end(JSON.stringify({ - message: 'Score summary for heat and athlete', - data: summary[0], + message: 'All athletes', + data: athletes, })); } catch(error) { serverError(res, error); } - }) - } else if (url.pathname === '/v1/leaderboard/getScore') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") - } - input = JSON.parse(b); - console.log(' GetScore request for:', input); - - const scores = await db.getScore( - input.heat, - input.athlete, - input.judge - ); - - if (scores.length < 1) { - throw new Error("Score not found") - } - - res.end(JSON.stringify({ - message: 'Requested score for heat, user and judge', - data: scores[0], - })); - } catch (error) { - serverError(res, error); - } - }) - } else if (url.pathname === '/v1/leaderboard/setScore') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") - } - input = JSON.parse(b); - console.log(' SetScore request for:', input); - - const scores = await db.setScore( - input.heat, - input.athlete, - input.judge, - input.score, - ); - - if (scores.length < 1) { - throw new Error("Score not updated") - } - - res.end(JSON.stringify({ - message: 'Score update for heat, user and judge', - data: scores[0], - })); - } catch (error) { - serverError(res, error); + default: + const pathExists = paths.find((i) => i === url.pathname); + if (pathExists) { + // wrong method for this path + notAllowed(res, req.method); + } else { + notFound(res, url.pathname); } - }) - } else if (url.pathname === '/v1/leaderboard/newHeat') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") + } // end switch + } else if (req.method === 'POST') { + let body = []; + + switch(url.pathname) { + case '/v1/echo': + req.on('data', chunk => { + body.push(chunk); + }).on('end', () => { + body = JSON.stringify({ + message: 'Uncle roger knows do egg fried rice since 3 years old, lah', + data: Buffer.concat(body).toString(), + }); + res.end(body); + }) + case '/v1/auth/requestMagicLink': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' Link request for:', input.email); + + const users = await db.getUser(input.email); + if (users.length < 1) { + throw new Error("User not found") + } + + const user = users[0] + + // create magic link + const exp = new Date(Date.now() + 1000 * jwt_ttl) + const token = jwt.sign({ email: user.email }, jwt_secret, { + expiresIn: 1000 * jwt_ttl, + }) + const magic_link = `${redirect_uri}?token=${token}` + const magic_href = `${api_uri}:${api_port}/v1/auth/verify?token=${token}` + console.log("๐Ÿ”—Magic href:", magic_href) + + // save token with expiration time + await db.saveToken(user.id, token, exp) + + // send magic link to user + await transport.sendMail({ + from: process.env.SMTP_FROM, + to: user.email, + subject: 'MyHeats Magic Link', + //text: `Click here โœจ to log in: ${magic_link}`, + html: `Click <a href="${magic_link}">here</a> โœจ to log in.`, + }) + + res.end(JSON.stringify({ + message: 'Magic link requested', + })); + } catch (error) { + serverError(res, error); } - input = JSON.parse(b); - console.log(' newHeat request for:', input); - - const heats = await db.newHeat( - input.name, - input.location, - input.planned_start, - ); - if (heats.length < 1) { - throw new Error("Heat not created") + }) + case '/v1/leaderboard/distinctStartlist': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' distinctStartlist request with headIds:', input.heat_ids); + + const startlist = await db.distinctStartlist(input.heat_ids); + + if (startlist.length < 1) { + throw new Error("Startlist not found") + } + res.end(JSON.stringify({ + message: 'Distinct athletes for multiple heats', + data: startlist, + })); + } catch(error) { + serverError(res, error); } - - res.end(JSON.stringify({ - message: 'New heat', - data: heats[0], - })); - } catch (error) { - serverError(res, error); - } - }) - } else if (url.pathname === '/v1/leaderboard/getHeat') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") + }) + case '/v1/leaderboard/startlistWithAthletes': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' startlistWithAthletes request with headId:', input.heat_id); + + const startlist = await db.startlistWithAthletes(input.heat_id); + + if (startlist.length < 1) { + throw new Error("No athletes for this startlist") + } + res.end(JSON.stringify({ + message: 'Startlist with athletes for heat', + data: startlist, + })); + } catch(error) { + serverError(res, error); } - input = JSON.parse(b); - console.log(' getHeat request for:', input); - - const heats = await db.getHeat( - input.heat_id, - ); - if (heats.length < 1) { - throw new Error("Heat not found") + }) + case '/v1/leaderboard/scoresForHeatAndAthlete': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' scoresForHeatAndAthlete request with heat and athlete:', + input.heat, input.athlete); + + const scores = await db.scoresForHeatAndAthlete( + input.heat, + input.athlete + ) + + if (scores.length < 1) { + throw new Error("No scores found") + } + res.end(JSON.stringify({ + message: 'Scores for heat and athlete', + data: scores, + })); + } catch(error) { + serverError(res, error); } - - res.end(JSON.stringify({ - message: 'New heat', - data: heats[0], - })); - } catch (error) { - serverError(res, error); - } - }) - } else if (url.pathname === '/v1/leaderboard/removeHeat') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") + }) + case '/v1/leaderboard/scoreSummaryForHeatAndAthlete': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' scoreSummaryForHeatAndAthlete request with heat and athlete:', + input.heat, input.athlete); + + const summary = await db.scoreSummaryForHeatAndAthlete( + input.heat, + input.athlete + ) + + if (summary.length < 1) { + throw new Error("Score summary not found") + } + res.end(JSON.stringify({ + message: 'Score summary for heat and athlete', + data: summary[0], + })); + } catch(error) { + serverError(res, error); } - input = JSON.parse(b); - console.log(' removeHeat request for:', input); - - const heats = await db.removeHeat(input.heat_id) - if (heats.length < 1) { - throw new Error("Heat not removed") + }) + case '/v1/leaderboard/getScore': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' GetScore request for:', input); + + const scores = await db.getScore( + input.heat, + input.athlete, + input.judge + ); + + if (scores.length < 1) { + throw new Error("Score not found") + } + + res.end(JSON.stringify({ + message: 'Requested score for heat, user and judge', + data: scores[0], + })); + } catch (error) { + serverError(res, error); } - res.end(JSON.stringify({ - message: 'Heat removed', - data: heats[0], - })); - } catch (error) { - serverError(res, error); - } - }) - } else if (url.pathname === '/v1/leaderboard/addAthleteToHeat') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") + }) + case '/v1/leaderboard/setScore': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' SetScore request for:', input); + + const scores = await db.setScore( + input.heat, + input.athlete, + input.judge, + input.score, + ); + + if (scores.length < 1) { + throw new Error("Score not updated") + } + + res.end(JSON.stringify({ + message: 'Score update for heat, user and judge', + data: scores[0], + })); + } catch (error) { + serverError(res, error); } - input = JSON.parse(b); - console.log(' addAthleteToHeat request for:', input); - - const startlist = await db.addAthleteToHeat( - input.athlete, - input.heat, - ); - if (startlist.length < 1) { - throw new Error("Startlist not updated") + }) + case '/v1/leaderboard/newHeat': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' newHeat request for:', input); + + const heats = await db.newHeat( + input.name, + input.location, + input.planned_start, + ); + if (heats.length < 1) { + throw new Error("Heat not created") + } + + res.end(JSON.stringify({ + message: 'New heat', + data: heats[0], + })); + } catch (error) { + serverError(res, error); } - - res.end(JSON.stringify({ - message: 'Athlete added to startlist', - data: startlist[0], - })); - } catch (error) { - serverError(res, error); - } - }) - } else if (url.pathname === '/v1/leaderboard/removeAthleteFromHeat') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") + }) + case '/v1/leaderboard/getHeat': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' getHeat request for:', input); + + const heats = await db.getHeat( + input.heat_id, + ); + if (heats.length < 1) { + throw new Error("Heat not found") + } + + res.end(JSON.stringify({ + message: 'New heat', + data: heats[0], + })); + } catch (error) { + serverError(res, error); } - input = JSON.parse(b); - console.log(' removeAthleteFromHeat request for:', input); - - const startlist = await db.removeAthleteFromHeat(input.startlist_id) - if (startlist.length < 1) { - throw new Error("Startlist not updated") + }) + case '/v1/leaderboard/removeHeat': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' removeHeat request for:', input); + + const heats = await db.removeHeat(input.heat_id) + if (heats.length < 1) { + throw new Error("Heat not removed") + } + res.end(JSON.stringify({ + message: 'Heat removed', + data: heats[0], + })); + } catch (error) { + serverError(res, error); } - - res.end(JSON.stringify({ - message: 'Athlete removed from startlist', - data: startlist[0], - })); - } catch (error) { - serverError(res, error); - } - }) - } else if (url.pathname === '/v1/leaderboard/addAthlete') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") + }) + case '/v1/leaderboard/addAthleteToHeat': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' addAthleteToHeat request for:', input); + + const startlist = await db.addAthleteToHeat( + input.athlete, + input.heat, + ); + if (startlist.length < 1) { + throw new Error("Startlist not updated") + } + + res.end(JSON.stringify({ + message: 'Athlete added to startlist', + data: startlist[0], + })); + } catch (error) { + serverError(res, error); } - input = JSON.parse(b); - console.log(' addAthlete request for:', input); - - const athlete = await db.addAthlete( - input.nr, - input.firstname, - input.lastname, - input.birthday, - input.school, - ); - if (athlete.length < 1) { - throw new Error("Startlist not updated") + }) + case '/v1/leaderboard/removeAthleteFromHeat': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' removeAthleteFromHeat request for:', input); + + const startlist = await db.removeAthleteFromHeat(input.startlist_id) + if (startlist.length < 1) { + throw new Error("Startlist not updated") + } + + res.end(JSON.stringify({ + message: 'Athlete removed from startlist', + data: startlist[0], + })); + } catch (error) { + serverError(res, error); } - - res.end(JSON.stringify({ - message: 'Athlete created', - data: athlete[0] - })); - } catch (error) { - serverError(res, error); - } - }) - } else if (url.pathname === '/v1/leaderboard/removeAthlete') { - let body = []; - req.on('data', chunk => { - body.push(chunk); - }).on('end', async () => { - const b = Buffer.concat(body); - try { - if (b.length < 1) { - throw new Error("Empty request body") + }) + case '/v1/leaderboard/addAthlete': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' addAthlete request for:', input); + + const athlete = await db.addAthlete( + input.nr, + input.firstname, + input.lastname, + input.birthday, + input.school, + ); + if (athlete.length < 1) { + throw new Error("Startlist not updated") + } + + res.end(JSON.stringify({ + message: 'Athlete created', + data: athlete[0] + })); + } catch (error) { + serverError(res, error); } - input = JSON.parse(b); - console.log(' removeAthlete request for:', input); - - const athlete = await db.removeAthlete(input.athlete_id) - if (athlete.length < 1) { - throw new Error("Athlete not removed") + }) + case '/v1/leaderboard/removeAthlete': + req.on('data', chunk => { + body.push(chunk); + }).on('end', async () => { + const b = Buffer.concat(body); + try { + if (b.length < 1) { + throw new Error("Empty request body") + } + input = JSON.parse(b); + console.log(' removeAthlete request for:', input); + + const athlete = await db.removeAthlete(input.athlete_id) + if (athlete.length < 1) { + throw new Error("Athlete not removed") + } + res.end(JSON.stringify({ + message: 'Athlete removed', + data: athlete[0], + })); + } catch (error) { + serverError(res, error); } - res.end(JSON.stringify({ - message: 'Athlete removed', - data: athlete[0], - })); - } catch (error) { - serverError(res, error); + }) + default: + const pathExists = paths.find((i) => i === url.pathname); + if (pathExists) { + // wrong method for this path + notAllowed(res, req.method); + } else { + notFound(res, url.pathname); } - }) - } else { - const pathExists = paths.find((i) => i === url.pathname); - if (pathExists) { - // wrong method for this path - notAllowed(res, req.method); - } else { - notFound(res, url.pathname); - } - } + } // end switch } else { notAllowed(res, req.method); }