This repository implements the backend services for the "London PCN Live Map + Anonymised Officer Leaderboard" experience. It combines a Fastify HTTP server, Prisma/PostgreSQL access layer, BullMQ-powered ETL workers, and a server-sent events fan-out to serve live parking ticket activity across participating London boroughs.
- Fastify 5 + TypeScript HTTP server with Helmet, CORS, sensible defaults, and per-route rate limiting.
- Runtime environment validation via
zodcovering auth, database, Redis, and observability configuration. - JWT / JWKS authentication guard enforcing
guestandadminRBAC roles while providing a permissive fallback for anonymous development. - PostGIS-ready Prisma layer with ticket, patrol, and ingestion watermark models, plus raw spatial queries for viewport filtering and spatiotemporal clustering.
- Officer leaderboard sequencing that groups tickets by ≤150 m proximity and ≤12 minute windows, applying deterministic daily salts for privacy-preserving "Parking Officer N" labelling.
- Redis + BullMQ ingestion pipeline (Camden fixture source provided) together with an admin trigger endpoint and worker process entry point.
- Server-sent events broadcaster for ticket, leaderboard, patrol, and heartbeat updates with subscription helpers.
- Tooling: ESLint (strict TypeScript), Prettier, Prisma generator, and TypeScript build scripts ready for CI usage.
-
Install dependencies:
npm install
-
Provision services:
- PostgreSQL 16 with PostGIS 3 extension enabled.
- Redis 7 for BullMQ job queueing and pub/sub fan-out.
-
Create an environment file (e.g.
.env) with the following variables. JWKS settings are required in production but optional for local development:NODE_ENV=development HOST=0.0.0.0 PORT=3000 # AUTH_JWKS_URL=https://your-auth-domain/.well-known/jwks.json # AUTH_AUDIENCE=https://your-api-audience # AUTH_ISSUER=https://your-auth-domain/ ADMIN_ROLE=admin GUEST_ROLE=guest LEADERBOARD_DAILY_SECRET=change-me-daily DATABASE_URL=postgresql://postgres:postgres@localhost:5432/londonparking REDIS_URL=redis://localhost:6379
-
Generate the Prisma client after adjusting the schema (run automatically by
npm run prisma):npm run prisma
-
Start the development API server:
-
In a second terminal start the ingestion worker (requires Redis):
npm run dev:worker
-
Run the lint and build pipelines:
npm run lint npm run build
The server currently exposes the read-only contract described in the technical blueprint. Tickets and leaderboard responses are backed by SQL queries and anonymisation routines, while the Camden ETL worker hydrates a set of fixture tickets to demonstrate the end-to-end flow.
| Method | Path | Description | Auth |
|---|---|---|---|
GET |
/api/healthz |
Liveness probe | none |
GET |
/api/tickets?bbox=WEST,SOUTH,EAST,NORTH&since&until&limit |
Viewport-filtered PCN tickets (PostGIS spatial query) | guest |
GET |
/api/leaderboard/officers?borough&since&until |
Officer leaderboard (daily salted spatiotemporal sequences) | guest |
GET |
/api/leaderboard/streets?borough&since&until |
Street-level fallback leaderboard | guest |
GET |
/api/stream |
Server-sent events channel (ticket, leaderboard, patrol, heartbeat) | guest |
POST |
/api/admin/ingest/:source |
Enqueue an ETL ingestion job via BullMQ | admin |
src/services/tickets.tsissues raw SQL againstpcn_ticketusingST_MakeEnvelopebounding boxes and temporal filters.src/services/leaderboard.tsgroups tickets into patrol sequences using haversine distance and time thresholds, hashes the grouping with a daily secret, and emits ranked officer labels plus street aggregates.src/etl/workers/sources/camden.tsseeds the database with fixture Camden tickets and updates ingestion watermarks. Additional boroughs can be added by extending thesourcesmap insrc/etl/workers/index.ts.src/lib/events.tsandsrc/http/routes/stream.tsimplement the SSE dispatcher and subscription lifecycle.
npm run workerstarts a BullMQ worker in production mode, processing ingestion jobs and emitting SSE notifications when new tickets arrive.- Workers require
REDIS_URLto be defined; the process exits early with a helpful error when Redis is absent.
-
Replace the Camden fixtures with real borough ETL integrations and extend the
sourcescatalogue. -
Publish patrol events and derived leaderboard deltas through the SSE channel and an eventual Redis pub/sub fan-out.
-
Add automated tests (unit, contract, e2e) once Postgres/Redis Testcontainers are wired into the toolchain.
-
Wire up observability (OpenTelemetry, Prometheus, Sentry) per the blueprint.
This project is provided under the ISC license. See LICENSE if/when added.