generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } enum UserStatus { ACTIVE SUSPENDED DELETED } enum ServerEdition { JAVA BEDROCK } enum SoftwareFamily { VANILLA PAPER PURPUR FABRIC FORGE NEOFORGE QUILT BEDROCK_DEDICATED } enum GameServerStatus { DRAFT PROVISIONING INSTALLING STOPPED QUEUED STARTING RUNNING STOPPING BACKING_UP RESTORING UNKNOWN ERROR DELETING DELETED } enum GameNodeStatus { ONLINE OFFLINE MAINTENANCE DRAINING UNREACHABLE } enum AllocationStatus { ACTIVE RELEASED } enum StartQueueStatus { QUEUED DISPATCHED CANCELLED EXPIRED } enum JobStatus { PENDING RUNNING COMPLETED FAILED CANCELLED } enum BackupStatus { PENDING RUNNING AVAILABLE FAILED DELETED } enum BackupType { MANUAL SCHEDULED PRE_RESTORE PRE_WORLD_REPLACE } enum BackupRestoreStatus { PENDING RUNNING COMPLETED FAILED } enum WorldUploadStatus { PENDING UPLOADING VALIDATING READY COMPLETED FAILED } enum AddonType { PLUGIN MOD MODPACK DATAPACK } enum AddonInstallStatus { PENDING INSTALLING INSTALLED FAILED REMOVING REMOVED } enum CreditTransactionType { GRANT USAGE_DEBIT ADJUSTMENT EXPIRE } enum DnsRecordStatus { PENDING ACTIVE FAILED REMOVED } enum WhmcsServiceStatus { PENDING ACTIVE SUSPENDED TERMINATED } enum IntegrationOperationStatus { COMPLETED FAILED } enum SsoTicketKind { SERVICE ADMIN } enum IntegrationEventStatus { PENDING ACKNOWLEDGED DEAD_LETTER } enum ReconciliationSeverity { INFO WARNING BLOCKING DESTRUCTIVE SECURITY } enum UsageExportStatus { DRAFT EXPORTED ADJUSTED } model User { id String @id @default(uuid()) email String @unique username String @unique displayName String? status UserStatus @default(ACTIVE) emailVerifiedAt DateTime? @db.Timestamptz(3) failedLoginAttempts Int @default(0) lockedUntil DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) credentials UserCredential[] sessions UserSession[] platformRoles UserPlatformRole[] gameServers GameServer[] auditEvents AuditEvent[] emailVerificationTokens EmailVerificationToken[] passwordResetTokens PasswordResetToken[] totpCredential TotpCredential? creditWallet CreditWallet? whmcsClientLink WhmcsClientLink? ssoTickets SsoTicket[] @@map("users") } model UserCredential { id String @id @default(uuid()) userId String passwordHash String createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@map("user_credentials") } model UserSession { id String @id @default(uuid()) userId String tokenHash String @unique expiresAt DateTime @db.Timestamptz(3) revokedAt DateTime? @db.Timestamptz(3) ipAddress String? userAgent String? isImpersonation Boolean @default(false) impersonationMetadata Json? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([expiresAt]) @@map("user_sessions") } model EmailVerificationToken { id String @id @default(uuid()) userId String tokenHash String @unique expiresAt DateTime @db.Timestamptz(3) usedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([expiresAt]) @@map("email_verification_tokens") } model PasswordResetToken { id String @id @default(uuid()) userId String tokenHash String @unique expiresAt DateTime @db.Timestamptz(3) usedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([expiresAt]) @@map("password_reset_tokens") } model TotpCredential { id String @id @default(uuid()) userId String @unique secret String enabledAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) recoveryCodes RecoveryCode[] @@map("totp_credentials") } model RecoveryCode { id String @id @default(uuid()) totpCredentialId String codeHash String usedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) totpCredential TotpCredential @relation(fields: [totpCredentialId], references: [id], onDelete: Cascade) @@index([totpCredentialId]) @@map("recovery_codes") } model PlatformRole { id String @id @default(uuid()) name String @unique description String? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) userRoles UserPlatformRole[] @@map("platform_roles") } model UserPlatformRole { id String @id @default(uuid()) userId String roleId String createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) role PlatformRole @relation(fields: [roleId], references: [id], onDelete: Cascade) @@unique([userId, roleId]) @@index([userId]) @@index([roleId]) @@map("user_platform_roles") } model GameServer { id String @id @default(uuid()) userId String planId String? nodeId String? name String status GameServerStatus @default(DRAFT) version Int @default(1) edition ServerEdition @default(JAVA) softwareFamily SoftwareFamily @default(VANILLA) minecraftVersion String @default("1.21.1") eulaAccepted Boolean @default(false) hostPort Int? containerId String? dataPath String? ramMb Int @default(1024) activeWorldName String @default("world") idleShutdownEnabled Boolean @default(true) lastPlayerSeenAt DateTime? @db.Timestamptz(3) idleStopAt DateTime? @db.Timestamptz(3) runningSince DateTime? @db.Timestamptz(3) lastMeteredAt DateTime? @db.Timestamptz(3) joinSlug String? @unique joinToStartEnabled Boolean @default(true) billingSuspendedAt DateTime? @db.Timestamptz(3) billingSuspendReason String? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) user User @relation(fields: [userId], references: [id]) plan Plan? @relation(fields: [planId], references: [id]) node GameNode? @relation(fields: [nodeId], references: [id]) stateTransitions GameServerStateTransition[] allocation GameServerAllocation? backups ServerBackup[] backupRestores BackupRestore[] backupSchedule BackupSchedule? worldUploads WorldUpload[] installedAddons InstalledAddon[] startQueueEntry ServerStartQueueEntry? usageRecords UsageRecord[] dnsRecords ServerDnsRecord[] whmcsServiceLink WhmcsServiceLink? ssoTickets SsoTicket[] @@index([userId]) @@index([nodeId]) @@index([status]) @@map("game_servers") } model GameServerAllocation { id String @id @default(uuid()) serverId String @unique nodeId String hostPort Int ramMbReserved Int status AllocationStatus @default(ACTIVE) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) node GameNode @relation(fields: [nodeId], references: [id]) @@unique([nodeId, hostPort]) @@index([nodeId]) @@map("game_server_allocations") } model GameServerStateTransition { id String @id @default(uuid()) gameServerId String fromStatus GameServerStatus? toStatus GameServerStatus reason String? correlationId String? actorId String? errorCode String? errorMessage String? metadata Json? createdAt DateTime @default(now()) @db.Timestamptz(3) gameServer GameServer @relation(fields: [gameServerId], references: [id], onDelete: Cascade) @@index([gameServerId]) @@index([createdAt]) @@map("game_server_state_transitions") } model GameNode { id String @id @default(uuid()) name String @unique hostname String status GameNodeStatus @default(OFFLINE) maxServers Int @default(10) maxRamMb Int @default(16384) platformRamMb Int @default(2048) portRangeStart Int @default(25565) portRangeEnd Int @default(25664) activeServers Int @default(0) lastHeartbeatAt DateTime? @db.Timestamptz(3) agentVersion String? enrollmentTokenHash String? enrolledAt DateTime? @db.Timestamptz(3) drainRequestedAt DateTime? @db.Timestamptz(3) metadata Json? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) gameServers GameServer[] heartbeats GameNodeHeartbeat[] allocations GameServerAllocation[] startQueueEntries ServerStartQueueEntry[] @@map("game_nodes") } model GameNodeHeartbeat { id String @id @default(uuid()) nodeId String payload Json? createdAt DateTime @default(now()) @db.Timestamptz(3) node GameNode @relation(fields: [nodeId], references: [id], onDelete: Cascade) @@index([nodeId]) @@index([createdAt]) @@map("game_node_heartbeats") } model ServerStartQueueEntry { id String @id @default(uuid()) serverId String @unique nodeId String status StartQueueStatus @default(QUEUED) priority Int @default(0) correlationId String? requestedAt DateTime @default(now()) @db.Timestamptz(3) expiresAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) node GameNode @relation(fields: [nodeId], references: [id]) @@index([status, priority, requestedAt]) @@index([nodeId]) @@map("server_start_queue") } model Plan { id String @id @default(uuid()) name String @unique slug String @unique description String? isFree Boolean @default(false) isActive Boolean @default(true) maxRamMb Int maxCpuCores Float maxStorageMb Int maxServersPerUser Int? idleTimeoutMinutes Int @default(15) idleGraceAfterStartMinutes Int @default(3) idleCountdownSeconds Int @default(120) queuePriority Int @default(0) monthlyCreditGrant Int @default(0) metadata Json? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) gameServers GameServer[] @@map("plans") } model FeatureFlag { id String @id @default(uuid()) key String @unique description String? enabled Boolean @default(false) metadata Json? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) @@map("feature_flags") } model SystemSetting { id String @id @default(uuid()) key String @unique value Json createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) @@map("system_settings") } model AuditEvent { id String @id @default(uuid()) userId String? action String entityType String? entityId String? metadata Json? ipAddress String? createdAt DateTime @default(now()) @db.Timestamptz(3) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId]) @@index([action]) @@index([entityType, entityId]) @@index([createdAt]) @@map("audit_events") } model JobRecord { id String @id @default(uuid()) queue String jobName String status JobStatus @default(PENDING) payload Json? result Json? error String? attempts Int @default(0) startedAt DateTime? @db.Timestamptz(3) completedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) @@index([queue, status]) @@index([createdAt]) @@map("job_records") } model CreditWallet { id String @id @default(uuid()) userId String @unique balance Int @default(0) periodStart DateTime @default(now()) @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) transactions CreditTransaction[] @@map("credit_wallets") } model CreditTransaction { id String @id @default(uuid()) walletId String amount Int type CreditTransactionType referenceType String? referenceId String? idempotencyKey String @unique metadata Json? createdAt DateTime @default(now()) @db.Timestamptz(3) wallet CreditWallet @relation(fields: [walletId], references: [id], onDelete: Cascade) @@index([walletId, createdAt]) @@map("credit_transactions") } model UsageRecord { id String @id @default(uuid()) serverId String userId String ramMb Int durationSeconds Int creditsCharged Int periodStart DateTime @db.Timestamptz(3) periodEnd DateTime @db.Timestamptz(3) idempotencyKey String @unique createdAt DateTime @default(now()) @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) @@index([serverId, periodStart]) @@index([userId, periodStart]) @@map("usage_records") } model ServerBackup { id String @id @default(uuid()) serverId String type BackupType @default(MANUAL) status BackupStatus @default(PENDING) s3Key String? sizeBytes BigInt? sha256 String? label String? failureReason String? createdAt DateTime @default(now()) @db.Timestamptz(3) completedAt DateTime? @db.Timestamptz(3) expiresAt DateTime? @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) restores BackupRestore[] @relation("RestoredFromBackup") safetyForRestores BackupRestore[] @relation("SafetyBackup") @@index([serverId]) @@index([status]) @@index([createdAt]) @@map("server_backups") } model BackupRestore { id String @id @default(uuid()) serverId String backupId String safetyBackupId String? status BackupRestoreStatus @default(PENDING) failureReason String? createdAt DateTime @default(now()) @db.Timestamptz(3) completedAt DateTime? @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) backup ServerBackup @relation("RestoredFromBackup", fields: [backupId], references: [id]) safetyBackup ServerBackup? @relation("SafetyBackup", fields: [safetyBackupId], references: [id]) @@index([serverId]) @@index([backupId]) @@index([status]) @@map("backup_restores") } model BackupSchedule { id String @id @default(uuid()) serverId String @unique enabled Boolean @default(true) intervalHours Int @default(24) retentionCount Int @default(5) nextRunAt DateTime? @db.Timestamptz(3) lastRunAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) @@map("backup_schedules") } model WorldUpload { id String @id @default(uuid()) serverId String status WorldUploadStatus @default(PENDING) s3Key String worldName String @default("world") sha256 String? sizeBytes BigInt? failureReason String? createdAt DateTime @default(now()) @db.Timestamptz(3) completedAt DateTime? @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) @@index([serverId]) @@index([status]) @@map("world_uploads") } model InstalledAddon { id String @id @default(uuid()) serverId String provider String @default("modrinth") projectId String projectSlug String projectName String versionId String versionNumber String addonType AddonType fileName String filePath String fileSha512 String? status AddonInstallStatus @default(PENDING) failureReason String? installedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) @@unique([serverId, projectId, versionId]) @@index([serverId]) @@index([status]) @@map("installed_addons") } model ServerDnsRecord { id String @id @default(uuid()) serverId String fqdn String recordType String target String port Int? srvPriority Int? srvWeight Int? status DnsRecordStatus @default(PENDING) lastSyncedAt DateTime? @db.Timestamptz(3) lastError String? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) @@unique([serverId, recordType, fqdn]) @@index([fqdn]) @@map("server_dns_records") } model WhmcsInstallation { id String @id @default(uuid()) integrationId String @unique name String apiSecret String mtlsClientFingerprint String? mtlsClientSubject String? isActive Boolean @default(true) metadata Json? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) clientLinks WhmcsClientLink[] serviceLinks WhmcsServiceLink[] operations IntegrationOperation[] ssoTickets SsoTicket[] events IntegrationEvent[] syncCursor IntegrationSyncCursor? reconciliationIssues ReconciliationIssue[] usageExports WhmcsUsageExport[] @@map("whmcs_installations") } model WhmcsClientLink { id String @id @default(uuid()) installationId String externalClientId String userId String @unique createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([installationId, externalClientId]) @@map("whmcs_client_links") } model WhmcsServiceLink { id String @id @default(uuid()) installationId String externalServiceId String serverId String @unique externalProductId String? status WhmcsServiceStatus @default(PENDING) suspendedAt DateTime? @db.Timestamptz(3) terminatedAt DateTime? @db.Timestamptz(3) lastReconciledAt DateTime? @db.Timestamptz(3) metadata Json? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) @@unique([installationId, externalServiceId]) @@index([installationId, status]) @@map("whmcs_service_links") } model IntegrationOperation { id String @id @default(uuid()) installationId String idempotencyKey String @unique operation String externalRef String? status IntegrationOperationStatus @default(COMPLETED) result Json? error String? createdAt DateTime @default(now()) @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) @@index([installationId, createdAt]) @@map("integration_operations") } model SsoTicket { id String @id @default(uuid()) installationId String tokenHash String @unique kind SsoTicketKind userId String serverId String? externalServiceId String? externalClientId String? externalUserId String? targetPath String issuer String audience String nonce String @unique expiresAt DateTime @db.Timestamptz(3) usedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) server GameServer? @relation(fields: [serverId], references: [id], onDelete: SetNull) @@index([expiresAt]) @@index([installationId, createdAt]) @@map("sso_tickets") } model IntegrationEvent { id String @id @default(uuid()) installationId String eventType String payload Json dedupeKey String @unique status IntegrationEventStatus @default(PENDING) acknowledgedAt DateTime? @db.Timestamptz(3) deadLetteredAt DateTime? @db.Timestamptz(3) deadLetterReason String? createdAt DateTime @default(now()) @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) @@index([installationId, status, createdAt]) @@index([installationId, createdAt]) @@map("integration_events") } model IntegrationSyncCursor { id String @id @default(uuid()) installationId String @unique eventsCursor String? updatedAt DateTime @updatedAt @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) @@map("integration_sync_cursors") } model ReconciliationIssue { id String @id @default(uuid()) installationId String runId String externalServiceId String? serverId String? code String severity ReconciliationSeverity message String details Json? autoRepaired Boolean @default(false) resolvedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) @@index([installationId, runId]) @@index([installationId, resolvedAt]) @@map("reconciliation_issues") } model WhmcsUsageExport { id String @id @default(uuid()) installationId String externalServiceId String serverId String periodStart DateTime @db.Timestamptz(3) periodEnd DateTime @db.Timestamptz(3) metrics Json contentHash String status UsageExportStatus @default(DRAFT) isTestMode Boolean @default(false) adjustmentOfId String? exportedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) @@unique([installationId, externalServiceId, periodStart, periodEnd, isTestMode]) @@index([installationId, periodEnd]) @@map("whmcs_usage_exports") }