- 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
71 lines
2.7 KiB
TypeScript
71 lines
2.7 KiB
TypeScript
/**
|
|
* CAL-08 spike script: enumerate Fastmail calendar collections for a single account.
|
|
*
|
|
* Purpose: Confirm that a Fastmail app password ("Mail, Contacts & Calendars" scope)
|
|
* can read BOTH the shared family calendar AND Lucas's personal calendar via
|
|
* CalDAV PROPFIND. This is the go/no-go gate for the N-credential per-member model
|
|
* described in D-02/D-05.
|
|
*
|
|
* Run:
|
|
* cd apps/api
|
|
* FASTMAIL_EMAIL=lucas@fastmail.com \
|
|
* FASTMAIL_APP_PASSWORD=<app-pw> \
|
|
* pnpm exec tsx src/broker/spike.ts
|
|
*
|
|
* Output: prints each returned calendar collection — url, displayName, ctag, syncToken.
|
|
* Expected: shared family calendar + Lucas's personal calendar both appear.
|
|
*
|
|
* Security (T-04-04):
|
|
* - App password is read from env, never logged.
|
|
* - Output prints only calendar URLs and display names, not the password.
|
|
* - This script is dev-only; NOT imported by the API or Docker image (T-04-SC).
|
|
*
|
|
* After running, record results in:
|
|
* .planning/phases/01-foundation-broker-spike/CAL-08-DECISION.md
|
|
*/
|
|
|
|
import { createFastmailClient } from './client.js'
|
|
|
|
async function main() {
|
|
const email = process.env.FASTMAIL_EMAIL
|
|
const appPassword = process.env.FASTMAIL_APP_PASSWORD
|
|
|
|
if (!email || !appPassword) {
|
|
console.error(
|
|
'Usage: FASTMAIL_EMAIL=<email> FASTMAIL_APP_PASSWORD=<pw> pnpm exec tsx src/broker/spike.ts',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
console.log(`[CAL-08 spike] Connecting to Fastmail CalDAV as: ${email}`)
|
|
console.log('[CAL-08 spike] Creating tsdav client...')
|
|
|
|
const client = await createFastmailClient(email, appPassword)
|
|
|
|
console.log('[CAL-08 spike] Fetching calendars via PROPFIND...')
|
|
const calendars = await client.fetchCalendars()
|
|
|
|
console.log(`\n[CAL-08 spike] Found ${calendars.length} calendar collection(s):\n`)
|
|
|
|
for (const cal of calendars) {
|
|
console.log('---')
|
|
console.log(` url: ${cal.url}`)
|
|
console.log(` displayName: ${cal.displayName ?? '(none)'}`)
|
|
// ctag/syncToken: Fastmail may return either field (Pitfall #6)
|
|
console.log(` ctag: ${(cal as { ctag?: string }).ctag ?? '(not returned)'}`)
|
|
console.log(` syncToken: ${(cal as { syncToken?: string }).syncToken ?? '(not returned)'}`)
|
|
}
|
|
|
|
console.log('\n[CAL-08 spike] Done.')
|
|
console.log('\nNext steps:')
|
|
console.log(' 1. Confirm the shared family calendar URL appears above.')
|
|
console.log(' 2. Confirm Lucas\'s personal calendar URL appears above.')
|
|
console.log(' 3. Record both URLs and ctag/syncToken findings in:')
|
|
console.log(' .planning/phases/01-foundation-broker-spike/CAL-08-DECISION.md')
|
|
}
|
|
|
|
main().catch((err: unknown) => {
|
|
console.error('[CAL-08 spike] Fatal error:', err instanceof Error ? err.message : String(err))
|
|
process.exit(1)
|
|
})
|