⚠️ Archived — read-only. This repo's code and deploy have moved toapps/apiingatherloop/game-master-bell, per PRD-v3 phase 2. The monorepo'sdeploy-api.ymlhas been the sole deploy source to the VPS since that migration was verified; this repo's own.github/workflows/deploy.yml(kept until now as a rollback path) has been removed. Everything below describes this repo as it stood before the move — kept for history, not for active use. File issues and PRs against the monorepo instead.
Self-hosted call API for Game Master Bell, per PRD-v2. It replaces the v1 Firebase Cloud Function: validates bell calls from the bell web app and fans them out as Web Push notifications to game master devices, with no Google/Firebase dependency.
Status: Phase A5 (decommission & runbook) — the API track is complete.
A valid call fans out a real Web Push notification to every stored
subscription concurrently; per-send outcomes are logged, and any
subscription whose send fails with 404/410 is pruned automatically. Table
codes are validated against the bell repo's tables.json, synced at startup
and refreshed hourly. Game master devices register/unregister a Web Push
subscription (SQLite-backed, passcode-gated), and the API's VAPID public key
is served for the receiver PWA to use. See
docs/RUNBOOK.md for the Firebase decommission checklist,
uptime monitoring setup, and the staff passcode rotation procedure.
Node.js 22 + TypeScript + Fastify.
pnpm install
pnpm dev # start the dev server with reload (http://localhost:3000)| Script | Purpose |
|---|---|
pnpm dev |
Run the API with hot reload |
pnpm build |
Compile TypeScript to dist/ |
pnpm start |
Run the compiled server (dist/server.js) |
pnpm lint / pnpm lint:fix |
ESLint |
pnpm format / pnpm format:check |
Prettier |
pnpm typecheck |
tsc --noEmit |
pnpm test |
Vitest |
pnpm vapid:generate |
Print a new VAPID key pair |
| Endpoint | Auth | Purpose |
|---|---|---|
GET /healthz |
None | Liveness check |
GET /vapid-key |
None | Returns { publicKey }, the API's VAPID public key. 500 if not configured. |
POST /call |
None | Validate { tableCode } against synced table data, then fan out a Web Push notification to every stored subscription. 400 malformed, 404 unknown/inactive, 200 on a valid call (push sends run concurrently; a dead device doesn't delay or fail the others). |
POST /subscriptions |
Staff passcode | Upsert { subscription, passcode } (a PushSubscription), keyed by endpoint — idempotent. 400 malformed, 401 bad passcode, 200 stored. |
DELETE /subscriptions |
Staff passcode | Remove { endpoint, passcode }. Idempotent (200 even if the endpoint was never stored). 400 malformed, 401 bad passcode. |
| Env var | Default | Purpose |
|---|---|---|
TABLES_URL |
raw URL to gatherloop/game-master-bell's packages/shared/src/tables.json on main |
Source of truth for table codes |
TABLES_CACHE_PATH |
./data/tables-cache.json |
On-disk fallback used if a fetch fails |
TABLES_REFRESH_INTERVAL_MS |
3600000 (1 hour) |
How often to re-fetch TABLES_URL |
SUBSCRIPTIONS_DB_PATH |
./data/subscriptions.db |
SQLite file backing the subscriptions store |
CORS_ORIGINS |
https://gatherloop.github.io |
Comma-separated list of origins allowed to call this API via CORS |
STAFF_PASSCODE |
(required) | Shared secret gating POST/DELETE /subscriptions |
VAPID_PUBLIC_KEY |
(required) | Served at GET /vapid-key; generate with pnpm run vapid:generate |
VAPID_PRIVATE_KEY |
(required) | Signs outgoing Web Push messages; generated alongside the public key |
VAPID_SUBJECT |
(required) | Contact info (mailto: or https:) sent with each push, per the VAPID spec |
The API refuses to start if it has never loaded any copy of the tables data
(neither a live fetch nor a disk cache), or if STAFF_PASSCODE/
VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEY/VAPID_SUBJECT are unset; a failed
tables refresh after startup just keeps the last good copy.
See docs/DEPLOY.md for running the API on the VPS via
Docker Compose behind a TLS-terminating reverse proxy, or
docs/DEPLOY_NATIVE.md for running it directly
under systemd on a lightweight VPS where Docker isn't worth the overhead.
.github/workflows/deploy.yml automates the native path on every push to
main.
See docs/RUNBOOK.md for the Firebase project
decommission checklist, wiring up uptime monitoring for GET /healthz, and
the staff passcode rotation procedure.