mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 22:51:30 -07:00
[api] some gameconfig changes
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Hono } from 'hono'
|
||||
import { describeRoute } from 'hono-openapi'
|
||||
|
||||
import charadesAprilWords from '../../static/charades-april.json'
|
||||
import charadesWords from '../../static/charades.json'
|
||||
import communityBoard from '../../static/community-board.json'
|
||||
import { authedId, unauthorized } from '../http'
|
||||
@@ -69,6 +70,24 @@ async function sanitizeRequest(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Charades word bank for a given moment: the April Fools list on April 1st, the
|
||||
* ordinary list every other day.
|
||||
*
|
||||
* A REPLACEMENT, not an addition — the joke list stands alone for the day, which is why
|
||||
* its ids (1258+) start past the end of the ordinary one rather than overlapping it. The
|
||||
* client fetches the bank when the activity starts, so a game already running keeps
|
||||
* whichever list it drew.
|
||||
*
|
||||
* The date is read in UTC, so the swap runs 00:00-23:59 UTC on April 1 for everyone
|
||||
* rather than rolling around the world with local midnight. `now` is injectable so tests
|
||||
* can pick a day.
|
||||
*/
|
||||
export function charadesWordsFor(now: Date = new Date()) {
|
||||
const isAprilFools = now.getUTCMonth() === 3 && now.getUTCDate() === 1
|
||||
return isAprilFools ? charadesAprilWords : charadesWords
|
||||
}
|
||||
|
||||
// Text sanitization, keepsakes, objectives/events/rewards, and the misc analytics
|
||||
// sinks the client hits during load.
|
||||
export const gameplayRoutes = new Hono<App>({ strict: false })
|
||||
@@ -128,6 +147,7 @@ export const gameplayRoutes = new Hono<App>({ strict: false })
|
||||
// ---- Activities -----------------------------------------------------------
|
||||
// Word bank for the Charades activity. The client requests the list by
|
||||
// activity name (`.../words/Charades`); other activities have no data yet.
|
||||
// On April 1st (UTC) the joke list replaces it — see `charadesWordsFor`.
|
||||
.get(
|
||||
'/api/activities/charades/v1/words/:activity',
|
||||
describeRoute({
|
||||
@@ -136,11 +156,12 @@ export const gameplayRoutes = new Hono<App>({ strict: false })
|
||||
description:
|
||||
'The words the Charades activity draws from. The client asks by activity name ' +
|
||||
'(`.../words/Charades`); the name is not matched on, so every activity gets the ' +
|
||||
'charades list — no other activity has data yet.',
|
||||
'charades list — no other activity has data yet. On April 1st (UTC) the April ' +
|
||||
'Fools word list is served in place of the ordinary one.',
|
||||
parameters: [stringParam('activity', 'Activity name, e.g. `Charades`. Not matched on.')],
|
||||
responses: { 200: json(JsonArray, 'The word list') },
|
||||
}),
|
||||
(c) => c.json(charadesWords)
|
||||
(c) => c.json(charadesWordsFor())
|
||||
)
|
||||
|
||||
// Keepsakes (room mementos). Stubbed empty.
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
isPlayerBanned,
|
||||
SCHEMA_DDL as REPORTS_SCHEMA_DDL,
|
||||
} from '../../reports-db'
|
||||
import { charadesWordsFor } from '../../routes/gameplay'
|
||||
import { getWarningsAgainst, SCHEMA_DDL as WARNINGS_SCHEMA_DDL } from '../../warnings-db'
|
||||
|
||||
import type { SavedImage } from '@repo/domain'
|
||||
@@ -324,7 +325,32 @@ describe('public endpoints', () => {
|
||||
const words = (await res.json()) as Array<{ Id: number; Difficulty: number; EN_US: string }>
|
||||
expect(Array.isArray(words)).toBe(true)
|
||||
expect(words.length).toBeGreaterThan(0)
|
||||
expect(words[0]).toEqual({ Id: 1, Difficulty: 0, EN_US: 'David Bowie' })
|
||||
// Which bank that is depends on the day — the April Fools list replaces the ordinary
|
||||
// one on April 1st — so compare against the same selector the route uses rather than
|
||||
// hard-coding a word here. The two banks' contents are pinned below.
|
||||
expect(words).toEqual(charadesWordsFor())
|
||||
})
|
||||
|
||||
test('serves the April Fools charades words on April 1st and the ordinary list otherwise', () => {
|
||||
// A replacement, not an addition: the joke list stands alone for the day, and its ids
|
||||
// start past the end of the ordinary one rather than overlapping it.
|
||||
const april = charadesWordsFor(new Date('2026-04-01T12:00:00Z'))
|
||||
expect(april[0]).toEqual({ Id: 1258, Difficulty: 10, EN_US: 'Nothing' })
|
||||
|
||||
const ordinary = charadesWordsFor(new Date('2026-04-02T12:00:00Z'))
|
||||
expect(ordinary[0]).toEqual({ Id: 1, Difficulty: 0, EN_US: 'David Bowie' })
|
||||
expect(ordinary.some((w) => w.Id === april[0].Id)).toBe(false)
|
||||
|
||||
// Every other day gets the ordinary list, including the edges of April 1 UTC and the
|
||||
// first of other months.
|
||||
expect(charadesWordsFor(new Date('2026-03-31T23:59:59Z'))).toBe(ordinary)
|
||||
expect(charadesWordsFor(new Date('2026-04-02T00:00:00Z'))).toBe(ordinary)
|
||||
expect(charadesWordsFor(new Date('2026-05-01T12:00:00Z'))).toBe(ordinary)
|
||||
expect(charadesWordsFor(new Date('2026-01-01T12:00:00Z'))).toBe(ordinary)
|
||||
|
||||
// The window is the whole of April 1 in UTC, not local time.
|
||||
expect(charadesWordsFor(new Date('2026-04-01T00:00:00Z'))).toBe(april)
|
||||
expect(charadesWordsFor(new Date('2026-04-01T23:59:59Z'))).toBe(april)
|
||||
})
|
||||
|
||||
// The client POSTs this with no body, despite it being a pure read; the route answers
|
||||
|
||||
Reference in New Issue
Block a user