Initial import

This commit is contained in:
sirily
2026-03-10 14:03:52 +03:00
commit 6c0ca4e28b
102 changed files with 6598 additions and 0 deletions

View File

View File

@@ -0,0 +1,366 @@
-- CreateSchema
CREATE SCHEMA IF NOT EXISTS "public";
-- CreateEnum
CREATE TYPE "SubscriptionStatus" AS ENUM ('pending_activation', 'active', 'past_due', 'canceled', 'expired');
-- CreateEnum
CREATE TYPE "PaymentInvoiceStatus" AS ENUM ('pending', 'paid', 'expired', 'canceled');
-- CreateEnum
CREATE TYPE "GenerationMode" AS ENUM ('text_to_image', 'image_to_image');
-- CreateEnum
CREATE TYPE "GenerationRequestStatus" AS ENUM ('queued', 'running', 'succeeded', 'failed', 'canceled');
-- CreateEnum
CREATE TYPE "GenerationAttemptStatus" AS ENUM ('started', 'succeeded', 'failed');
-- CreateEnum
CREATE TYPE "ProviderFailureCategory" AS ENUM ('transport', 'timeout', 'provider_5xx', 'provider_4xx_user', 'insufficient_funds', 'unknown');
-- CreateEnum
CREATE TYPE "ProviderKeyState" AS ENUM ('active', 'cooldown', 'out_of_funds', 'manual_review', 'disabled');
-- CreateEnum
CREATE TYPE "UsageLedgerEntryType" AS ENUM ('cycle_reset', 'generation_success', 'manual_adjustment', 'refund');
-- CreateEnum
CREATE TYPE "TelegramPairingStatus" AS ENUM ('pending', 'completed', 'expired', 'revoked');
-- CreateEnum
CREATE TYPE "AdminActorType" AS ENUM ('system', 'web_admin', 'telegram_admin', 'cli_operator');
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"passwordResetVersion" INTEGER NOT NULL DEFAULT 0,
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SubscriptionPlan" (
"id" TEXT NOT NULL,
"code" TEXT NOT NULL,
"displayName" TEXT NOT NULL,
"monthlyRequestLimit" INTEGER NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "SubscriptionPlan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Subscription" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"planId" TEXT NOT NULL,
"status" "SubscriptionStatus" NOT NULL,
"renewsManually" BOOLEAN NOT NULL DEFAULT true,
"activatedAt" TIMESTAMP(3),
"currentPeriodStart" TIMESTAMP(3),
"currentPeriodEnd" TIMESTAMP(3),
"canceledAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Subscription_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PaymentInvoice" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"subscriptionId" TEXT,
"provider" TEXT NOT NULL,
"providerInvoiceId" TEXT,
"status" "PaymentInvoiceStatus" NOT NULL,
"currency" TEXT NOT NULL,
"amountCrypto" DECIMAL(20,8) NOT NULL,
"amountUsd" DECIMAL(12,2),
"paymentAddress" TEXT,
"expiresAt" TIMESTAMP(3),
"paidAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "PaymentInvoice_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GenerationRequest" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"mode" "GenerationMode" NOT NULL,
"status" "GenerationRequestStatus" NOT NULL DEFAULT 'queued',
"providerModel" TEXT NOT NULL,
"prompt" TEXT NOT NULL,
"sourceImageKey" TEXT,
"resolutionPreset" TEXT NOT NULL,
"batchSize" INTEGER NOT NULL,
"imageStrength" DECIMAL(4,3),
"idempotencyKey" TEXT,
"terminalErrorCode" TEXT,
"terminalErrorText" TEXT,
"requestedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"startedAt" TIMESTAMP(3),
"completedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "GenerationRequest_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GenerationAttempt" (
"id" TEXT NOT NULL,
"generationRequestId" TEXT NOT NULL,
"providerKeyId" TEXT NOT NULL,
"attemptIndex" INTEGER NOT NULL,
"status" "GenerationAttemptStatus" NOT NULL DEFAULT 'started',
"usedProxy" BOOLEAN NOT NULL DEFAULT false,
"directFallbackUsed" BOOLEAN NOT NULL DEFAULT false,
"failureCategory" "ProviderFailureCategory",
"providerHttpStatus" INTEGER,
"providerErrorCode" TEXT,
"providerErrorText" TEXT,
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"completedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GenerationAttempt_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GeneratedAsset" (
"id" TEXT NOT NULL,
"generationRequestId" TEXT NOT NULL,
"objectKey" TEXT NOT NULL,
"mimeType" TEXT NOT NULL,
"width" INTEGER,
"height" INTEGER,
"bytes" INTEGER,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GeneratedAsset_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "UsageLedgerEntry" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"generationRequestId" TEXT,
"entryType" "UsageLedgerEntryType" NOT NULL,
"deltaRequests" INTEGER NOT NULL,
"cycleStartedAt" TIMESTAMP(3),
"cycleEndsAt" TIMESTAMP(3),
"note" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "UsageLedgerEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProviderProxy" (
"id" TEXT NOT NULL,
"label" TEXT NOT NULL,
"baseUrl" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProviderProxy_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProviderKey" (
"id" TEXT NOT NULL,
"providerCode" TEXT NOT NULL,
"label" TEXT NOT NULL,
"apiKeyCiphertext" TEXT NOT NULL,
"apiKeyLastFour" TEXT NOT NULL,
"state" "ProviderKeyState" NOT NULL DEFAULT 'active',
"roundRobinOrder" INTEGER NOT NULL,
"consecutiveRetryableFailures" INTEGER NOT NULL DEFAULT 0,
"cooldownUntil" TIMESTAMP(3),
"lastErrorCategory" "ProviderFailureCategory",
"lastErrorCode" TEXT,
"lastErrorAt" TIMESTAMP(3),
"balanceMinorUnits" BIGINT,
"balanceCurrency" TEXT,
"balanceRefreshedAt" TIMESTAMP(3),
"proxyId" TEXT,
"disabledAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProviderKey_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProviderKeyStatusEvent" (
"id" TEXT NOT NULL,
"providerKeyId" TEXT NOT NULL,
"fromState" "ProviderKeyState",
"toState" "ProviderKeyState" NOT NULL,
"reason" TEXT NOT NULL,
"errorCategory" "ProviderFailureCategory",
"errorCode" TEXT,
"actorType" "AdminActorType" NOT NULL,
"actorRef" TEXT,
"metadata" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ProviderKeyStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TelegramPairing" (
"id" TEXT NOT NULL,
"telegramUserId" TEXT NOT NULL,
"telegramUsername" TEXT,
"displayNameSnapshot" TEXT NOT NULL,
"codeHash" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"status" "TelegramPairingStatus" NOT NULL DEFAULT 'pending',
"completedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TelegramPairing_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TelegramAdminAllowlistEntry" (
"telegramUserId" TEXT NOT NULL,
"telegramUsername" TEXT,
"displayNameSnapshot" TEXT NOT NULL,
"pairedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"revokedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TelegramAdminAllowlistEntry_pkey" PRIMARY KEY ("telegramUserId")
);
-- CreateTable
CREATE TABLE "AdminAuditLog" (
"id" TEXT NOT NULL,
"actorType" "AdminActorType" NOT NULL,
"actorRef" TEXT,
"action" TEXT NOT NULL,
"targetType" TEXT NOT NULL,
"targetId" TEXT,
"metadata" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AdminAuditLog_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "SubscriptionPlan_code_key" ON "SubscriptionPlan"("code");
-- CreateIndex
CREATE INDEX "Subscription_userId_status_idx" ON "Subscription"("userId", "status");
-- CreateIndex
CREATE UNIQUE INDEX "PaymentInvoice_providerInvoiceId_key" ON "PaymentInvoice"("providerInvoiceId");
-- CreateIndex
CREATE INDEX "PaymentInvoice_userId_status_idx" ON "PaymentInvoice"("userId", "status");
-- CreateIndex
CREATE UNIQUE INDEX "GenerationRequest_idempotencyKey_key" ON "GenerationRequest"("idempotencyKey");
-- CreateIndex
CREATE INDEX "GenerationRequest_userId_status_requestedAt_idx" ON "GenerationRequest"("userId", "status", "requestedAt");
-- CreateIndex
CREATE INDEX "GenerationAttempt_providerKeyId_startedAt_idx" ON "GenerationAttempt"("providerKeyId", "startedAt");
-- CreateIndex
CREATE UNIQUE INDEX "GenerationAttempt_generationRequestId_attemptIndex_key" ON "GenerationAttempt"("generationRequestId", "attemptIndex");
-- CreateIndex
CREATE UNIQUE INDEX "GeneratedAsset_objectKey_key" ON "GeneratedAsset"("objectKey");
-- CreateIndex
CREATE INDEX "GeneratedAsset_generationRequestId_idx" ON "GeneratedAsset"("generationRequestId");
-- CreateIndex
CREATE UNIQUE INDEX "UsageLedgerEntry_generationRequestId_key" ON "UsageLedgerEntry"("generationRequestId");
-- CreateIndex
CREATE INDEX "UsageLedgerEntry_userId_createdAt_idx" ON "UsageLedgerEntry"("userId", "createdAt");
-- CreateIndex
CREATE UNIQUE INDEX "ProviderProxy_label_key" ON "ProviderProxy"("label");
-- CreateIndex
CREATE UNIQUE INDEX "ProviderKey_label_key" ON "ProviderKey"("label");
-- CreateIndex
CREATE INDEX "ProviderKey_providerCode_state_roundRobinOrder_idx" ON "ProviderKey"("providerCode", "state", "roundRobinOrder");
-- CreateIndex
CREATE INDEX "ProviderKeyStatusEvent_providerKeyId_createdAt_idx" ON "ProviderKeyStatusEvent"("providerKeyId", "createdAt");
-- CreateIndex
CREATE INDEX "TelegramPairing_telegramUserId_status_idx" ON "TelegramPairing"("telegramUserId", "status");
-- CreateIndex
CREATE INDEX "TelegramPairing_expiresAt_status_idx" ON "TelegramPairing"("expiresAt", "status");
-- CreateIndex
CREATE INDEX "AdminAuditLog_targetType_targetId_createdAt_idx" ON "AdminAuditLog"("targetType", "targetId", "createdAt");
-- CreateIndex
CREATE INDEX "AdminAuditLog_actorType_createdAt_idx" ON "AdminAuditLog"("actorType", "createdAt");
-- AddForeignKey
ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_planId_fkey" FOREIGN KEY ("planId") REFERENCES "SubscriptionPlan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PaymentInvoice" ADD CONSTRAINT "PaymentInvoice_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PaymentInvoice" ADD CONSTRAINT "PaymentInvoice_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "Subscription"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GenerationRequest" ADD CONSTRAINT "GenerationRequest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GenerationAttempt" ADD CONSTRAINT "GenerationAttempt_generationRequestId_fkey" FOREIGN KEY ("generationRequestId") REFERENCES "GenerationRequest"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GenerationAttempt" ADD CONSTRAINT "GenerationAttempt_providerKeyId_fkey" FOREIGN KEY ("providerKeyId") REFERENCES "ProviderKey"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GeneratedAsset" ADD CONSTRAINT "GeneratedAsset_generationRequestId_fkey" FOREIGN KEY ("generationRequestId") REFERENCES "GenerationRequest"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UsageLedgerEntry" ADD CONSTRAINT "UsageLedgerEntry_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UsageLedgerEntry" ADD CONSTRAINT "UsageLedgerEntry_generationRequestId_fkey" FOREIGN KEY ("generationRequestId") REFERENCES "GenerationRequest"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProviderKey" ADD CONSTRAINT "ProviderKey_proxyId_fkey" FOREIGN KEY ("proxyId") REFERENCES "ProviderProxy"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProviderKeyStatusEvent" ADD CONSTRAINT "ProviderKeyStatusEvent_providerKeyId_fkey" FOREIGN KEY ("providerKeyId") REFERENCES "ProviderKey"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,20 @@
CREATE TABLE "UserSession" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"tokenHash" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"revokedAt" TIMESTAMP(3),
"lastSeenAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "UserSession_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "UserSession_tokenHash_key" ON "UserSession"("tokenHash");
CREATE INDEX "UserSession_userId_createdAt_idx" ON "UserSession"("userId", "createdAt");
CREATE INDEX "UserSession_expiresAt_revokedAt_idx" ON "UserSession"("expiresAt", "revokedAt");
ALTER TABLE "UserSession"
ADD CONSTRAINT "UserSession_userId_fkey"
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,18 @@
CREATE TABLE "PasswordResetToken" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"tokenHash" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"consumedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PasswordResetToken_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "PasswordResetToken_tokenHash_key" ON "PasswordResetToken"("tokenHash");
CREATE INDEX "PasswordResetToken_userId_createdAt_idx" ON "PasswordResetToken"("userId", "createdAt");
CREATE INDEX "PasswordResetToken_expiresAt_consumedAt_idx" ON "PasswordResetToken"("expiresAt", "consumedAt");
ALTER TABLE "PasswordResetToken"
ADD CONSTRAINT "PasswordResetToken_userId_fkey"
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
ALTER TABLE "SubscriptionPlan"
ADD COLUMN "monthlyPriceUsd" DECIMAL(12,2) NOT NULL DEFAULT 9.99,
ADD COLUMN "billingCurrency" TEXT NOT NULL DEFAULT 'USDT';

View File

@@ -0,0 +1,2 @@
# Do not edit by hand unless you are intentionally resetting migration history.
provider = "postgresql"

View File

@@ -0,0 +1,351 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum SubscriptionStatus {
pending_activation
active
past_due
canceled
expired
}
enum PaymentInvoiceStatus {
pending
paid
expired
canceled
}
enum GenerationMode {
text_to_image
image_to_image
}
enum GenerationRequestStatus {
queued
running
succeeded
failed
canceled
}
enum GenerationAttemptStatus {
started
succeeded
failed
}
enum ProviderFailureCategory {
transport
timeout
provider_5xx
provider_4xx_user
insufficient_funds
unknown
}
enum ProviderKeyState {
active
cooldown
out_of_funds
manual_review
disabled
}
enum UsageLedgerEntryType {
cycle_reset
generation_success
manual_adjustment
refund
}
enum TelegramPairingStatus {
pending
completed
expired
revoked
}
enum AdminActorType {
system
web_admin
telegram_admin
cli_operator
}
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
passwordResetVersion Int @default(0)
isAdmin Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subscriptions Subscription[]
invoices PaymentInvoice[]
generationRequests GenerationRequest[]
usageLedgerEntries UsageLedgerEntry[]
sessions UserSession[]
passwordResetTokens PasswordResetToken[]
}
model UserSession {
id String @id @default(cuid())
userId String
tokenHash String @unique
expiresAt DateTime
revokedAt DateTime?
lastSeenAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, createdAt])
@@index([expiresAt, revokedAt])
}
model PasswordResetToken {
id String @id @default(cuid())
userId String
tokenHash String @unique
expiresAt DateTime
consumedAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, createdAt])
@@index([expiresAt, consumedAt])
}
model SubscriptionPlan {
id String @id @default(cuid())
code String @unique
displayName String
monthlyRequestLimit Int
monthlyPriceUsd Decimal @db.Decimal(12, 2)
billingCurrency String
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subscriptions Subscription[]
}
model Subscription {
id String @id @default(cuid())
userId String
planId String
status SubscriptionStatus
renewsManually Boolean @default(true)
activatedAt DateTime?
currentPeriodStart DateTime?
currentPeriodEnd DateTime?
canceledAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
plan SubscriptionPlan @relation(fields: [planId], references: [id], onDelete: Restrict)
invoices PaymentInvoice[]
@@index([userId, status])
}
model PaymentInvoice {
id String @id @default(cuid())
userId String
subscriptionId String?
provider String
providerInvoiceId String? @unique
status PaymentInvoiceStatus
currency String
amountCrypto Decimal @db.Decimal(20, 8)
amountUsd Decimal? @db.Decimal(12, 2)
paymentAddress String?
expiresAt DateTime?
paidAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
subscription Subscription? @relation(fields: [subscriptionId], references: [id], onDelete: SetNull)
@@index([userId, status])
}
model GenerationRequest {
id String @id @default(cuid())
userId String
mode GenerationMode
status GenerationRequestStatus @default(queued)
providerModel String
prompt String
sourceImageKey String?
resolutionPreset String
batchSize Int
imageStrength Decimal? @db.Decimal(4, 3)
idempotencyKey String? @unique
terminalErrorCode String?
terminalErrorText String?
requestedAt DateTime @default(now())
startedAt DateTime?
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
attempts GenerationAttempt[]
assets GeneratedAsset[]
usageLedgerEntry UsageLedgerEntry?
@@index([userId, status, requestedAt])
}
model GenerationAttempt {
id String @id @default(cuid())
generationRequestId String
providerKeyId String
attemptIndex Int
status GenerationAttemptStatus @default(started)
usedProxy Boolean @default(false)
directFallbackUsed Boolean @default(false)
failureCategory ProviderFailureCategory?
providerHttpStatus Int?
providerErrorCode String?
providerErrorText String?
startedAt DateTime @default(now())
completedAt DateTime?
createdAt DateTime @default(now())
generationRequest GenerationRequest @relation(fields: [generationRequestId], references: [id], onDelete: Cascade)
providerKey ProviderKey @relation(fields: [providerKeyId], references: [id], onDelete: Restrict)
@@unique([generationRequestId, attemptIndex])
@@index([providerKeyId, startedAt])
}
model GeneratedAsset {
id String @id @default(cuid())
generationRequestId String
objectKey String @unique
mimeType String
width Int?
height Int?
bytes Int?
createdAt DateTime @default(now())
generationRequest GenerationRequest @relation(fields: [generationRequestId], references: [id], onDelete: Cascade)
@@index([generationRequestId])
}
model UsageLedgerEntry {
id String @id @default(cuid())
userId String
generationRequestId String? @unique
entryType UsageLedgerEntryType
deltaRequests Int
cycleStartedAt DateTime?
cycleEndsAt DateTime?
note String?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
generationRequest GenerationRequest? @relation(fields: [generationRequestId], references: [id], onDelete: SetNull)
@@index([userId, createdAt])
}
model ProviderProxy {
id String @id @default(cuid())
label String @unique
baseUrl String
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
providerKeys ProviderKey[]
}
model ProviderKey {
id String @id @default(cuid())
providerCode String
label String @unique
apiKeyCiphertext String
apiKeyLastFour String
state ProviderKeyState @default(active)
roundRobinOrder Int
consecutiveRetryableFailures Int @default(0)
cooldownUntil DateTime?
lastErrorCategory ProviderFailureCategory?
lastErrorCode String?
lastErrorAt DateTime?
balanceMinorUnits BigInt?
balanceCurrency String?
balanceRefreshedAt DateTime?
proxyId String?
disabledAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
proxy ProviderProxy? @relation(fields: [proxyId], references: [id], onDelete: SetNull)
attempts GenerationAttempt[]
statusEvents ProviderKeyStatusEvent[]
@@index([providerCode, state, roundRobinOrder])
}
model ProviderKeyStatusEvent {
id String @id @default(cuid())
providerKeyId String
fromState ProviderKeyState?
toState ProviderKeyState
reason String
errorCategory ProviderFailureCategory?
errorCode String?
actorType AdminActorType
actorRef String?
metadata Json?
createdAt DateTime @default(now())
providerKey ProviderKey @relation(fields: [providerKeyId], references: [id], onDelete: Cascade)
@@index([providerKeyId, createdAt])
}
model TelegramPairing {
id String @id @default(cuid())
telegramUserId String
telegramUsername String?
displayNameSnapshot String
codeHash String
expiresAt DateTime
status TelegramPairingStatus @default(pending)
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([telegramUserId, status])
@@index([expiresAt, status])
}
model TelegramAdminAllowlistEntry {
telegramUserId String @id
telegramUsername String?
displayNameSnapshot String
pairedAt DateTime @default(now())
revokedAt DateTime?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model AdminAuditLog {
id String @id @default(cuid())
actorType AdminActorType
actorRef String?
action String
targetType String
targetId String?
metadata Json?
createdAt DateTime @default(now())
@@index([targetType, targetId, createdAt])
@@index([actorType, createdAt])
}