Files

7.8 KiB

API Surface

Generated from .planning/intel/api-map.json. Do not edit by hand.

Warning: api-map.json is stale (>24 hours old). Data below may be out of date.

GET /health

  • method: GET
  • path: /health
  • auth: none
  • file: apps/api/src/routes/health.ts
  • description: DB liveness probe. Returns { ok: true, db: 'up' } or 503.

GET /callback

  • method: GET
  • path: /callback
  • auth: none (OIDC callback — must be before auth guard)
  • file: apps/api/src/index.ts
  • description: OIDC authorization-code callback. Processed by @hono/oidc-auth processOAuthCallback.

GET /api/login

  • method: GET
  • path: /api/login
  • auth: oidcAuthMiddleware
  • file: apps/api/src/index.ts
  • description: Auth entry point. Redirects to / after successful OIDC login. PWA navigates here for re-auth.

GET /api/me

  • method: GET
  • path: /api/me
  • auth: oidcAuthMiddleware
  • file: apps/api/src/routes/me.ts
  • response: { user: { id: number, displayName: string|null, color: string } }
  • description: Returns authenticated member's identity and assigned color. Upserts user row on first call.

GET /api/events

  • method: GET
  • path: /api/events
  • auth: oidcAuthMiddleware
  • params: start (YYYY-MM-DD, required), end (YYYY-MM-DD, required)
  • file: apps/api/src/routes/events.ts
  • response: { occurrences: CalendarOccurrence[] }
  • description: Windowed calendar events. Max 90-day window. Reads only from MariaDB cache; never hits Fastmail. Expands RRULEs server-side.

POST /api/events/create

  • method: POST
  • path: /api/events/create
  • auth: oidcAuthMiddleware
  • body: CreateEventPayload (title, allDay, start, end, recurrence?, location?, description?, calendarUrl?)
  • file: apps/api/src/routes/events.ts
  • response: 202 { uid: string }
  • description: Enqueues create to calendarOutbox. Async CalDAV write-back via outbox worker. Returns uid immediately.

PATCH /api/events/:uid/edit

  • method: PATCH
  • path: /api/events/:uid/edit
  • auth: oidcAuthMiddleware
  • params: uid (path)
  • body: CreateEventPayload
  • file: apps/api/src/routes/events.ts
  • response: 202 { uid: string }
  • description: Enqueues update (or delete+create pair for calendar-move) to calendarOutbox. Async write-back.

DELETE /api/events/:uid

  • method: DELETE
  • path: /api/events/:uid
  • auth: oidcAuthMiddleware
  • params: uid (path)
  • file: apps/api/src/routes/events.ts
  • response: 202 { uid: string }
  • description: Enqueues delete to calendarOutbox with cached etag (If-Match). Async write-back.

GET /api/events/sync-status

  • method: GET
  • path: /api/events/sync-status
  • auth: oidcAuthMiddleware
  • params: uid (query, required)
  • file: apps/api/src/routes/events.ts
  • response: { uid: string, status: 'pending'|'done'|'failed'|'dead', error?: string }
  • description: Outbox status poll for a given event UID, scoped to current member. Used by SyncStateToast.

GET /api/events/writable-calendars

  • method: GET
  • path: /api/events/writable-calendars
  • auth: oidcAuthMiddleware
  • file: apps/api/src/routes/events.ts
  • response: { calendars: [{ url, displayName, color, isShared }] }
  • description: Authoritative D-03 writable set: member's own calendars + shared Family calendar. Client never derives this itself.

GET /api/sse/heartbeat

  • method: GET
  • path: /api/sse/heartbeat
  • auth: oidcAuthMiddleware
  • file: apps/api/src/routes/sse.ts
  • response: text/event-stream — event: heartbeat, data: { ts, id } every 10s
  • description: SSE smoke-test endpoint for Pangolin tunnel validation.

