mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
Migration 0121 replaces the MedicationIntakeEvent unique constraint with a partial unique index `WHERE deleted_at IS NULL`. A slot the user previously deleted (tombstoned) no longer occupies the unique slot, so a re-take re-creates the row cleanly instead of P2002-ing against the tombstone and 500-ing. The migration dedups any pre-existing duplicate live rows first (keep newest, tombstone the rest) so the unique build can't fail on real data. Measurement and Workout deliberately stay full uniques: Measurement's compound-key writes use prisma.upsert, which Prisma 7 compiles to native INSERT ... ON CONFLICT and Postgres cannot arbiter against a partial unique (and consolidate-daily-mean intentionally resurrects a tombstoned canonical row, which needs the full unique); Workout has no deleted_at column. The rationale is documented in the migration header and the schema comments. Migration 0122 adds MoodEntry.externalId plus a NULL-distinct `@@unique([userId, source, externalId])`. The moodLog webhook, the pull sync, and the JSON import now carry the source's stable id into externalId and upsert on the new key when present, so a re-import is idempotent even when moodLoggedAt re-rounds or re-zones. Native/MANUAL entries keep their NULL externalId and stay on the legacy (userId, date, moodLoggedAt) path. Migration 0123 adds AuditLog(userId, action, createdAt desc) and MoodEntry(userId, moodLoggedAt) composite indexes for the status-cache and mood-insights read paths. Also corrects the stale "v1.6.0 drops this column" note on MedicationSchedule.daysOfWeek (the column is still dual-written and read by live paths, so it is retained) and documents the UTC-only timezone storage invariant in a schema header comment. Integration tests cover the intake re-take after delete (no resurrection / no 500) and mood re-import idempotency (with and without an external id) on real Postgres. (cherry picked from commit 9a6723f469bdec063a99cd08df132e07ec3c95a5)
27 lines
1.2 KiB
SQL
27 lines
1.2 KiB
SQL
-- v1.12.1 — composite read-path indexes.
|
|
--
|
|
-- P1-2. `AuditLog(user_id, action, created_at desc)`.
|
|
-- `status-cache.ts` filters `AuditLog` on `{ userId, action }` ordered
|
|
-- by `created_at desc` on every insight-status card mount (7 generators).
|
|
-- The table only had `(user_id, created_at)` and `(action, created_at)`,
|
|
-- so the planner picked one prefix and re-filtered + re-sorted the other
|
|
-- column over the highest-churn table in the app. The three-column index
|
|
-- turns both the `findFirst` and the `take:5` into an index-only range
|
|
-- scan.
|
|
--
|
|
-- P1-3. `MoodEntry(user_id, mood_logged_at)`.
|
|
-- `mood-aggregates.ts` + `mood-status.ts` query `MoodEntry` by
|
|
-- `{ userId, deletedAt: null, moodLoggedAt: { gte } }` ordered by
|
|
-- `mood_logged_at desc`. No existing index leads with
|
|
-- `(user_id, mood_logged_at)`; the planner used the `user_id` prefix then
|
|
-- sorted. Additive, low risk.
|
|
--
|
|
-- Both are pure `CREATE INDEX IF NOT EXISTS` — additive, no column add,
|
|
-- no backfill.
|
|
|
|
CREATE INDEX IF NOT EXISTS "audit_logs_user_id_action_created_at_idx"
|
|
ON "audit_logs" ("user_id", "action", "created_at" DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS "mood_entries_user_id_mood_logged_at_idx"
|
|
ON "mood_entries" ("user_id", "mood_logged_at");
|