feat(01-04): wire broker + routes into bootstrap, add SSE endpoint, EventProof

- Mount /api/events, /api/sse in index.ts behind oidcAuthMiddleware; /callback + /health before guard
- Call startBrokerPoller() on boot (5-min ctag-poll background schedule)
- Add sseRouter with GET /heartbeat (streamSSE, 10s interval) for Pangolin SSE smoke test (D-08, T-04-01)
- Add CAL-08 spike script (broker/spike.ts): createFastmailClient → fetchCalendars → print calendar URLs
- Add fetchEvents() to pwa/api/client.ts with typed CalendarEvent/EventsResponse shapes
- Add EventProof.tsx: React Query ['events'], renders first event title+date or empty-state (CAL-01 broker proof)
- Update App.tsx to render MemberBadge + EventProof on landing page
- Add ical.js@2.2.1 to PWA dependencies for VEVENT summary parsing in EventProof
- All 24 API unit tests green; tsc --noEmit clean in both apps/api and apps/pwa
This commit is contained in:
Lucas Berger
2026-06-04 11:16:10 -04:00
parent d2d8333bf8
commit 48f90ceca9
8 changed files with 313 additions and 4 deletions
+38
View File
@@ -32,3 +32,41 @@ export async function fetchMe(): Promise<MeResponse> {
return res.json() as Promise<MeResponse>
}
/**
* A single cached calendar event from the broker's MariaDB cache.
* Mirrors the calendarEvents table schema from apps/api/src/db/schema.ts.
*/
export interface CalendarEvent {
id: number
calendarId: number
uid: string
etag: string | null
rawVevent: string
dtstartUtc: string | null
dtstartDate: string | null
allDay: boolean
updatedAt: string | null
}
export interface EventsResponse {
events: CalendarEvent[]
}
/**
* Fetches all cached calendar events from the broker's MariaDB cache.
* No live Fastmail call — the 5-min poller keeps the cache fresh.
*
* Used by EventProof to display the first cached event as broker proof (CAL-01).
*/
export async function fetchEvents(): Promise<EventsResponse> {
const res = await fetch('/api/events', {
credentials: 'include',
})
if (!res.ok) {
throw new Error(`GET /api/events failed: ${res.status}`)
}
return res.json() as Promise<EventsResponse>
}