mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
A person's whole 'which doctor' history was one free-text column on a reminder: they typed the practice name once and could reach nothing else that happened there. This turns that string into a record. Five tables in migration 0320, all additive: an encounter that happened or is booked, the account's own address book of practitioners, and three links from a visit to the documents, lab results and conditions it produced. The encounter is slim on purpose — the diagnosis stays on the linked condition, the address on the linked practitioner, and the reason each field was kept or cut is written at the model. No module gate, and the schema says why rather than leaving the absence to be read as an oversight. Every carrier lands with the tables rather than after them. All five are classified as travelling both ways and are proven doing it: a seeded row of each goes out through the real export builder, the account is deleted so the schema's cascade decides what survives, and the rows are counted back through the real restore route. Two references deliberately do not survive and are named instead of dropped quietly — the reminder a visit points at is not in the backup, and a link whose far side a portable export does not carry cannot resolve. The wipe plan deletes the links before the lab results and documents they hang off, or the audit row would report zero for rows that existed. The three encrypted columns are registered for key rotation, without which dropping a legacy key would make those notes permanently unreadable. Widening the reminder origin enum is a type error at the reminder DTO, which is the right outcome: the mapper now refuses an appointment row outright rather than relabelling it as a checkup.
prisma/
The database layer. HealthLog runs PostgreSQL 16 via Prisma 7. This directory holds the single schema file and the forward-only migration history; the generated client is emitted elsewhere.
Contents
schema.prisma— 61 models, one file.cuid()primary keys,snake_casecolumns via@map(...), encrypted columns mostly suffixed*Encrypted(search the file; there are 24 encrypted column references). Thegenerator clientblock emits to../src/generated/prisma(imported as@/generated/prisma).migrations/— numbered, forward-only SQL migrations (0001_…upward).migration_lock.tomlpins the provider topostgresql.
Migration workflow
# author a migration from a schema edit
pnpm dlx prisma migrate dev --name <short_snake_case_name>
# regenerate the client after any schema change
pnpm dlx prisma generate
Number a new migration with the next free prefix. The production entrypoint runs prisma migrate deploy on boot, so every migration must apply cleanly forward — there is no down-migration path. Always include the migration file in the same change as the schema edit.
Conventions
- Encryption is fail-closed:
*Encryptedcolumns are written through@/lib/crypto(AES-256-GCM, versioned key ids); never store plaintext in an encrypted column. Key rotation isscripts/rotate-encryption-key.ts+docs/ops/encryption-key-rotation.md. - The generated client under
src/generated/prismais build output — do not edit it. - Schema-touching changes that affect the API surface must regenerate
docs/api/openapi.yaml(see../CLAUDE.md).
Related: ../src/lib/README.md (db.ts, rollups/), ../docs/ops/ (migrations + backup runbooks).