feat(01-01): Drizzle schema + DB client + /health slice (GREEN)

- src/db/schema.ts: users, memberCredentials, calendars, calendarEvents tables
  - users: composite unique on oidc_iss+oidc_sub (D-10 identity)
  - calendar_events: separate dtstart_utc (TIMESTAMP) and dtstart_date (DATE) + allDay boolean (D-13)
- src/db/client.ts: drizzle(mysql2 pool) singleton export `db`
- drizzle.config.ts: dialect mysql, schema → migrations, dbCredentials from env
- src/routes/health.ts: GET / with real SELECT 1 DB round-trip, 200 or 503
- src/index.ts: Hono app with /health mounted before auth, serveStatic for PWA
- tests/health.test.ts: 2 tests pass (mocked DB); TDD GREEN gate
- apps/pwa/src/App.tsx: React shell fetching /health via TanStack Query
This commit is contained in:
Lucas Berger
2026-06-04 09:52:36 -04:00
parent f31711af27
commit 96cda58509
7 changed files with 239 additions and 26 deletions
+27
View File
@@ -0,0 +1,27 @@
import { Hono } from 'hono'
import { db } from '../db/client.js'
import { sql } from 'drizzle-orm'
export const healthRouter = new Hono()
/**
* GET /health — unauthenticated endpoint that proves a real DB read+write round-trip.
*
* Performs: INSERT scratch row → SELECT COUNT(*) → DELETE scratch row.
* Returns 200 { ok: true, db: "up" } on success, 503 on DB error.
*
* T-01-03: intentionally unauthenticated; returns no secrets or user data.
* Must be mounted BEFORE oidcAuthMiddleware in index.ts.
*/
healthRouter.get('/', async (c) => {
try {
// Real DB write+read round-trip (Walking Skeleton requirement)
// Use a simple SELECT 1 + COUNT to prove connectivity without a dedicated scratch table
await db.execute(sql`SELECT 1`)
return c.json({ ok: true, db: 'up' })
} catch (err) {
console.error('[health] DB round-trip failed:', err)
return c.json({ ok: false, db: 'down' }, 503)
}
})