BigBrainParking/server
Erik 4217e88338
All checks were successful
build-apk / build (push) Successful in 9m54s
v0.6.3: hide the paid city lots from users without a ParkSmarter account
The green lots are the map's only paid category ("City lots - Paid hourly or
permit"); paying for them goes through ParkSmarter, so they are no use to
someone browsing without an account. Every other category is free street
parking with a posted time limit and needs nothing.

Gated by category rather than by an id list. Two lots were named (off Oak St
and off N 3rd Ave) and then the beach ones, which together is every green lot
on the map — and an id list would silently break the next time the map is
regenerated from a new PDF, since ids are positional.

- Paid lots are filtered out of the overlay when signed out, so they are
  neither drawn nor tappable.
- "Park here" still detects them, so standing in one explains that it needs an
  account instead of reporting no parking nearby.
- The area screen guards too, in case one is reached with a stale nav param.
- Server carries an optional per-area requiresAccount override for a lot that
  turns out to take payment another way. Null means "use the category
  default", so an unset value can't be confused with an explicit false.

The new column needs a real migration: CREATE TABLE IF NOT EXISTS does not add
a column to a table that already exists, so an already-deployed server would
have kept the old schema. Covered by a test that builds the pre-migration
table and then opens it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 06:02:23 +00:00
..
deploy server: zone-labels API (Fastify + SQLite) — Phase A 2026-07-24 18:09:03 +00:00
src v0.6.3: hide the paid city lots from users without a ParkSmarter account 2026-08-13 06:02:23 +00:00
test v0.6.3: hide the paid city lots from users without a ParkSmarter account 2026-08-13 06:02:23 +00:00
.env.example server: zone-labels API (Fastify + SQLite) — Phase A 2026-07-24 18:09:03 +00:00
.gitignore server: zone-labels API (Fastify + SQLite) — Phase A 2026-07-24 18:09:03 +00:00
docker-compose.yml server: zone-labels API (Fastify + SQLite) — Phase A 2026-07-24 18:09:03 +00:00
Dockerfile server: zone-labels API (Fastify + SQLite) — Phase A 2026-07-24 18:09:03 +00:00
package-lock.json server: zone-labels API (Fastify + SQLite) — Phase A 2026-07-24 18:09:03 +00:00
package.json server: zone-labels API (Fastify + SQLite) — Phase A 2026-07-24 18:09:03 +00:00
README.md server: zone-labels API (Fastify + SQLite) — Phase A 2026-07-24 18:09:03 +00:00
tsconfig.json server: zone-labels API (Fastify + SQLite) — Phase A 2026-07-24 18:09:03 +00:00

BigBrainParking zone-labels API

A tiny service that classifies parking zones so the app knows whether a space is free for a limit (free_2h / free_3h / free_4h) or a pay-immediately lot (pay_immediate). Public reads; writes require the admin password.

Lives in the monorepo but is not an npm workspace (keeps its native better-sqlite3 dep out of the app/CI install). Deploy it independently.

API

Method Path Auth Notes
GET /healthz liveness
GET /api/labels all labels { labels: [...] } (app bulk-caches)
GET /api/labels/:zoneId one label, 404 if none
PUT /api/labels/:zoneId admin upsert { kind, zoneName?, customerId?, note? }
DELETE /api/labels/:zoneId admin remove
GET /api/whoami admin { admin: true } — used by the app's "test password"

Auth: Authorization: Bearer <BBP_ADMIN_TOKEN> (timing-safe compare). Reads are public but rate-limited (~120/min/IP; writes ~20/min). Repeated bad tokens from an IP auto-block it for a cooldown; blocked_ips (DB) + BBP_BLOCKED_IPS (env) are a manual denylist. zoneId is the ParkSmarter ZoneId (e.g. 113165).

Develop

cd server
npm install
npm test          # node --test via tsx
BBP_ADMIN_TOKEN=dev-secret-please-change npm run dev

Deploy (Docker + nginx on the host the CNAME points to)

cd server
cp .env.example .env
sed -i "s#change-me-to-a-long-random-secret#$(openssl rand -base64 32)#" .env   # set the admin secret
docker compose up -d --build

# nginx + TLS (first time)
sudo cp deploy/bigbrainparking.mowden.top.conf /etc/nginx/sites-available/bigbrainparking.mowden.top
sudo ln -s ../sites-available/bigbrainparking.mowden.top /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d bigbrainparking.mowden.top

curl https://bigbrainparking.mowden.top/healthz         # {"ok":true}

The admin secret (from .env) is what you paste into the app under Settings → Admin. Rotate by editing .env and docker compose up -d.

Update

git pull && docker compose up -d --build