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 STARTING RUNNING STOPPING BACKING_UP RESTORING ERROR DELETING DELETED } enum GameNodeStatus { ONLINE OFFLINE MAINTENANCE DRAINING UNREACHABLE } enum AllocationStatus { ACTIVE RELEASED } 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 } 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? @@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? 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") 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[] @@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) activeServers Int @default(0) lastHeartbeatAt DateTime? @db.Timestamptz(3) agentVersion String? enrollmentTokenHash String? enrolledAt 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[] @@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 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 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 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") }