docs(05-03): complete calendar backend plan

- Create 05-03-SUMMARY.md with full execution documentation
- Update STATE.md: advance to plan 4/5, add decisions, record metrics
- Update ROADMAP.md: mark 05-03 complete, 3/5 plans done
- Update REQUIREMENTS.md: CAL-01/02/03 + DASH-05 backend complete
This commit is contained in:
2026-06-24 15:19:27 +02:00
parent bb8990ca66
commit 694bcff4a4
4 changed files with 177 additions and 16 deletions
+4 -4
View File
@@ -147,12 +147,12 @@ Which phases cover which requirements. Updated during roadmap creation.
| DASH-02 | Phase 5 | Complete |
| DASH-03 | Phase 5 | Complete |
| DASH-04 | Phase 5 | Complete |
| DASH-05 | Phase 5 | Pending |
| DASH-05 | Phase 5 | Backend Complete (05-03) |
| DASH-06 | Phase 5 | Complete |
| DASH-07 | Phase 5 | Complete |
| CAL-01 | Phase 5 | Pending |
| CAL-02 | Phase 5 | Pending |
| CAL-03 | Phase 5 | Pending |
| CAL-01 | Phase 5 | Backend Complete (05-03) |
| CAL-02 | Phase 5 | Backend Complete (05-03) |
| CAL-03 | Phase 5 | Backend Complete (05-03) |
| MOD-01 | Phase 3 | Pending |
| MOD-02 | Phase 3 | Pending |
| MOD-03 | Phase 3 | Pending |
+2 -2
View File
@@ -173,7 +173,7 @@ Decimal phases appear between their surrounding integers in numeric order.
**Wave 3** *(blocked on Wave 2 completion)*
- [ ] 05-03-PLAN.md -- Calendar backend: Prisma model, AES-256-GCM crypto, source CRUD, CalDAV/ICS/Exchange providers, DB push (CAL-01/02/03, DASH-05)
- [x] 05-03-PLAN.md -- Calendar backend: Prisma model, AES-256-GCM crypto, source CRUD, CalDAV/ICS/Exchange providers, DB push (CAL-01/02/03, DASH-05)
**Wave 4** *(blocked on Wave 3 completion)*
@@ -210,5 +210,5 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6
| 2. Authentication & Multi-Tenancy | 2/5 | In Progress| |
| 3. Module System & Domaincheck | 3/4 | In Progress| |
| 4. Marketplace & Portal Navigation | 0/4 | Not started | - |
| 5. Dashboard & Calendar | 2/5 | In Progress| |
| 5. Dashboard & Calendar | 3/5 | In Progress| |
| 6. Desktop Client & CI/CD | 0/TBD | Not started | - |
+15 -10
View File
@@ -3,15 +3,15 @@ gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
status: executing
stopped_at: Completed 05-02-PLAN.md
last_updated: "2026-06-24T13:02:42.630Z"
last_activity: 2026-06-24 -- Phase 05 execution started
stopped_at: Completed 05-03-PLAN.md
last_updated: "2026-06-24T13:16:25Z"
last_activity: 2026-06-24 -- Calendar backend complete
progress:
total_phases: 6
completed_phases: 3
total_plans: 21
completed_plans: 17
percent: 50
completed_plans: 18
percent: 86
---
# Project State
@@ -26,11 +26,11 @@ See: .planning/PROJECT.md (updated 2026-06-18)
## Current Position
Phase: 05 (dashboard-calendar) — EXECUTING
Plan: 3 of 5
Plan: 4 of 5
Status: Ready to execute
Last activity: 2026-06-24 -- Phase 05 execution started
Last activity: 2026-06-24 -- Calendar backend complete (Plan 03)
Progress: [████████░░] 81%
Progress: [████████░░] 86%
## Performance Metrics
@@ -56,6 +56,7 @@ Progress: [████████░░] 81%
| Phase 02-authentication-multi-tenancy P03 | 5min | 2 tasks | 20 files |
| Phase 05-dashboard-calendar P01 | 14min | 4 tasks | 28 files |
| Phase 05-dashboard-calendar P02 | 4min | 4 tasks | 16 files |
| Phase 05-dashboard-calendar P03 | 11min | 4 tasks | 14 files |
## Accumulated Context
@@ -79,6 +80,10 @@ Recent decisions affecting current work:
- [Phase ?]: react-grid-layout v2 uses dragConfig/resizeConfig instead of isDraggable/isResizable
- [05-02]: SearchProvider defaults as constants merged with user DB rows (no seed migration)
- [05-02]: useRef with explicit undefined initial value for React 19 strict TypeScript
- [05-03]: DAVClient class constructor instead of createDAVClient factory (tsdav v2 type compatibility)
- [05-03]: Dynamic imports for ews-javascript-api and @microsoft/microsoft-graph-client (lazy-load)
- [05-03]: In-memory Map cache with 5-min TTL for calendar events (Redis not needed at current scale)
- [05-03]: ews-javascript-api imported as any (no TypeScript definitions available)
### Pending Todos
@@ -98,6 +103,6 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity
Last session: 2026-06-24T12:57:00Z
Stopped at: Completed 05-02-PLAN.md
Last session: 2026-06-24T13:16:25Z
Stopped at: Completed 05-03-PLAN.md
Resume file: None
@@ -0,0 +1,156 @@
---
phase: 05-dashboard-calendar
plan: 03
subsystem: api
tags: [calendar, caldav, ics, exchange, aes-256-gcm, tsdav, node-ical, ews, microsoft-graph, prisma, nestjs]
requires:
- phase: 05-dashboard-calendar/01
provides: Dashboard grid + widget registry + Prisma models (DashboardLayout, WidgetInstance)
- phase: 05-dashboard-calendar/02
provides: Widget settings panel pattern + SearchProvider CRUD pattern
provides:
- CalendarSource Prisma model with AES-256-GCM encrypted credentials
- CalendarModule with source CRUD, multi-protocol providers, event aggregation
- REST API endpoints for calendar source management and event fetching
- CalendarCryptoService for credential encryption/decryption
- ICS/CalDAV/Exchange provider implementations with graceful degradation
affects: [05-dashboard-calendar/04, 05-dashboard-calendar/05]
tech-stack:
added: [tsdav@2.2.2, node-ical@0.26.1, ews-javascript-api@0.15.3, @microsoft/microsoft-graph-client@3.0.7]
patterns: [provider-dispatch-by-type, encrypted-credentials-at-rest, ssrf-url-validation, promise-allsettled-aggregation, in-memory-ttl-cache]
key-files:
created:
- apps/api/src/calendar/calendar.module.ts
- apps/api/src/calendar/calendar.controller.ts
- apps/api/src/calendar/calendar.service.ts
- apps/api/src/calendar/crypto.service.ts
- apps/api/src/calendar/providers/ics.provider.ts
- apps/api/src/calendar/providers/caldav.provider.ts
- apps/api/src/calendar/providers/exchange.provider.ts
- apps/api/src/calendar/dto/create-calendar-source.dto.ts
- apps/api/src/calendar/dto/update-calendar-source.dto.ts
- apps/api/src/calendar/dto/calendar-events-query.dto.ts
modified:
- apps/api/prisma/schema.prisma
- apps/api/src/app.module.ts
- apps/api/package.json
- docker-compose.yml
key-decisions:
- "DAVClient class constructor + login() instead of createDAVClient factory (type safety with tsdav v2)"
- "Dynamic imports for ews-javascript-api and @microsoft/microsoft-graph-client (lazy-load, avoid unused SDK weight)"
- "In-memory Map cache with 5-min TTL for events (Redis not needed at current scale; Pitfall 4)"
- "ews-javascript-api imported as any due to no TypeScript definitions available"
patterns-established:
- "Provider dispatch pattern: getProvider(type) returns CalendarProvider implementation by source.type"
- "Credential encryption: AES-256-GCM with iv:authTag:ciphertext hex format"
- "SSRF validation: https-only DTO + private IP range blocking in service"
- "Safe select pattern: SOURCE_SAFE_SELECT excludes encryptedPassword, derives hasCredentials boolean"
requirements-completed: [CAL-01, CAL-02, CAL-03, DASH-05]
duration: 11min
completed: 2026-06-24
---
# Phase 05 Plan 03: Calendar Backend Summary
**Multi-protocol calendar backend with CalDAV/ICS/Exchange providers, AES-256-GCM credential encryption, SSRF-protected source CRUD, and in-memory cached event aggregation**
## Performance
- **Duration:** 11 min
- **Started:** 2026-06-24T13:05:25Z
- **Completed:** 2026-06-24T13:16:25Z
- **Tasks:** 4
- **Files modified:** 14
## Accomplishments
- CalendarSource Prisma model with encrypted credentials pushed to live PostgreSQL
- Full CalendarModule: source CRUD (6 endpoints), ownership checks, SSRF validation
- Three calendar providers: ICS (node-ical + RRULE expansion), CalDAV (tsdav), Exchange (Graph API + EWS)
- Event aggregation across visible sources with Promise.allSettled, per-user cache with 5-min TTL
- Credential encryption via AES-256-GCM; passwords never returned in API responses
## Task Commits
Each task was committed atomically:
1. **Task 1: Calendar backend -- model, crypto, source CRUD module** - `9ec6313` (feat)
2. **Task 2: Calendar providers (CalDAV, ICS, Exchange)** - `389ac9b` (feat)
3. **Task 3: Event aggregation + caching backend** - `0cd8efe` (feat)
4. **Task 4: Prisma schema push + encryption key check** - `bb8990c` (chore)
## Files Created/Modified
- `apps/api/prisma/schema.prisma` - Added CalendarSource model (16 columns)
- `apps/api/src/calendar/calendar.module.ts` - NestJS module with all providers registered
- `apps/api/src/calendar/calendar.controller.ts` - REST endpoints for sources + events
- `apps/api/src/calendar/calendar.service.ts` - Source CRUD + event aggregation with cache
- `apps/api/src/calendar/crypto.service.ts` - AES-256-GCM encrypt/decrypt service
- `apps/api/src/calendar/providers/ics.provider.ts` - ICS fetch + parse + RRULE expansion
- `apps/api/src/calendar/providers/caldav.provider.ts` - CalDAV via tsdav with time-range filter
- `apps/api/src/calendar/providers/exchange.provider.ts` - Exchange Graph API + EWS dispatch
- `apps/api/src/calendar/dto/create-calendar-source.dto.ts` - Create DTO with https-only validation
- `apps/api/src/calendar/dto/update-calendar-source.dto.ts` - Update DTO with isVisible toggle
- `apps/api/src/calendar/dto/calendar-events-query.dto.ts` - Events query DTO (from/to dates)
- `apps/api/src/app.module.ts` - Registered CalendarModule
- `apps/api/package.json` - Added tsdav, node-ical, ews-javascript-api, @microsoft/microsoft-graph-client
- `docker-compose.yml` - Added CALENDAR_ENCRYPTION_KEY env var
## Decisions Made
- Used `DAVClient` class constructor + `login()` instead of `createDAVClient` factory function because the factory returns a plain object without `login()` method in tsdav v2 types
- Dynamic imports for EWS and Graph SDKs to avoid loading unused SDK code at startup
- In-memory Map cache with 5-minute TTL for events (Redis overkill at current scale; easy to swap later)
- `ews-javascript-api` imported as `any` since the library ships no TypeScript definitions
- ICS provider uses native `fetch()` + `ical.sync.parseICS()` instead of `ical.async.fromURL()` for better timeout control (AbortController)
## Deviations from Plan
None - plan executed exactly as written.
## Issues Encountered
- **tsdav `createDAVClient` type mismatch:** The factory function returns a utility object missing the `login()` method in its type. Resolved by using the `DAVClient` class constructor directly.
- **ews-javascript-api no TypeScript definitions:** Library is JS-only with no `.d.ts` files. The `DateTime` type expected by `CalendarView` constructor is an EWS-internal type. Resolved by importing as `any` and using JS Date objects which the runtime accepts.
- **node-ical `async.fromURL` overload ambiguity:** Passing options to the async overload causes TypeScript to select the void-returning callback signature. Resolved by using native `fetch()` + `sync.parseICS()` instead.
## User Setup Required
**CALENDAR_ENCRYPTION_KEY** must be configured before the CalendarModule can start:
1. Generate a 32-byte hex key: `openssl rand -hex 32`
2. Add to `apps/api/.env`: `CALENDAR_ENCRYPTION_KEY=<generated-key>`
3. Add to Docker environment or `.env` root for production deployment
4. The key has been added to the development `.env` and `docker-compose.yml`
## Threat Mitigations Implemented
| Threat ID | Mitigation |
|-----------|------------|
| T-05-09 | Prisma `select` excludes encryptedPassword; returns `hasCredentials` boolean |
| T-05-10 | AES-256-GCM encryption via CalendarCryptoService |
| T-05-11 | DTO https-only + private IP range blocking + localhost/internal blocking |
| T-05-12 | All endpoints scoped to userId; ownership check before update/delete |
| T-05-13 | Generic error messages; no credential details in error responses |
## Next Phase Readiness
- Calendar backend API complete and ready for frontend integration (Plan 04/05)
- CalendarSource table live in PostgreSQL
- Event aggregation endpoint functional — frontend can fetch via `GET /calendar/events`
- Source CRUD ready for Settings UI — `GET/POST/PATCH/DELETE /calendar/sources`
## Self-Check: PASSED
- All 10 created files verified present
- All 4 task commits verified in git log (9ec6313, 389ac9b, 0cd8efe, bb8990c)
---
*Phase: 05-dashboard-calendar*
*Completed: 2026-06-24*