Commit Graph
1 Commits
Author SHA1 Message Date
Marc-André Bombeck 0ccc00b774 feat(api): SyncMode foundation — syncVersion column, sync-state endpoint, bulk backfill routes
R-E C-2 server-side. Three additive columns + three new endpoints
let the iOS SyncMode store pair, drain, and reconcile against a
fresh paired-server.

Schema (migration 0062, additive):
  - Measurement.sync_version (Int, default 1) for last-writer-wins
  - Measurement.deleted_at (Timestamp, nullable) for soft-deletes
  - User.last_synced_at (Timestamp, nullable) for the handshake
    checkpoint

Endpoints:
  - GET /api/sync/state — handshake response (lastSyncedAt, server
    clock, live + tombstoned measurement counters); call also bumps
    the checkpoint so iOS reads the OLD value then trusts subsequent
    writes
  - POST /api/mood-entries/bulk — UPSERT per entry keyed by
    (userId, date, moodLoggedAt); probe-then-upsert so the response
    reliably reports inserted vs duplicate; rate-limited at 60/min
  - POST /api/medications/intake/bulk — INSERT per entry with
    P2002-on-idempotencyKey → duplicate; up-front user-owns-medication
    check skips foreign rows with a documented reason

iOS contract: every change is additive. Existing per-sample POSTs
keep round-tripping (sync_version defaults to 1 server-side;
deleted_at stays NULL on every existing read path; last_synced_at is
opt-in). The new endpoints are net-new — no iOS consumer yet.

Tests cover the handshake checkpoint advance, live-vs-tombstoned
counters, clean batch ingest, re-post upsert semantics, ownership
filtering, idempotency-key collision, and over-cap rejection — all
against the real-Postgres container.
2026-05-16 12:04:45 +02:00