69 lines
4.1 KiB
SQL
69 lines
4.1 KiB
SQL
-- 001_legacy_additive.sql — additive upgrades historically shipped as
|
|
-- db/upgrade.sql, adopted into the ordered ledger (review MIG-01).
|
|
-- Idempotent: every statement is guarded by an existence check, so first
|
|
-- ledger adoption on an already-upgraded database is a no-op. From ledger
|
|
-- adoption onward recorded migrations are immutable; new changes go into
|
|
-- new numbered files. The runner (db/migrate.sh) applies each file in its
|
|
-- own transaction with ON_ERROR_STOP.
|
|
|
|
-- Review MIG-02: report orphaned bookmark->contact references with an
|
|
-- actionable error before adding the foreign key, instead of failing with a
|
|
-- bare constraint violation. Never erase relationships silently — repair is
|
|
-- a deliberate operator decision.
|
|
DO $$
|
|
DECLARE orphans bigint;
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='Bookmark_contactId_fkey' AND conrelid='"Bookmark"'::regclass) THEN
|
|
SELECT count(*) INTO orphans FROM "Bookmark" b
|
|
WHERE b."contactId" IS NOT NULL
|
|
AND NOT EXISTS (SELECT 1 FROM "Contact" c WHERE c.id=b."contactId");
|
|
IF orphans > 0 THEN
|
|
RAISE EXCEPTION 'migration 001: % bookmark(s) reference missing Contact rows; repair them deliberately (e.g. UPDATE "Bookmark" SET "contactId"=NULL WHERE "contactId" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM "Contact" c WHERE c.id="Bookmark"."contactId")) and re-run', orphans;
|
|
END IF;
|
|
ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_contactId_fkey" FOREIGN KEY ("contactId") REFERENCES "Contact"(id) ON DELETE SET NULL;
|
|
END IF;
|
|
END $$;
|
|
-- Bring pre-port mailbox switches into the background signal scheduler.
|
|
-- Existing sources (including paused watches) and modern watchers are preserved.
|
|
INSERT INTO "SignalSource"(id,type,config,enabled,"bookmarkId","ownerId")
|
|
SELECT 'legacy-'||b.id,'email',json_build_object('subject',COALESCE(b."imapQuery",''),'legacy',true),true,b.id,b."ownerId"
|
|
FROM "Bookmark" b
|
|
WHERE b."imapWatchEnabled" AND NOT EXISTS (
|
|
SELECT 1 FROM "SignalSource" s WHERE s."bookmarkId"=b.id AND s."ownerId"=b."ownerId" AND s.config->>'legacy'='true'
|
|
)
|
|
ON CONFLICT(id) DO NOTHING;
|
|
CREATE INDEX IF NOT EXISTS "Bookmark_ownerId_updatedAt_id_idx" ON "Bookmark"("ownerId","updatedAt" DESC,id DESC);
|
|
-- Revocable sessions: cookie JWTs embed this version; signout bumps it.
|
|
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "tokenVersion" integer NOT NULL DEFAULT 1;
|
|
-- Query-pattern indexes: shared-date visibility join and the alerts poll.
|
|
CREATE INDEX IF NOT EXISTS "SharedDate_creatorId_recipientId_date_idx" ON "SharedDate"("creatorId","recipientId",date);
|
|
CREATE INDEX IF NOT EXISTS "Bookmark_ownerId_alertAt_unsent_idx" ON "Bookmark"("ownerId","alertAt") WHERE NOT "alertSent";
|
|
-- Prisma's Json maps to jsonb; enables GIN indexing and dedups keys.
|
|
-- Re-running on an already-jsonb column is a validated no-op.
|
|
ALTER TABLE "SignalSource" ALTER COLUMN config TYPE jsonb USING config::jsonb;
|
|
-- Optimistic concurrency for page saves (review DATA-01): content saves bump
|
|
-- this revision and a stale base version in PATCH is rejected with 409.
|
|
ALTER TABLE "Page" ADD COLUMN IF NOT EXISTS version integer NOT NULL DEFAULT 1;
|
|
-- Durable per-source mailbox ingestion checkpoints (review SYNC-02): polls
|
|
-- process only UIDs newer than lastUid and advance only after durable inserts.
|
|
CREATE TABLE IF NOT EXISTS "SignalCheckpoint" (
|
|
"sourceId" text PRIMARY KEY REFERENCES "SignalSource"(id) ON DELETE CASCADE,
|
|
"uidValidity" bigint NOT NULL,
|
|
"lastUid" bigint NOT NULL,
|
|
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
-- One-shot administrator bootstrap state (review AUTH-06): once recorded,
|
|
-- signup never auto-promotes again; recovery is operator-driven.
|
|
CREATE TABLE IF NOT EXISTS "AppSetting" (
|
|
key text PRIMARY KEY,
|
|
value jsonb NOT NULL DEFAULT '{}',
|
|
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
-- Installations that already have a qualifying administrator completed
|
|
-- bootstrap before this ledger existed; record it so demoting that
|
|
-- administrator cannot re-open first-signup promotion.
|
|
INSERT INTO "AppSetting"(key,value)
|
|
SELECT 'adminBootstrap',jsonb_build_object('completed',true)
|
|
WHERE EXISTS (SELECT 1 FROM "User" WHERE role='ADMIN' AND email NOT LIKE '%@example.com')
|
|
ON CONFLICT(key) DO NOTHING;
|