aboutsummaryrefslogtreecommitdiffstats
path: root/server/daily.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/daily.ts')
-rw-r--r--server/daily.ts123
1 files changed, 123 insertions, 0 deletions
diff --git a/server/daily.ts b/server/daily.ts
new file mode 100644
index 0000000..35350f7
--- /dev/null
+++ b/server/daily.ts
@@ -0,0 +1,123 @@
+import { Router } from "express";
+import rateLimit from "express-rate-limit";
+import {
+ buildSessionToken,
+ getDailySong,
+ getUtcDate,
+ GuessEntry,
+ normalizeSignedState,
+ obfuscateSong,
+ SignedState,
+ signState,
+ verifySessionToken,
+ verifyStateSignature,
+} from "./shared";
+
+export const dailyRouter = Router();
+
+const guessLimiter = rateLimit({
+ windowMs: 60 * 1000,
+ limit: 30,
+ standardHeaders: true,
+ legacyHeaders: false,
+ message: { error: "Too many requests. Please slow down." },
+});
+
+dailyRouter.get("/today", (_req, res) => {
+ const date = getUtcDate();
+ const song = getDailySong(date);
+
+ const initialState: SignedState = {
+ date,
+ currentTry: 0,
+ didGuess: false,
+ guesses: [],
+ };
+
+ res.json({
+ date,
+ data: obfuscateSong(song, date),
+ sessionToken: buildSessionToken(date),
+ initialSig: signState(initialState),
+ });
+});
+
+dailyRouter.post("/guess", guessLimiter, (req, res) => {
+ const today = getUtcDate();
+ const body = req.body as {
+ sessionToken?: unknown;
+ state?: unknown;
+ sig?: unknown;
+ guess?: unknown;
+ };
+
+ if (!verifySessionToken(body.sessionToken, today)) {
+ res.status(401).json({ error: "Invalid session token." });
+ return;
+ }
+
+ const state = normalizeSignedState(body.state);
+ if (!state) {
+ res.status(400).json({ error: "Invalid state payload." });
+ return;
+ }
+
+ if (state.date !== today) {
+ res.status(409).json({ error: "State date is not valid for today." });
+ return;
+ }
+
+ if (!verifyStateSignature(state, body.sig)) {
+ res.status(403).json({ error: "State signature mismatch." });
+ return;
+ }
+
+ if (state.didGuess || state.currentTry >= 6) {
+ res.status(409).json({ error: "Round already finished." });
+ return;
+ }
+
+ let nextGuess: GuessEntry;
+ const guess = body.guess as { artist?: unknown; name?: unknown } | null | undefined;
+
+ if (guess == null) {
+ nextGuess = {
+ state: "Skipped",
+ };
+ } else if (typeof guess.artist === "string" && typeof guess.name === "string") {
+ const solution = getDailySong(today);
+
+ if (guess.artist === solution.artist && guess.name === solution.name) {
+ nextGuess = {
+ song: { artist: guess.artist, name: guess.name },
+ state: "Correct",
+ };
+ } else if (guess.artist === solution.artist) {
+ nextGuess = {
+ song: { artist: guess.artist, name: guess.name },
+ state: "PartiallyCorrect",
+ };
+ } else {
+ nextGuess = {
+ song: { artist: guess.artist, name: guess.name },
+ state: "Incorrect",
+ };
+ }
+ } else {
+ res.status(400).json({ error: "Invalid guess payload." });
+ return;
+ }
+
+ const nextState: SignedState = {
+ date: state.date,
+ currentTry: Math.min(6, state.currentTry + 1),
+ didGuess: state.didGuess || nextGuess.state === "Correct",
+ guesses: [...state.guesses, nextGuess],
+ };
+
+ res.json({
+ state: nextState,
+ sig: signState(nextState),
+ guessState: nextGuess.state,
+ });
+});
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage