-- Phase 1: Authentication models and user extensions -- AlterTable ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "displayName" TEXT; ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "emailVerifiedAt" TIMESTAMPTZ(3); ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "failedLoginAttempts" INTEGER NOT NULL DEFAULT 0; ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "lockedUntil" TIMESTAMPTZ(3); -- CreateTable CREATE TABLE IF NOT EXISTS "email_verification_tokens" ( "id" TEXT NOT NULL, "userId" TEXT NOT NULL, "tokenHash" TEXT NOT NULL, "expiresAt" TIMESTAMPTZ(3) NOT NULL, "usedAt" TIMESTAMPTZ(3), "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "email_verification_tokens_pkey" PRIMARY KEY ("id") ); CREATE TABLE IF NOT EXISTS "password_reset_tokens" ( "id" TEXT NOT NULL, "userId" TEXT NOT NULL, "tokenHash" TEXT NOT NULL, "expiresAt" TIMESTAMPTZ(3) NOT NULL, "usedAt" TIMESTAMPTZ(3), "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "password_reset_tokens_pkey" PRIMARY KEY ("id") ); CREATE TABLE IF NOT EXISTS "totp_credentials" ( "id" TEXT NOT NULL, "userId" TEXT NOT NULL, "secret" TEXT NOT NULL, "enabledAt" TIMESTAMPTZ(3), "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMPTZ(3) NOT NULL, CONSTRAINT "totp_credentials_pkey" PRIMARY KEY ("id") ); CREATE TABLE IF NOT EXISTS "recovery_codes" ( "id" TEXT NOT NULL, "totpCredentialId" TEXT NOT NULL, "codeHash" TEXT NOT NULL, "usedAt" TIMESTAMPTZ(3), "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "recovery_codes_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE UNIQUE INDEX IF NOT EXISTS "email_verification_tokens_tokenHash_key" ON "email_verification_tokens"("tokenHash"); CREATE INDEX IF NOT EXISTS "email_verification_tokens_userId_idx" ON "email_verification_tokens"("userId"); CREATE INDEX IF NOT EXISTS "email_verification_tokens_expiresAt_idx" ON "email_verification_tokens"("expiresAt"); CREATE UNIQUE INDEX IF NOT EXISTS "password_reset_tokens_tokenHash_key" ON "password_reset_tokens"("tokenHash"); CREATE INDEX IF NOT EXISTS "password_reset_tokens_userId_idx" ON "password_reset_tokens"("userId"); CREATE INDEX IF NOT EXISTS "password_reset_tokens_expiresAt_idx" ON "password_reset_tokens"("expiresAt"); CREATE UNIQUE INDEX IF NOT EXISTS "totp_credentials_userId_key" ON "totp_credentials"("userId"); CREATE INDEX IF NOT EXISTS "recovery_codes_totpCredentialId_idx" ON "recovery_codes"("totpCredentialId"); -- AddForeignKey DO $$ BEGIN ALTER TABLE "email_verification_tokens" ADD CONSTRAINT "email_verification_tokens_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN ALTER TABLE "totp_credentials" ADD CONSTRAINT "totp_credentials_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN ALTER TABLE "recovery_codes" ADD CONSTRAINT "recovery_codes_totpCredentialId_fkey" FOREIGN KEY ("totpCredentialId") REFERENCES "totp_credentials"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$; -- Seed platform roles INSERT INTO "platform_roles" ("id", "name", "description", "createdAt", "updatedAt") VALUES (gen_random_uuid()::text, 'USER', 'Standard platform user', NOW(), NOW()), (gen_random_uuid()::text, 'SUPPORT', 'Support staff', NOW(), NOW()), (gen_random_uuid()::text, 'MODERATOR', 'Content moderator', NOW(), NOW()), (gen_random_uuid()::text, 'ADMIN', 'Platform administrator', NOW(), NOW()), (gen_random_uuid()::text, 'SUPER_ADMIN', 'Super administrator', NOW(), NOW()) ON CONFLICT ("name") DO NOTHING;