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

@@ -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])
}