| 03-event-write-back-pwa-install |
03 |
api |
| hono |
| drizzle |
| zod |
| calendarOutbox |
| write-back |
| outbox-pattern |
| access-control |
| tdd |
|
| phase |
provides |
| 03-event-write-back-pwa-install/03-01 |
calendarOutbox schema + calendarEvents.objectUrl + Wave-0 RED test scaffold |
|
| phase |
provides |
| 03-event-write-back-pwa-install/03-02 |
broker primitives (vevent.ts, write.ts) — not used by routes but confirm broker boundary |
|
|
| POST /api/events/create — validates, checks D-03 ownership, enqueues pending outbox row, returns 202 with uid |
| PATCH /api/events/:uid/edit — looks up event, checks ownership, enqueues update or transaction-paired delete+create for calendar moves |
| DELETE /api/events/:uid — looks up event, checks ownership, enqueues delete row with server-side etag |
| GET /api/events/sync-status — member-scoped outbox status poll (D-09) |
| GET /api/events/writable-calendars — authoritative D-03 writable set (own personal + shared Family; never other member's personal) |
| zod schemas for event fields (title 255, location/description 2000 — T-03-08 bounds) |
|
| 03-04 (outbox worker drains rows these endpoints enqueue) |
| 03-05 (EventForm + client.ts consume these endpoints + writable-calendars) |
|
| added |
patterns |
|
|
| resolveUserId(c): dev-bypass c.get('user') first, fallback to getAuth(c) for OIDC — same pattern as me.ts |
| Enqueue-only write endpoints: no Fastmail call in routes; db.insert(calendarOutbox) is the only side effect |
| Edit-as-move: db.transaction with paired delete+create sharing a groupId (D-04) |
| sync-status: .orderBy(desc(createdAt)).limit(1) to get latest outbox row; userId-scoped (T-03-07) |
| writable-calendars: WHERE userId=currentUser.id OR isShared=1 — authoritative D-03 enforcement (T-03-11) |
| Test mock pattern for db.transaction: factory fn cb receives mock tx with insert; vi.mock hoisted factory captures mutable refs |
| devAuthBypass mock in tests: vi.mock('../auth/devBypass.js') injects dev user so write tests get authenticated context |
|
|
| created |
modified |
|
|
| apps/api/src/routes/events.ts |
| apps/api/tests/routes/events.test.ts |
|
|
| resolveUserId helper uses any type to avoid Hono context generic complexity — acceptable for internal helper |
| Two-query ownership check for edit/delete (get event, then check calendar isShared) to maintain simple from().where() chain that test mocks can intercept without innerJoin complexity |
| Writable-calendars response maps to { url, displayName, color, isShared } — the Plan 05 WritableCalendar shape |
| sync-status returns { uid, status: 'done' } when no outbox row found (nothing pending = settled) |
|
| Enqueue-only write route: validate → check ownership → db.insert(calendarOutbox) → return 202; no broker call |
| D-03 ownership enforcement at two layers: write endpoints AND writable-calendars listing |
| vi.mock devAuthBypass for write-endpoint tests avoids needing ENV manipulation or OIDC infrastructure |
|
| CAL-04 |
| CAL-05 |
| CAL-06 |
| CAL-07 |
|
7min |
2026-06-05 |