Files
website/server/src/router/v1/player/events.router.js
wtclaude 1667e636bd feat(events): the public calendar, event pages and participation history (Phase 14a)
The anonymous surface an event was always for: GET /public/events,
/public/events/:slug and /public/events/series/:slug, plus
GET /player/events/history, and the four screens over them.

Four org-lead decisions taken up front: split Phase 14 into 14a (website)
and 14b (the app); add a `listed` flag rather than letting `state` mean both
schedulable and announced; put the `events` capability string in the version
block rather than publishing core as a pseudo-module; and drop "venue" from
the spec rather than adding a field nothing had ever built.

`listed` is announcement, not permission. Publishing is what makes a
definition runnable, so without a separate flag a surprise event would have
to be advertised in order to be allowed to happen. It is a column, a switch
in Phase 13's editor, and three SQL predicates -- never a filter applied
after a read, which works exactly as well until the first caller that forgets.

The public shapes are a projection, and the projection is the security
boundary: nothing is spread, so a column added to event_runs next year does
not ride out through it. The spec, health, cleanup, claims, errors and
member_key are all absent by construction.

The six public event triggers gained `eventUrl` (version 1 -> 2), carrying
?run= because the page lives at the definition's slug while every trigger is
about one occurrence. notify.event-started gained the button, at seedVersion 2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-08 06:18:38 -05:00

40 lines
2.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Player · Events — this account's participation history (EVENTS.md § API
// surface, Phase 14a). Mounted at /api/v1/player/events by player/index.js.
//
// The group gate is `requireAuth` and it is the whole gate: this is role-agnostic
// self-service, like the rest of /player. Staff are a superset of players (see
// player/index.js), and an admin reading their own attendance is exactly as
// ordinary as a player doing it.
//
// **No backtick in a `#swagger.parameters` annotation.** Unlike `#swagger.summary`
// and `#swagger.description`, which are plain strings, a parameters annotation is
// parsed as an object literal — a backtick inside its quoted `description` is
// rewritten as a quote, and swagger-autogen then DROPS the whole annotation with a
// syntax error rather than failing the build.
//
// **There is no id parameter, deliberately.** The history is `req.user.id`'s and
// nothing else's; a route that took a user id would be one middleware mistake
// away from publishing who attended what, which is a question about people
// rather than about events.
const express = require('express')
const ctrl = require('./events.controller')
const eventsRouter = express.Router()
eventsRouter.get(
'/history',
// #swagger.tags = ['Player · Events']
// #swagger.summary = 'This account’s event participation'
// #swagger.description = 'The events this account took part in, most recent first — the run, when it was, the score a module reported, and the rank once results were published. `rank` is null until then, which is a real state rather than an error. Rehearsals and unlisted events are absent, the same rule the public calendar follows. Keyset paging: pass the last entry’s `id` as `before`.'
// #swagger.parameters['limit'] = { in: 'query', required: false, schema: { type: 'integer' }, description: 'Page size, max 200 (default 50).' }
// #swagger.parameters['before'] = { in: 'query', required: false, schema: { type: 'integer' }, description: 'Cursor: the id of the last entry on the previous page.' }
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
/* #swagger.responses[200] = { description: 'Participation history', content: { "application/json": { schema: { $ref: "#/components/schemas/PlayerEventHistory" } } } } */
/* #swagger.responses[401] = { description: 'Not signed in', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
ctrl.getHistory,
)
module.exports = eventsRouter