352 lines
11 KiB
Plaintext
352 lines
11 KiB
Plaintext
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])
|
|
}
|