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
+41
View File
@@ -0,0 +1,41 @@
import { useQuery } from '@tanstack/react-query'
interface HealthResponse {
ok: boolean
db: string
}
async function fetchHealth(): Promise<HealthResponse> {
const res = await fetch('/health')
if (!res.ok) {
throw new Error(`Health check failed: ${res.status}`)
}
return res.json() as Promise<HealthResponse>
}
export default function App() {
const { data, isLoading, isError } = useQuery({
queryKey: ['health'],
queryFn: fetchHealth,
retry: 1,
refetchInterval: 30_000,
})
return (
<div style={{ fontFamily: 'system-ui, sans-serif', padding: '2rem', maxWidth: '480px', margin: '0 auto' }}>
<h1 style={{ fontSize: '1.5rem', marginBottom: '1rem' }}>FamilySync</h1>
<div
style={{
padding: '1rem',
borderRadius: '8px',
background: isLoading ? '#f5f5f5' : isError ? '#fee2e2' : '#dcfce7',
color: isLoading ? '#666' : isError ? '#991b1b' : '#166534',
}}
>
{isLoading && 'Checking stack...'}
{isError && 'stack: down'}
{data && `stack: ${data.ok && data.db === 'up' ? 'up' : 'down'}`}
</div>
</div>
)
}