New server/ service classifying zones as free_2h/3h/4h street vs pay_immediate lots. Public reads; writes require the admin bearer token (timing-safe compare). @fastify/rate-limit (120/min global, 20/min writes), manual IP denylist + auto-block on repeated auth failures. SQLite via better-sqlite3. Dockerfile + compose (loopback-only, mem/cpu capped) + nginx block + README. 9 tests pass. Deployed live at https://bigbrainparking.mowden.top (behind nginx + certbot). Not an npm workspace — kept out of the app/CI install to avoid the native dep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
59 lines
2.1 KiB
Markdown
59 lines
2.1 KiB
Markdown
# 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
|
||
|
||
```bash
|
||
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)
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
git pull && docker compose up -d --build
|
||
```
|