- services/api/README.md: new — contract, middleware stack, background tasks, config table (LOG_LEVEL, SENTRY_DSN), health story, extraction criteria - ml/serving/README.md: add Observability section (structlog JSON, traceparent → trace_id binding), add SENTRY_DSN + ENV to config table - services/recommender/README.md: fix policy table — egreedy-v2 is active (#99), egreedy-v1 is shadow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
90 lines
3.8 KiB
Markdown
90 lines
3.8 KiB
Markdown
# services/api
|
|
|
|
Express BFF that serves all client-facing routes, manages sessions, runs background signal sync, and proxies admin calls to `ml/serving`.
|
|
|
|
## Contract
|
|
|
|
```
|
|
GET /health { ok: true }
|
|
|
|
POST /api/auth/login → redirect to Google OAuth
|
|
GET /api/auth/callback OAuth return URL
|
|
POST /api/auth/logout
|
|
GET /api/auth/session → { user? }
|
|
|
|
GET /api/integrations list connected integrations
|
|
POST /api/integrations/todoist/connect start Todoist OAuth
|
|
GET /api/integrations/todoist/callback
|
|
DELETE /api/integrations/:provider disconnect
|
|
|
|
POST /api/recommend → { tip }
|
|
POST /api/tip/:id/feedback { action } → { ok }
|
|
|
|
GET /api/user/profile
|
|
DELETE /api/user account deletion
|
|
|
|
POST /api/push/subscribe
|
|
DELETE /api/push/subscribe
|
|
|
|
GET /api/admin/stats DAU/WAU, feedback breakdown
|
|
GET /api/admin/users
|
|
GET /api/admin/events recent event stream (ring buffer)
|
|
GET /api/admin/sim/runs offline sim run list
|
|
POST /api/admin/sim/run launch offline sim
|
|
GET /api/admin/sim/runs/:id/output tail sim stdout
|
|
...
|
|
|
|
GET /api/ml/* admin-only proxy to ml/serving
|
|
```
|
|
|
|
## Middleware stack (request order)
|
|
|
|
1. `cors` — origin limited to `WEB_BASE_URL`
|
|
2. `tracingMiddleware` — reads or generates W3C `traceparent`; sets `req.traceId` + `req.traceparent`
|
|
3. `pinoHttp` — structured JSON request/response logs with `traceId` field; `/health` suppressed
|
|
4. `express.json()` / `cookieParser`
|
|
5. `sessionMiddleware` — validates `sid` cookie, attaches `req.userId`
|
|
|
|
## Observability
|
|
|
|
Logs are structured JSON via **pino**. Every line includes `traceId` (extracted from the incoming W3C `traceparent` header, or generated fresh). The same `traceparent` is forwarded on all outbound HTTP calls to `ml/serving` so traces correlate end-to-end.
|
|
|
|
Sentry error capture is active when `SENTRY_DSN` is set.
|
|
|
|
## Background tasks
|
|
|
|
- **Todoist sync scheduler** — runs every `TODOIST_SYNC_INTERVAL_MS` (default 15 min); starts 10 s after boot to avoid startup surge.
|
|
- **Retention purge** — deletes `tipScores` and `tipFeedback` rows older than 30 days; runs on boot and daily.
|
|
- **Profile TTL invalidation** — listens to `signals.task.synced` and `signals.tip.feedback` on the in-process Bus; invalidates cached user-level profile features so the next `/recommend` gets fresh values.
|
|
|
|
## Config
|
|
|
|
| Env var | Default | Description |
|
|
|---------|---------|-------------|
|
|
| `PORT` | `3001` | Listen port |
|
|
| `NODE_ENV` | `development` | Environment label |
|
|
| `DATABASE_PATH` | `./data/oo.db` | SQLite file |
|
|
| `SESSION_SECRET` | required | Cookie signing secret |
|
|
| `GOOGLE_CLIENT_ID/SECRET` | required | OAuth |
|
|
| `TODOIST_CLIENT_ID/SECRET` | required | OAuth |
|
|
| `API_BASE_URL` | `http://localhost:3001` | Self-referential redirect URI |
|
|
| `WEB_BASE_URL` | `http://localhost:3000` | CORS + post-login redirect |
|
|
| `ML_SERVING_URL` | `http://localhost:8000` | ml/serving base URL |
|
|
| `NATS_URL` | `` | NATS broker; empty = in-process bus only |
|
|
| `TODOIST_SYNC_INTERVAL_MS` | `900000` | Background sync cadence |
|
|
| `TIP_PROMPT_VERSION` | `` | Prompt variant(s) for `/generate` |
|
|
| `LOG_LEVEL` | `info` | pino log level |
|
|
| `SENTRY_DSN` | `` | Sentry DSN; empty = Sentry disabled |
|
|
| `VAPID_*` | | Web push keys |
|
|
|
|
## Health story
|
|
|
|
`GET /health` returns `{ ok: true }`. No dependency checks — upstream deps (`ml/serving`, NATS) have their own health endpoints checked separately.
|
|
|
|
## Extraction criteria
|
|
|
|
Extract to its own host when:
|
|
- Auth session management needs a dedicated Redis/PG session store, **or**
|
|
- Background sync load (Todoist, future connectors) displaces API serving on the shared host, **or**
|
|
- Team boundary emerges between auth/BFF and recommender orchestration.
|