GET /api/sse/lists

  • method: GET
  • path: /api/sse/lists
  • auth: oidcAuthMiddleware
  • file: apps/api/src/routes/sse.ts
  • response: text/event-stream — events: item:added | item:updated | item:deleted | list:updated | list:deleted, plus heartbeat every 30s
  • description: Scoped live-list fan-out stream (LIST-04, D-04). Subscribes only to list channels accessible to the caller (owner + shares). Event payload triggers client-side query invalidation (D-10). 30s keepalive heartbeat for Pangolin tunnel.

GET /api/lists

  • method: GET
  • path: /api/lists
  • auth: oidcAuthMiddleware
  • file: apps/api/src/routes/lists.ts
  • response: { lists: [{ id, name, isShared, ownerId, activeCount, doneCount, createdAt, updatedAt }] }
  • description: Scoped list index. Returns only lists the caller owns or has a list_shares row for (T-04-02, D-04). Includes per-list item counts.

POST /api/lists

  • method: POST
  • path: /api/lists
  • auth: oidcAuthMiddleware
  • body: { name: string, isShared?: boolean (default true) }
  • file: apps/api/src/routes/lists.ts
  • response: 201 { id, name, isShared, ownerId, activeCount, doneCount, createdAt, updatedAt }
  • description: Create a named list. isShared=true (default) auto-inserts list_shares for all other members (D-01/D-02). Shares are server-managed only — no client shares endpoint (T-04-08).

PATCH /api/lists/:id

  • method: PATCH
  • path: /api/lists/:id
  • auth: oidcAuthMiddleware
  • params: id (path)
  • body: { name?: string, isShared?: boolean } — at least one field required
  • file: apps/api/src/routes/lists.ts
  • response: { id, name, isShared, ownerId, createdAt, updatedAt }
  • description: Update name and/or isShared. Caller must be owner or sharee. isShared mutations owner-only (T-04-07/T-04-08). Visibility change reconciles list_shares (false→true inserts; true→false deletes all non-owner shares).

DELETE /api/lists/:id

  • method: DELETE
  • path: /api/lists/:id
  • auth: oidcAuthMiddleware
  • params: id (path)
  • file: apps/api/src/routes/lists.ts
  • response: { id }
  • description: Owner-only delete. Cascade via FK onDelete:cascade removes items and shares. Non-owner sharees receive 403.

GET /api/lists/:id/items

  • method: GET
  • path: /api/lists/:id/items
  • auth: oidcAuthMiddleware
  • params: id (path)
  • file: apps/api/src/routes/lists.ts
  • response: { items: [{ id, listId, text, checked, rank, createdAt, updatedAt }] }
  • description: Returns all items for the list ordered by rank ASC. Access-gated: owner or sharee only (T-04-05).

POST /api/lists/:id/items

  • method: POST
  • path: /api/lists/:id/items
  • auth: oidcAuthMiddleware
  • params: id (path)
  • body: { text: string (1..500) }
  • file: apps/api/src/routes/lists.ts
  • response: 201 { id, listId, text, checked, rank, createdAt, updatedAt }
  • description: Add item to list. Rank assigned via rankForAppend(lastActiveRank) — appends after last unchecked item. Access-gated (T-04-05). publishListEvent item:added fan-out.

PATCH /api/list-items/:itemId

  • method: PATCH
  • path: /api/list-items/:itemId
  • auth: oidcAuthMiddleware
  • params: itemId (path)
  • body: exactly one of: { checked: boolean } | { text: string } | { position: string }
  • file: apps/api/src/routes/lists.ts
  • response: { id, listId, text, checked, rank, createdAt, updatedAt }
  • description: Per-field last-write-wins item update (D-08). Exactly one field enforced by Zod (T-04-07). uncheck (checked:false) recomputes rank to active-bottom. 404 if item missing (T-04-09, no upsert). publishListEvent item:updated fan-out.

DELETE /api/list-items/:itemId

  • method: DELETE
  • path: /api/list-items/:itemId
  • auth: oidcAuthMiddleware
  • params: itemId (path)
  • file: apps/api/src/routes/lists.ts
  • response: { id }
  • description: Delete item instantly (D-06). Delete-wins semantics (D-09): no rollback path. Access-gated: owner or sharee. publishListEvent item:deleted fan-out.