chore(01-01): scaffold monorepo, Docker Compose stack, and Vitest harness

- pnpm workspace with apps/api (Hono/Drizzle) and apps/pwa (Vite/React 19)
- Pinned versions per RESEARCH: hono@4.12.23, drizzle-orm@0.45.2, mysql2@3.22.4, tsdav@2.2.2, ical.js@2.2.1, zod@^3.25.0, node-cron@^4.2.1
- docker-compose.yml with mariadb:11 healthcheck, api depends_on service_healthy, redis stub
- docker-compose.dev.yml overrides for local dev (bind mounts, exposed ports)
- .env.example lists all env vars (DB_*, OIDC_*, APP_PASSWORD_ENCRYPTION_KEY)
- .gitignore excludes .env (never commit secrets)
- apps/api/vitest.config.ts with environment: node
- Wave 0 test stubs: health, auth/user, broker/crypto, broker/sync, broker/poller
This commit is contained in:
Lucas Berger
2026-06-04 09:50:16 -04:00
parent fb849605b1
commit 3f591566d1
22 changed files with 2923 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
/**
* Wave 0 stub — Broker: AES-GCM app-password encryption
*
* These tests are RED stubs. Implementation lives in:
* apps/api/src/broker/crypto.ts (Plan 03)
*
* Tests will be filled GREEN in Plan 03 when crypto helpers are implemented.
*/
import { describe, it } from 'vitest'
describe('encryptPassword / decryptPassword', () => {
it.todo('roundtrip: decrypt(encrypt(plaintext)) === plaintext (Plan 03)')
it.todo('different IVs produce different ciphertext for the same plaintext (Plan 03)')
it.todo('decrypting with a tampered authTag throws (Plan 03)')
it.todo('decrypting with a tampered ciphertext throws (Plan 03)')
it.todo('stored payload is valid JSON with iv, authTag, ciphertext fields (Plan 03)')
})
+24
View File
@@ -0,0 +1,24 @@
/**
* Wave 0 stub — Broker: ctag polling + change detection
*
* These tests are RED stubs. Implementation lives in:
* apps/api/src/broker/poller.ts (Plan 03)
*
* Tests will be filled GREEN in Plan 03 when the poller is implemented.
*
* Key behavior (D-13): ctag unchanged → no DB write (skip sync entirely)
*/
import { describe, it } from 'vitest'
describe('broker poller', () => {
it.todo('skips syncCalendar when ctag is unchanged (Plan 03)')
it.todo('calls syncCalendar when ctag changes (Plan 03)')
it.todo('calls syncCalendar when ctag was null (first sync) (Plan 03)')
it.todo('handles decryptPassword failure gracefully without crashing the poller (Plan 03)')
it.todo('processes all member credentials in a poll cycle (Plan 03)')
})
+29
View File
@@ -0,0 +1,29 @@
/**
* Wave 0 stub — Broker: syncCalendar event upsert + all-day handling
*
* These tests are RED stubs. Implementation lives in:
* apps/api/src/broker/sync.ts (Plan 03)
*
* Tests will be filled GREEN in Plan 03 when syncCalendar is implemented.
*
* Key behaviors to verify (D-13):
* - All-day events: dtstart_date (DATE) set, dtstart_utc NULL, allDay=true
* - Timed events: dtstart_utc (TIMESTAMP UTC) set, dtstart_date NULL, allDay=false
* - UID used as idempotency key: second sync of same UID is an upsert, not duplicate
*/
import { describe, it } from 'vitest'
describe('syncCalendar', () => {
it.todo('stores all-day events with dtstart_date (DATE) and dtstart_utc=NULL (Plan 03)')
it.todo('stores timed events with dtstart_utc (TIMESTAMP) and dtstart_date=NULL (Plan 03)')
it.todo('sets allDay=true for all-day events, allDay=false for timed (Plan 03)')
it.todo('upserts on duplicate UID within the same calendar (Plan 03)')
it.todo('stores the raw VEVENT blob in rawVevent column (Plan 03)')
it.todo('updates the calendar ctag/syncToken after a successful sync (Plan 03)')
})