mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
Lay the data-model foundation for the Fitbit/Pixel integration over the Google Health API, mirroring the WHOOP provider. No OAuth, sync, or UI yet. - Prisma: FitbitConnection (1:1 per user, encrypted access/refresh tokens) and FitbitOAuthState models, two encrypted BYO-credential columns on User, and FITBIT appended to the MeasurementSource enum. Migration 0117_v1120_fitbit_integration is additive only — one enum value plus two tables and two nullable columns, no new MeasurementType (every launch metric maps onto an existing type). - Wire the two new encrypted columns and the FitbitConnection token columns into scripts/rotate-encryption-key.ts. - FITBIT touchpoints: the read measurementSourceEnum (not the writable set), the DEFAULT_SOURCE_PRIORITY and workout-source ladders, the sources-section label map, the IntegrationKey union plus its display name, and a fitbit()/fitbitStatus() query-key pair. The source-rank SQL and read-tier collapse pick FITBIT up from the ladders with no code change. - Add the FITBIT source label to all six locale bundles and regenerate the OpenAPI contract so the source enum carries FITBIT for the iOS decoder.
84 lines
3.7 KiB
SQL
84 lines
3.7 KiB
SQL
-- v1.12.0 — Fitbit/Pixel integration via the Google Health API (schema + data
|
|
-- layer).
|
|
--
|
|
-- Purely-additive: one enum-extension, two new tables, and two new nullable
|
|
-- columns on `users`. No backfill, no existing row touched. Unlike the WHOOP
|
|
-- migration (0111) this adds NO new `measurement_type` values — every Fitbit
|
|
-- launch metric maps onto an existing type.
|
|
--
|
|
-- 1. `measurement_source` += `FITBIT` for the server-side native ingest.
|
|
-- 2. `fitbit_connections` — per-user encrypted token row (1:1 with users),
|
|
-- mirroring `whoop_connections` minus `max_heart_rate`, plus
|
|
-- `fitbit_user_id` and `backfill_completed_at`.
|
|
-- 3. `fitbit_oauth_states` — short-lived `(nonce → userId)` CSRF ledger,
|
|
-- identical in shape to `whoop_oauth_states`.
|
|
-- 4. `users` += `fitbit_client_id_encrypted` / `fitbit_client_secret_encrypted`
|
|
-- — per-user BYO-keys (Google's Testing-mode per-app authorized-user cap
|
|
-- makes a single shared client unworkable for a multi-operator product).
|
|
--
|
|
-- Idempotent guards (`IF NOT EXISTS`) make reruns safe. Forward-only.
|
|
--
|
|
-- Reversibility: Postgres cannot remove an enum value, so `FITBIT` stays; with
|
|
-- no rows carrying it it is inert. The two tables drop with `DROP TABLE IF
|
|
-- EXISTS`, the two columns with `DROP COLUMN IF EXISTS`.
|
|
|
|
-- ── 1. measurement_source — append the Fitbit server-owned source ──────
|
|
ALTER TYPE "measurement_source" ADD VALUE IF NOT EXISTS 'FITBIT';
|
|
|
|
-- ── 2. fitbit_connections ─────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS "fitbit_connections" (
|
|
"id" TEXT NOT NULL,
|
|
"user_id" TEXT NOT NULL,
|
|
"fitbit_user_id" TEXT NOT NULL,
|
|
"access_token" TEXT NOT NULL,
|
|
"refresh_token" TEXT NOT NULL,
|
|
"token_expires_at" TIMESTAMP(3) NOT NULL,
|
|
"scope" TEXT,
|
|
"last_synced_at" TIMESTAMP(3),
|
|
"backfill_completed_at" TIMESTAMP(3),
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "fitbit_connections_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "fitbit_connections_user_id_key"
|
|
ON "fitbit_connections" ("user_id");
|
|
|
|
DO $$ BEGIN
|
|
ALTER TABLE "fitbit_connections"
|
|
ADD CONSTRAINT "fitbit_connections_user_id_fkey"
|
|
FOREIGN KEY ("user_id") REFERENCES "users"("id")
|
|
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
-- ── 3. fitbit_oauth_states ────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS "fitbit_oauth_states" (
|
|
"nonce" TEXT NOT NULL,
|
|
"user_id" TEXT NOT NULL,
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"expires_at" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "fitbit_oauth_states_pkey" PRIMARY KEY ("nonce")
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS "fitbit_oauth_states_expires_at_idx"
|
|
ON "fitbit_oauth_states" ("expires_at");
|
|
|
|
DO $$ BEGIN
|
|
ALTER TABLE "fitbit_oauth_states"
|
|
ADD CONSTRAINT "fitbit_oauth_states_user_id_fkey"
|
|
FOREIGN KEY ("user_id") REFERENCES "users"("id")
|
|
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
-- ── 4. users — per-user Fitbit BYO-key columns (encrypted at app level) ─
|
|
ALTER TABLE "users"
|
|
ADD COLUMN IF NOT EXISTS "fitbit_client_id_encrypted" TEXT;
|
|
ALTER TABLE "users"
|
|
ADD COLUMN IF NOT EXISTS "fitbit_client_secret_encrypted" TEXT;
|