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:
@@ -147,12 +147,12 @@ Which phases cover which requirements. Updated during roadmap creation.
|
|||||||
| DASH-02 | Phase 5 | Complete |
|
| DASH-02 | Phase 5 | Complete |
|
||||||
| DASH-03 | Phase 5 | Complete |
|
| DASH-03 | Phase 5 | Complete |
|
||||||
| DASH-04 | 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-06 | Phase 5 | Complete |
|
||||||
| DASH-07 | Phase 5 | Complete |
|
| DASH-07 | Phase 5 | Complete |
|
||||||
| CAL-01 | Phase 5 | Pending |
|
| CAL-01 | Phase 5 | Backend Complete (05-03) |
|
||||||
| CAL-02 | Phase 5 | Pending |
|
| CAL-02 | Phase 5 | Backend Complete (05-03) |
|
||||||
| CAL-03 | Phase 5 | Pending |
|
| CAL-03 | Phase 5 | Backend Complete (05-03) |
|
||||||
| MOD-01 | Phase 3 | Pending |
|
| MOD-01 | Phase 3 | Pending |
|
||||||
| MOD-02 | Phase 3 | Pending |
|
| MOD-02 | Phase 3 | Pending |
|
||||||
| MOD-03 | Phase 3 | Pending |
|
| MOD-03 | Phase 3 | Pending |
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ Decimal phases appear between their surrounding integers in numeric order.
|
|||||||
|
|
||||||
**Wave 3** *(blocked on Wave 2 completion)*
|
**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)*
|
**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| |
|
| 2. Authentication & Multi-Tenancy | 2/5 | In Progress| |
|
||||||
| 3. Module System & Domaincheck | 3/4 | In Progress| |
|
| 3. Module System & Domaincheck | 3/4 | In Progress| |
|
||||||
| 4. Marketplace & Portal Navigation | 0/4 | Not started | - |
|
| 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 | - |
|
| 6. Desktop Client & CI/CD | 0/TBD | Not started | - |
|
||||||
|
|||||||
+15
-10
@@ -3,15 +3,15 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: executing
|
status: executing
|
||||||
stopped_at: Completed 05-02-PLAN.md
|
stopped_at: Completed 05-03-PLAN.md
|
||||||
last_updated: "2026-06-24T13:02:42.630Z"
|
last_updated: "2026-06-24T13:16:25Z"
|
||||||
last_activity: 2026-06-24 -- Phase 05 execution started
|
last_activity: 2026-06-24 -- Calendar backend complete
|
||||||
progress:
|
progress:
|
||||||
total_phases: 6
|
total_phases: 6
|
||||||
completed_phases: 3
|
completed_phases: 3
|
||||||
total_plans: 21
|
total_plans: 21
|
||||||
completed_plans: 17
|
completed_plans: 18
|
||||||
percent: 50
|
percent: 86
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -26,11 +26,11 @@ See: .planning/PROJECT.md (updated 2026-06-18)
|
|||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 05 (dashboard-calendar) — EXECUTING
|
Phase: 05 (dashboard-calendar) — EXECUTING
|
||||||
Plan: 3 of 5
|
Plan: 4 of 5
|
||||||
Status: Ready to execute
|
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
|
## Performance Metrics
|
||||||
|
|
||||||
@@ -56,6 +56,7 @@ Progress: [████████░░] 81%
|
|||||||
| Phase 02-authentication-multi-tenancy P03 | 5min | 2 tasks | 20 files |
|
| 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 P01 | 14min | 4 tasks | 28 files |
|
||||||
| Phase 05-dashboard-calendar P02 | 4min | 4 tasks | 16 files |
|
| Phase 05-dashboard-calendar P02 | 4min | 4 tasks | 16 files |
|
||||||
|
| Phase 05-dashboard-calendar P03 | 11min | 4 tasks | 14 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -79,6 +80,10 @@ Recent decisions affecting current work:
|
|||||||
- [Phase ?]: react-grid-layout v2 uses dragConfig/resizeConfig instead of isDraggable/isResizable
|
- [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]: 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-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
|
### Pending Todos
|
||||||
|
|
||||||
@@ -98,6 +103,6 @@ Items acknowledged and carried forward from previous milestone close:
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-06-24T12:57:00Z
|
Last session: 2026-06-24T13:16:25Z
|
||||||
Stopped at: Completed 05-02-PLAN.md
|
Stopped at: Completed 05-03-PLAN.md
|
||||||
Resume file: None
|
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*
|
||||||
Reference in New Issue
Block a user