mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Fitbit re-scores a night after the fact, and the per-segment externalId carried a positional index plus the stage label. Both moved on a re-score: renumbered segments minted fresh keys, the previous scoring's rows stayed live because nothing swept them, and the night total counted both copies from then on. Key the segment on the stable session anchor plus its own start instant (`<logId>:sleep:<segment-start>`), and add the replace-by-window sweep every other segment-writing provider already has. The two ship together with the natural-key rescue that has been sitting in the Fitbit upsert unused: the sweep tombstones the old-keyed rows first, so the rescue re-keys those very rows in place instead of inserting a parallel copy or losing the insert to `skipDuplicates` against the natural unique index. A one-shot repair queue re-reads the bounded sleep history once per connection so no user has to do anything; it stamps `sleepRepairedAt` (migration 0270) only on a clean run, never touches the sync watermark, and reports the rows it removed. Nights older than the 365-day backfill horizon keep their old keys and whatever duplicates they already hold — they can no longer gain new ones. Alongside: `fitbitGet` now honours a `Retry-After` inside a 120 s cap by waiting once and retrying once, and fails fast above it so a long hold cannot starve the rest of the poll cohort or outlive a job's expiration. The measurement upsert skips a write whose payload is unchanged, which takes the hourly overlap off the delta feed for every Fitbit resource.
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).