From 075cf28a5e43c931efdc53839d0b59d2ddb6c3ea Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Mon, 27 Apr 2026 22:10:51 +0200 Subject: [PATCH] feat(ST-1001): add LoginPairing model + pg_notify trigger via migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Schema (prisma/schema.prisma): - model LoginPairing met id (cuid), secret_hash + desktop_token_hash (beide NOT NULL — scheiden mobiel- en desktop-bewijs), status (pending|approved|consumed |cancelled), optionele user_id met onDelete: SetNull, desktop_ua VarChar(255), desktop_ip VarChar(45) voor IPv6, created_at + expires_at + approved_at + consumed_at, indexes op (expires_at) en (status, expires_at) - back-relation login_pairings LoginPairing[] op User Migratie (20260427200734_add_login_pairing): - Prisma-gegenereerde DDL voor login_pairings + indexes + FK - Toegevoegde notify_pairing_change() functie + login_pairings_notify trigger op AFTER INSERT/UPDATE; emit pg_notify('scrum4me_pairing', payload) met { op: 'I'|'U', pairing_id, status } - DELETE niet ondersteund — pairings gaan naar consumed/cancelled, niet weg - Channel naam analoog aan scrum4me_changes uit ST-801 Verification: Node pg-client roundtrip-test via DATABASE_URL toonde notifies bij INSERT (op=I) en UPDATE (op=U) met correcte payload-shape. Bouwt voort op M8 LISTEN/NOTIFY-infra. SSE-route /api/auth/pair/stream/[id] in ST-1004 abonneert hierop. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../migration.sql | 67 +++++++++++++++++++ prisma/schema.prisma | 20 ++++++ 2 files changed, 87 insertions(+) create mode 100644 prisma/migrations/20260427200734_add_login_pairing/migration.sql diff --git a/prisma/migrations/20260427200734_add_login_pairing/migration.sql b/prisma/migrations/20260427200734_add_login_pairing/migration.sql new file mode 100644 index 0000000..5af26fe --- /dev/null +++ b/prisma/migrations/20260427200734_add_login_pairing/migration.sql @@ -0,0 +1,67 @@ +-- CreateTable +CREATE TABLE "login_pairings" ( + "id" TEXT NOT NULL, + "secret_hash" TEXT NOT NULL, + "desktop_token_hash" TEXT NOT NULL, + "status" TEXT NOT NULL, + "user_id" TEXT, + "desktop_ua" VARCHAR(255), + "desktop_ip" VARCHAR(45), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expires_at" TIMESTAMP(3) NOT NULL, + "approved_at" TIMESTAMP(3), + "consumed_at" TIMESTAMP(3), + + CONSTRAINT "login_pairings_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "login_pairings_expires_at_idx" ON "login_pairings"("expires_at"); + +-- CreateIndex +CREATE INDEX "login_pairings_status_expires_at_idx" ON "login_pairings"("status", "expires_at"); + +-- AddForeignKey +ALTER TABLE "login_pairings" ADD CONSTRAINT "login_pairings_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- ST-1001: Postgres LISTEN/NOTIFY voor QR-pairing flow. +-- +-- AFTER INSERT/UPDATE-trigger op login_pairings emit een JSON-payload op het +-- `scrum4me_pairing`-kanaal. De SSE-route /api/auth/pair/stream/[pairingId] +-- (ST-1004) abonneert op dit kanaal en filtert per pairing_id. +-- +-- DELETE wordt niet ondersteund — pairings gaan naar status='consumed' of +-- 'cancelled', niet weg. Een eventuele cleanup-job die rijen wel deleten zou +-- kan dat zonder dit kanaal te bereiken. +-- +-- Payload shape: +-- { op: 'I' | 'U', +-- pairing_id: text, +-- status: text } +-- +-- Channel-name is hardcoded analoog aan `scrum4me_changes` uit ST-801. Bij +-- wijziging deze migratie én app/api/auth/pair/stream/[pairingId]/route.ts +-- bijwerken. + +CREATE OR REPLACE FUNCTION notify_pairing_change() RETURNS trigger AS $$ +DECLARE + payload jsonb; +BEGIN + payload := jsonb_build_object( + 'op', CASE TG_OP + WHEN 'INSERT' THEN 'I' + WHEN 'UPDATE' THEN 'U' + END, + 'pairing_id', NEW.id, + 'status', NEW.status + ); + + PERFORM pg_notify('scrum4me_pairing', payload::text); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS login_pairings_notify ON login_pairings; +CREATE TRIGGER login_pairings_notify + AFTER INSERT OR UPDATE ON login_pairings + FOR EACH ROW EXECUTE FUNCTION notify_pairing_change(); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7e88b31..920907b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -65,6 +65,7 @@ model User { todos Todo[] product_members ProductMember[] assigned_stories Story[] @relation("StoryAssignee") + login_pairings LoginPairing[] @@index([active_product_id]) @@map("users") @@ -247,3 +248,22 @@ model Todo { @@index([user_id, product_id]) @@map("todos") } + +model LoginPairing { + id String @id @default(cuid()) + secret_hash String + desktop_token_hash String + status String + user_id String? + user User? @relation(fields: [user_id], references: [id], onDelete: SetNull) + desktop_ua String? @db.VarChar(255) + desktop_ip String? @db.VarChar(45) + created_at DateTime @default(now()) + expires_at DateTime + approved_at DateTime? + consumed_at DateTime? + + @@index([expires_at]) + @@index([status, expires_at]) + @@map("login_pairings") +}