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 c641d7154dbc7bddee80c28cb0efc7dc81c44782
parent ee6104a0c85442b96e095f9cb62b22dd01c3203f
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date:   Sat, 28 Sep 2024 13:26:10 +0200

feat(api): handle no response 204

Diffstat:
Msrc/api/server.cjs | 16+++++++++++-----
Msrc/frontend/Leaderboard.jsx | 76++++++++++++++++++++++++++++++++++++++++++----------------------------------
2 files changed, 53 insertions(+), 39 deletions(-)

diff --git a/src/api/server.cjs b/src/api/server.cjs @@ -253,7 +253,8 @@ server.on('request', async (req, res) => { const startlist = await db.distinctStartlist(input.heat_ids); if (startlist.length < 1) { - throw new Error("Startlist not found") + noContent(res); + return } res.end(JSON.stringify({ message: 'Distinct athletes for multiple heats', @@ -297,7 +298,6 @@ server.on('request', async (req, res) => { }).on('end', async () => { const b = Buffer.concat(body); try { - await verifyToken(req, token) if (b.length < 1) { throw new Error("Empty request body") } @@ -311,7 +311,8 @@ server.on('request', async (req, res) => { ) if (scores.length < 1) { - throw new Error("No scores found") + noContent(res); + return } res.end(JSON.stringify({ message: 'Scores for heat and athlete', @@ -328,7 +329,6 @@ server.on('request', async (req, res) => { }).on('end', async () => { const b = Buffer.concat(body); try { - await verifyToken(req, token) if (b.length < 1) { throw new Error("Empty request body") } @@ -342,7 +342,8 @@ server.on('request', async (req, res) => { ) if (summary.length < 1) { - throw new Error("Score summary not found") + noContent(res); + return } res.end(JSON.stringify({ message: 'Score summary for heat and athlete', @@ -669,6 +670,11 @@ async function verifyToken(req, token) { return user } +function noContent(res) { + console.warn('x Warning: 204 no rice 😱'); + res.statusCode = 204; + res.end(); +} function notFound(res, path) { console.error('x Error: 404 not found'); res.statusCode = 404; diff --git a/src/frontend/Leaderboard.jsx b/src/frontend/Leaderboard.jsx @@ -10,6 +10,7 @@ const ws_port = import.meta.env.VITE_WS_PORT // use a socket for the real-time leaderboard data let socket = new WebSocket(`${ws_uri}:${ws_port}/v1/leaderboard`); +console.info("Attached to server websocket"); export async function addAthleteToHeat(athlete, heat) { const res = await fetch(`${api_uri}:${api_port}/v1/leaderboard/addAthleteToHeat`, { @@ -35,11 +36,16 @@ export async function getStartlistForHeats(heatIds) { "heat_ids": heatIds, }), }) - const { data, error } = await res.json() - if (error) { - throw error + if (res.status === 204) { + // return empty startlist + return [] + } else { + const { data, error } = await res.json() + if (error) { + throw error + } + return data } - return data } async function getScoresForHeatAndAthlete(heatId, athleteId) { @@ -51,11 +57,13 @@ async function getScoresForHeatAndAthlete(heatId, athleteId) { "athlete": athleteId, }), }) - const { data, error } = await res.json() - if (error) { - throw error + if (res.status !== 204) { + const { data, error } = await res.json() + if (error) { + throw error + } + return data } - return data } async function getScoreSummaryForHeatAndAthlete(heatId, athleteId) { @@ -67,24 +75,18 @@ async function getScoreSummaryForHeatAndAthlete(heatId, athleteId) { "athlete": athleteId, }), }) - const { data, error } = await res.json() - if (error) { - throw error + if (res.status !== 204) { + const { data, error } = await res.json() + if (error) { + throw error + } + return data } - return data } async function getScoreSummary(heatIds) { const startListWithScores = [] - - let startlist = undefined - try { - startlist = await getStartlistForHeats(heatIds) - } catch (error) { - console.error(error) - // fail silently & return empty startlist in case of errors - return [] - } + const startlist = await getStartlistForHeats(heatIds) for (const i of startlist) { i.heats = [] @@ -93,16 +95,16 @@ async function getScoreSummary(heatIds) { try { // this is an array, because the athlete can be scored by multiple judges const scores = await getScoresForHeatAndAthlete(h, i.athlete) - const { score_summary } = await getScoreSummaryForHeatAndAthlete(h, i.athlete) - - // add heat results of athlete to startlist entry - i.heats.push({ - heatId: h, - scores: scores, - summary: score_summary - }) + const summary = await getScoreSummaryForHeatAndAthlete(h, i.athlete) + if (scores && summary) { + // add heat results of athlete to startlist entry + i.heats.push({ + heatId: h, + scores: scores, + summary: summary.score_summary + }) + } } catch (error) { - // else don't push any heats (fail silently) console.error(error) } @@ -311,15 +313,21 @@ function Leaderboard({session}) { useEffect(() => { (async() => { - // subscribe to scoring from judges - socket.send(JSON.stringify({ - method: "watchScores", - })) socket.onmessage = async function(event) { // todo: reload only required scores const scoreSummary = await getScoreSummary(heatSelection.map(h => h.value)) setLeaderboard(scoreSummary) } + socket.onclose = async function(event) { + console.info("Server removed us from client list, reattaching socket"); + socket = new WebSocket(`${ws_uri}:${ws_port}/v1/leaderboard`); + } + socket.onopen = function (event) { + // subscribe to scoring from judges when socket is opened + socket.send(JSON.stringify({ + method: "watchScores", + })) + } setLoading(false) })(); }, [heatSelection]);