style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
@@ -3,7 +3,7 @@ phase: 02-calendar-display
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["02-01"]
|
||||
depends_on: ['02-01']
|
||||
files_modified:
|
||||
- apps/api/src/broker/expand.ts
|
||||
- apps/api/src/routes/events.ts
|
||||
@@ -15,27 +15,27 @@ user_setup: []
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "GET /api/events?start=&end= returns a flat array of concrete occurrences (no recurring masters, no raw VCALENDAR blobs) windowed to the requested date range"
|
||||
- "Each occurrence carries the owner member color (from users.color) or the shared-family rose, plus an isShared flag, an ownerUserId, and the DB calendarId"
|
||||
- "Recurring events are expanded server-side with VTIMEZONE registered before expansion so DST occurrences keep correct wall-clock time"
|
||||
- 'GET /api/events?start=&end= returns a flat array of concrete occurrences (no recurring masters, no raw VCALENDAR blobs) windowed to the requested date range'
|
||||
- 'Each occurrence carries the owner member color (from users.color) or the shared-family rose, plus an isShared flag, an ownerUserId, and the DB calendarId'
|
||||
- 'Recurring events are expanded server-side with VTIMEZONE registered before expansion so DST occurrences keep correct wall-clock time'
|
||||
- "All-day occurrences are returned with allDay:true and a 'YYYY-MM-DD' start (no timezone shift) — single local timezone for v1 (D-10)"
|
||||
- "EXDATE-excluded occurrences are omitted from the expansion"
|
||||
- "Invalid or missing start/end query params are rejected (zod) before any SQL runs; window capped at 90 days"
|
||||
- 'EXDATE-excluded occurrences are omitted from the expansion'
|
||||
- 'Invalid or missing start/end query params are rejected (zod) before any SQL runs; window capped at 90 days'
|
||||
artifacts:
|
||||
- path: "apps/api/src/broker/expand.ts"
|
||||
provides: "expandOccurrences() — ICAL.RecurExpansion + VTIMEZONE registration + allDay split → CalendarOccurrence[]"
|
||||
exports: ["expandOccurrences", "CalendarOccurrence"]
|
||||
- path: "apps/api/src/routes/events.ts"
|
||||
provides: "windowed /api/events with calendarEvents→calendars→users join, hasRrule pre-filter, zod validation"
|
||||
contains: "zValidator"
|
||||
- path: 'apps/api/src/broker/expand.ts'
|
||||
provides: 'expandOccurrences() — ICAL.RecurExpansion + VTIMEZONE registration + allDay split → CalendarOccurrence[]'
|
||||
exports: ['expandOccurrences', 'CalendarOccurrence']
|
||||
- path: 'apps/api/src/routes/events.ts'
|
||||
provides: 'windowed /api/events with calendarEvents→calendars→users join, hasRrule pre-filter, zod validation'
|
||||
contains: 'zValidator'
|
||||
key_links:
|
||||
- from: "apps/api/src/routes/events.ts"
|
||||
to: "apps/api/src/broker/expand.ts"
|
||||
via: "expandOccurrences() called per recurring/timed row"
|
||||
pattern: "expandOccurrences"
|
||||
- from: "apps/api/src/routes/events.ts"
|
||||
to: "users.color"
|
||||
via: "innerJoin calendars→users, select color + isShared + users.id"
|
||||
- from: 'apps/api/src/routes/events.ts'
|
||||
to: 'apps/api/src/broker/expand.ts'
|
||||
via: 'expandOccurrences() called per recurring/timed row'
|
||||
pattern: 'expandOccurrences'
|
||||
- from: 'apps/api/src/routes/events.ts'
|
||||
to: 'users.color'
|
||||
via: 'innerJoin calendars→users, select color + isShared + users.id'
|
||||
pattern: "users\\.color"
|
||||
---
|
||||
|
||||
@@ -102,6 +102,7 @@ shared-family calendar (open question A3) — the operator marks it.
|
||||
7. Use rrule ONLY as a fallback if ICAL.RecurExpansion cannot parse a given RRULE — do not import it on the primary path (D-09).
|
||||
|
||||
Turn the Plan 01 RED expand.test.ts stub green against the three fixtures.
|
||||
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd apps/api && pnpm test -- tests/broker/expand.test.ts</automated>
|
||||
@@ -145,6 +146,7 @@ shared-family calendar (open question A3) — the operator marks it.
|
||||
For each row, derive `color = row.isShared ? '#F25C7A' : row.userColor` and `isShared = row.isShared`, then call `expandOccurrences(row.rawVevent, windowStartDate, windowEndDate, { calendarId, calendarName: row.displayName, ownerUserId: row.userId, color, isShared })`. The `ownerUserId: row.userId` field is load-bearing — the client routes calendar color by it. Flatten all results into one array. Wrap the DB+expansion body in try/catch returning 503 on DB error (health.ts pattern). Return `c.json({ occurrences })`.
|
||||
|
||||
Turn the Plan 01 RED events.test.ts stub green (mock db.select chain following the health.test.ts vi.mock pattern; assert color field, isShared, ownerUserId, and 400 on bad params).
|
||||
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd apps/api && pnpm test -- tests/routes/events.test.ts</automated>
|
||||
@@ -184,21 +186,23 @@ shared-family calendar (open question A3) — the operator marks it.
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| browser → /api/events | start/end query params are untrusted input crossing into SQL |
|
||||
| cached VEVENT → expansion | rawVevent originates from Fastmail; parsed by ical.js |
|
||||
| Boundary | Description |
|
||||
| ------------------------- | ------------------------------------------------------------ |
|
||||
| browser → /api/events | start/end query params are untrusted input crossing into SQL |
|
||||
| cached VEVENT → expansion | rawVevent originates from Fastmail; parsed by ical.js |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-02b-01 | Tampering | start/end query params | mitigate | zod ISO-date regex validation before SQL; Drizzle parameterized queries (no string interpolation) |
|
||||
| T-02b-02 | Denial of service | unwindowed/overwide fetch | mitigate | start+end required (zod); window hard-capped at 90 days; hasRrule index prevents full-table scan |
|
||||
| T-02b-03 | Information disclosure | cross-account calendar leakage | mitigate | Route is behind oidcAuthMiddleware (Phase 1); each member's own credential fetched their own collections; no other-account ACL path exists (CAL-08-DECISION) |
|
||||
| T-02b-04 | Tampering | malformed rawVevent | accept | expandOccurrences try/catch returns [] on parse failure; matches sync.ts resilience; no crash |
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
| --------- | ---------------------- | ------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| T-02b-01 | Tampering | start/end query params | mitigate | zod ISO-date regex validation before SQL; Drizzle parameterized queries (no string interpolation) |
|
||||
| T-02b-02 | Denial of service | unwindowed/overwide fetch | mitigate | start+end required (zod); window hard-capped at 90 days; hasRrule index prevents full-table scan |
|
||||
| T-02b-03 | Information disclosure | cross-account calendar leakage | mitigate | Route is behind oidcAuthMiddleware (Phase 1); each member's own credential fetched their own collections; no other-account ACL path exists (CAL-08-DECISION) |
|
||||
| T-02b-04 | Tampering | malformed rawVevent | accept | expandOccurrences try/catch returns [] on parse failure; matches sync.ts resilience; no crash |
|
||||
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
@@ -208,20 +212,22 @@ shared-family calendar (open question A3) — the operator marks it.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
- /api/events returns windowed, color-tagged, DST-correct, all-day-safe, EXDATE-aware occurrences
|
||||
- Each occurrence carries ownerUserId + isShared for client-side color routing
|
||||
- Bad/oversized windows rejected with 400
|
||||
- Shared-family calendar marked and verified end-to-end
|
||||
</success_criteria>
|
||||
</success_criteria>
|
||||
|
||||
<artifacts_produced>
|
||||
|
||||
## Artifacts this phase produces (Plan 02)
|
||||
|
||||
- `expandOccurrences` (function) + `CalendarOccurrence` (interface) — apps/api/src/broker/expand.ts
|
||||
- Evolved `eventsRouter` GET / handler with `{ occurrences }` response shape — apps/api/src/routes/events.ts
|
||||
- `eventsQuerySchema` (zod) for start/end validation
|
||||
- New JSON contract field set: id, uid, calendarId, calendarName, ownerUserId, color, isShared, title, start, end, allDay, location, description
|
||||
</artifacts_produced>
|
||||
</artifacts_produced>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/02-calendar-display/02-02-SUMMARY.md` when done
|
||||
|
||||
Reference in New Issue
Block a user