From 37a88d7e3d2e932844d978031878c5b28a1f444e Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sun, 17 May 2026 13:56:02 +0000 Subject: [PATCH] fix(admin): migration Subscription/BYOK et repli getUsers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La table Subscription était dans le schéma sans migration SQL, ce qui cassait le rendu Server Components de /admin. Migration idempotente + fallback getUsers si la jointure échoue encore. Co-authored-by: Cursor --- memento-note/app/actions/admin.ts | 36 ++++-- .../migration.sql | 119 ++++++++++++++++++ 2 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 memento-note/prisma/migrations/20260517140000_add_subscription_byok_usage_tables/migration.sql diff --git a/memento-note/app/actions/admin.ts b/memento-note/app/actions/admin.ts index 7ad6268..ea1a170 100644 --- a/memento-note/app/actions/admin.ts +++ b/memento-note/app/actions/admin.ts @@ -23,9 +23,31 @@ async function checkAdmin() { return session } +const userListSelect = { + id: true, + name: true, + email: true, + role: true, + createdAt: true, + subscription: { + select: { + tier: true, + status: true, + currentPeriodEnd: true, + }, + }, +} as const + export async function getUsers() { await checkAdmin() try { + return await prisma.user.findMany({ + orderBy: { createdAt: 'desc' }, + select: userListSelect, + }) + } catch (error) { + // Prod DB parfois en retard sur les migrations (ex. table Subscription absente). + console.error('getUsers with subscription failed, fallback without:', error) const users = await prisma.user.findMany({ orderBy: { createdAt: 'desc' }, select: { @@ -34,19 +56,9 @@ export async function getUsers() { email: true, role: true, createdAt: true, - subscription: { - select: { - tier: true, - status: true, - currentPeriodEnd: true, - } - } - } + }, }) - return users - } catch (error) { - console.error('Failed to fetch users:', error) - throw new Error('Failed to fetch users') + return users.map((u) => ({ ...u, subscription: null })) } } diff --git a/memento-note/prisma/migrations/20260517140000_add_subscription_byok_usage_tables/migration.sql b/memento-note/prisma/migrations/20260517140000_add_subscription_byok_usage_tables/migration.sql new file mode 100644 index 0000000..b92c7f4 --- /dev/null +++ b/memento-note/prisma/migrations/20260517140000_add_subscription_byok_usage_tables/migration.sql @@ -0,0 +1,119 @@ +-- Tables présentes dans schema.prisma mais jamais migrées (admin / billing / BYOK). + +DO $$ BEGIN + CREATE TYPE "SubscriptionTier" AS ENUM ('BASIC', 'PRO', 'BUSINESS', 'ENTERPRISE'); +EXCEPTION + WHEN duplicate_object THEN NULL; +END $$; + +DO $$ BEGIN + CREATE TYPE "SubscriptionStatus" AS ENUM ('ACTIVE', 'PAST_DUE', 'CANCELED', 'TRIALING', 'INACTIVE'); +EXCEPTION + WHEN duplicate_object THEN NULL; +END $$; + +CREATE TABLE IF NOT EXISTS "UserAPIKey" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "provider" TEXT NOT NULL, + "alias" TEXT NOT NULL DEFAULT '', + "encryptedKey" TEXT NOT NULL, + "keyHash" TEXT NOT NULL, + "model" TEXT, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "lastUsedAt" TIMESTAMP(3), + "lastUsedFor" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "UserAPIKey_pkey" PRIMARY KEY ("id") +); + +CREATE UNIQUE INDEX IF NOT EXISTS "UserAPIKey_userId_provider_key" ON "UserAPIKey"("userId", "provider"); +CREATE INDEX IF NOT EXISTS "UserAPIKey_userId_idx" ON "UserAPIKey"("userId"); +CREATE INDEX IF NOT EXISTS "UserAPIKey_keyHash_idx" ON "UserAPIKey"("keyHash"); + +DO $$ BEGIN + ALTER TABLE "UserAPIKey" ADD CONSTRAINT "UserAPIKey_userId_fkey" + FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; +EXCEPTION + WHEN duplicate_object THEN NULL; +END $$; + +CREATE TABLE IF NOT EXISTS "Subscription" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "tier" "SubscriptionTier" NOT NULL DEFAULT 'BASIC', + "status" "SubscriptionStatus" NOT NULL DEFAULT 'ACTIVE', + "stripeCustomerId" TEXT, + "stripeSubscriptionId" TEXT, + "stripePriceId" TEXT, + "trialEndsAt" TIMESTAMP(3), + "currentPeriodStart" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "currentPeriodEnd" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "canceledAt" TIMESTAMP(3), + "cancelAtPeriodEnd" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Subscription_pkey" PRIMARY KEY ("id") +); + +CREATE UNIQUE INDEX IF NOT EXISTS "Subscription_userId_key" ON "Subscription"("userId"); +CREATE UNIQUE INDEX IF NOT EXISTS "Subscription_stripeCustomerId_key" ON "Subscription"("stripeCustomerId"); +CREATE UNIQUE INDEX IF NOT EXISTS "Subscription_stripeSubscriptionId_key" ON "Subscription"("stripeSubscriptionId"); + +DO $$ BEGIN + ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_userId_fkey" + FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; +EXCEPTION + WHEN duplicate_object THEN NULL; +END $$; + +CREATE TABLE IF NOT EXISTS "UsageLog" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "feature" TEXT NOT NULL, + "periodStart" TIMESTAMP(3) NOT NULL, + "periodEnd" TIMESTAMP(3) NOT NULL, + "requestsCount" INTEGER NOT NULL DEFAULT 0, + "tokensUsed" INTEGER NOT NULL DEFAULT 0, + "syncedAt" TIMESTAMP(3), + "metadata" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "UsageLog_pkey" PRIMARY KEY ("id") +); + +CREATE UNIQUE INDEX IF NOT EXISTS "UsageLog_userId_feature_periodStart_key" ON "UsageLog"("userId", "feature", "periodStart"); +CREATE INDEX IF NOT EXISTS "UsageLog_userId_periodStart_idx" ON "UsageLog"("userId", "periodStart"); +CREATE INDEX IF NOT EXISTS "UsageLog_periodStart_idx" ON "UsageLog"("periodStart"); + +DO $$ BEGIN + ALTER TABLE "UsageLog" ADD CONSTRAINT "UsageLog_userId_fkey" + FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; +EXCEPTION + WHEN duplicate_object THEN NULL; +END $$; + +CREATE TABLE IF NOT EXISTS "FeatureFlag" ( + "id" TEXT NOT NULL, + "key" TEXT NOT NULL, + "enabled" BOOLEAN NOT NULL DEFAULT false, + "tiers" TEXT[], + "metadata" TEXT, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "FeatureFlag_pkey" PRIMARY KEY ("id") +); + +CREATE UNIQUE INDEX IF NOT EXISTS "FeatureFlag_key_key" ON "FeatureFlag"("key"); + +DO $$ BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'User' AND column_name = 'accentColor' + ) THEN + ALTER TABLE "User" ADD COLUMN "accentColor" TEXT NOT NULL DEFAULT '#A47148'; + END IF; +END $$;