99 lines
4.9 KiB
Markdown
99 lines
4.9 KiB
Markdown
---
|
|
type: guide
|
|
title: Event Window
|
|
description: How the event window state machine is computed and surfaced via REST + SSE.
|
|
tags: [guide, event, countdown, sse, state-machine]
|
|
timestamp: 2026-07-21T22:19:08Z
|
|
---
|
|
|
|
# Configuration
|
|
|
|
Two `setting` rows control the window:
|
|
|
|
| Setting key | Default (seeded) | Description |
|
|
|-------------------|------------------|--------------------------------------------|
|
|
| `eventStartUtc` | `now + 60s` | ISO 8601 instant the event becomes live. |
|
|
| `eventEndUtc` | `now + 7d` | ISO 8601 instant the event ends. |
|
|
|
|
Both are seeded by `SeedSystemData1700000000100` and editable through
|
|
`SettingsService`.
|
|
|
|
# State machine
|
|
|
|
`EventStatusService.getState(now)`
|
|
(`backend/src/common/services/event-status.service.ts`) returns one of
|
|
four branches:
|
|
|
|
| Condition | `state` | `secondsToStart` | `secondsToEnd` |
|
|
|----------------------------------------|------------------|--------------------------|--------------------------|
|
|
| `eventStartUtc` / `eventEndUtc` missing or unparseable | `unconfigured` | `null` | `null` |
|
|
| `now < eventStartUtc` | `countdown` | `floor((start - now)/s)` | `null` |
|
|
| `start <= now <= end` | `running` | `null` | `floor((end - now)/s)` |
|
|
| `now > eventEndUtc` | `stopped` | `null` | `null` |
|
|
|
|
The legacy `EventStatusService.getStatus(now)` shape
|
|
(`{status: 'Stopped'|'Running', countdownMs, startUtc, endUtc, serverNowUtc}`)
|
|
is preserved for backward compatibility. `Stopped` is overloaded to
|
|
mean "still counting down" (`countdownMs = eventStartUtc - now`) when
|
|
`state === 'countdown'`, and "ended" (`countdownMs = 0`) when
|
|
`state === 'stopped'` / `unconfigured`.
|
|
|
|
# Endpoints
|
|
|
|
| Endpoint | Auth | Shape |
|
|
|--------------------------------------------|-------------|-------------------------------------------|
|
|
| `GET /api/v1/event/status` | `@Public()` | Legacy `{status, countdownMs, startUtc, endUtc, serverNowUtc}`. |
|
|
| `GET /api/v1/event/stream` | `@Public()` | SSE that emits the legacy payload on a 1-second tick + on hub pushes. |
|
|
| `GET /api/v1/settings/event` | `@Public()` | Raw `{eventStartUtc, eventEndUtc}` (null when unset). |
|
|
| `GET /api/v1/events/status` | Authenticated (JWT) | SSE that emits the full `EventStatePayload` on connect + on a 60-second tick + on hub pushes; consecutive identical payloads suppressed via `distinctUntilChanged(JSON.stringify)`. |
|
|
|
|
See [System Endpoints](/api/system.md) for the full request/response
|
|
shape.
|
|
|
|
# Frontend wiring
|
|
|
|
The authenticated shell subscribes to `/api/v1/events/status` from
|
|
`HomeComponent.ngOnInit()` via `EventStatusStore.start(() => new EventSource('/api/v1/events/status', { withCredentials: true }))`
|
|
(`frontend/src/app/features/home/home.component.ts`,
|
|
`frontend/src/app/core/services/event-status.store.ts`). The store:
|
|
|
|
1. Calls `applyServerStatus(payload)` on every `message` frame (parses
|
|
JSON, sets `state`, `serverNowUtc`, `eventStartUtc`, `eventEndUtc`,
|
|
and stores the clock-skew anchor `Date.now() - serverNow`).
|
|
2. Runs a local 1-second tick so the `secondsToStart` / `secondsToEnd`
|
|
computed signals update between SSE pushes.
|
|
3. Exposes `countdownText` (computed): `""` for `unconfigured`,
|
|
`"Event ended"` for `stopped`, and `formatDdHhMm(seconds)` for
|
|
`countdown` / `running`. The format helper lives in the pure module
|
|
`event-status.pure.ts` (`DD:HH:mm`, zero-padded).
|
|
|
|
The header (`ShellHeaderComponent`) renders an LED with the
|
|
`led-{state}` class so the colour reflects the current state (the
|
|
exact theme colours are wired in `styles.css` against the existing
|
|
`--color-success` / `--color-warning` / `--color-danger` tokens).
|
|
|
|
`HomeComponent.ngOnDestroy()` (and the `DestroyRef` hook in the store
|
|
itself) close the SSE source and clear the tick interval, so leaving
|
|
the shell (e.g. by logging out and landing on `/login`) tears the
|
|
stream down cleanly.
|
|
|
|
# Push path
|
|
|
|
1. The admin cron / settings update writes to `setting.eventStartUtc` /
|
|
`setting.eventEndUtc`.
|
|
2. `SseHubService.publish('event', payload)` fans the new payload out
|
|
to every subscriber of `/api/v1/event/stream` and (via the
|
|
`flat()` mapping) `/api/v1/events/status`.
|
|
3. Each connected client receives the new state immediately; otherwise
|
|
it picks up the next 60-second tick.
|
|
|
|
The hub is in-process; multi-replica deployments need a shared pub/sub
|
|
to fan out across pods (not in scope for this repo).
|
|
|
|
# See also
|
|
|
|
- [Auth and Settings Tables](/database/auth-settings.md)
|
|
- [System Endpoints](/api/system.md)
|
|
- [Scoreboard Stream](/guides/scoreboard-stream.md)
|
|
- [Authenticated Shell](/guides/authenticated-shell.md)
|