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 ALLOCATING PROVISIONING INSTALLING STOPPED QUEUED STARTING RUNNING STOPPING BACKING_UP RESTORING MIGRATING SUSPENDED MAINTENANCE UNKNOWN ERROR DELETING DELETED } enum GameServerMemberRole { OWNER ADMIN OPERATOR DEVELOPER VIEWER } enum OrganizationMemberRole { OWNER ADMIN MEMBER } enum NotificationChannel { IN_APP EMAIL WEBHOOK DISCORD } enum NotificationType { SERVER_READY SERVER_START_FAILED SERVER_STOPPED IDLE_STOP_WARNING BACKUP_SUCCESS BACKUP_FAILED RESTORE_SUCCESS RESTORE_FAILED CREDITS_LOW BILLING_ISSUE INVITE NODE_INCIDENT MAINTENANCE SECURITY NEW_LOGIN TWO_FA_CHANGED } enum AbuseReportStatus { OPEN INVESTIGATING RESOLVED DISMISSED } enum SupportTicketStatus { OPEN IN_PROGRESS WAITING RESOLVED CLOSED } enum SupportTicketPriority { LOW NORMAL HIGH URGENT } enum ScheduleTaskType { START STOP RESTART BACKUP CONSOLE_COMMAND UPDATE_CHECK MAINTENANCE } enum CatalogProviderKind { MODRINTH CURSEFORGE MOJANG PAPERMC CUSTOM } 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? whmcsUserLinks WhmcsUserLink[] ssoTickets SsoTicket[] organizationMembers OrganizationMember[] organizationInvites OrganizationInvite[] @relation("OrganizationInviteInviter") gameServerMembers GameServerMember[] notifications Notification[] notificationPreferences NotificationPreference[] abuseReports AbuseReport[] supportTickets SupportTicket[] securityChangeLogs SecurityChangeLog[] dataExportRequests DataExportRequest[] accountDeletionRequests AccountDeletionRequest[] @@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[] members GameServerMember[] worlds World[] schedules Schedule[] properties GameServerProperty[] environmentVars GameServerEnvironmentVariable[] secrets GameServerSecret[] @@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[] capabilities GameNodeCapability[] maintenanceWindows GameNodeMaintenanceWindow[] @@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[] userLinks WhmcsUserLink[] serviceLinks WhmcsServiceLink[] productMappings WhmcsProductMapping[] optionMappings WhmcsOptionMapping[] addonMappings WhmcsAddonMapping[] 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") } model Organization { id String @id @default(uuid()) name String slug String @unique createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) members OrganizationMember[] invites OrganizationInvite[] @@map("organizations") } model OrganizationMember { id String @id @default(uuid()) organizationId String userId String role OrganizationMemberRole @default(MEMBER) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([organizationId, userId]) @@index([userId]) @@map("organization_members") } model OrganizationInvite { id String @id @default(uuid()) organizationId String email String role OrganizationMemberRole @default(MEMBER) tokenHash String @unique invitedById String expiresAt DateTime @db.Timestamptz(3) acceptedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) invitedBy User @relation("OrganizationInviteInviter", fields: [invitedById], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([email]) @@map("organization_invites") } model GameServerMember { id String @id @default(uuid()) serverId String userId String role GameServerMemberRole @default(VIEWER) permissions Json? invitedAt DateTime @default(now()) @db.Timestamptz(3) acceptedAt 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) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([serverId, userId]) @@index([userId]) @@map("game_server_members") } model GameServerProperty { id String @id @default(uuid()) serverId String key String value 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, key]) @@map("game_server_properties") } model GameServerEnvironmentVariable { id String @id @default(uuid()) serverId String key String value String isSecret Boolean @default(false) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) @@unique([serverId, key]) @@map("game_server_environment_variables") } model GameServerSecret { id String @id @default(uuid()) serverId String name String valueCiphertext 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, name]) @@map("game_server_secrets") } model GameNodeCapability { id String @id @default(uuid()) nodeId String key String value String? createdAt DateTime @default(now()) @db.Timestamptz(3) node GameNode @relation(fields: [nodeId], references: [id], onDelete: Cascade) @@unique([nodeId, key]) @@map("game_node_capabilities") } model GameNodeMaintenanceWindow { id String @id @default(uuid()) nodeId String startsAt DateTime @db.Timestamptz(3) endsAt DateTime @db.Timestamptz(3) drainFirst Boolean @default(true) message String? createdAt DateTime @default(now()) @db.Timestamptz(3) node GameNode @relation(fields: [nodeId], references: [id], onDelete: Cascade) @@index([nodeId, startsAt]) @@map("game_node_maintenance_windows") } model MinecraftVersion { id String @id @default(uuid()) version String @unique edition ServerEdition @default(JAVA) isActive Boolean @default(true) createdAt DateTime @default(now()) @db.Timestamptz(3) softwareBuilds SoftwareBuild[] @@map("minecraft_versions") } model SoftwareBuild { id String @id @default(uuid()) family SoftwareFamily minecraftVersionId String downloadUrl String? sha256 String? isActive Boolean @default(true) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) minecraftVersion MinecraftVersion @relation(fields: [minecraftVersionId], references: [id], onDelete: Cascade) @@unique([family, minecraftVersionId]) @@map("software_builds") } model RuntimeImage { id String @id @default(uuid()) name String @unique imageRef String digest String? javaVersion String? isActive Boolean @default(true) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) @@map("runtime_images") } model CatalogProvider { id String @id @default(uuid()) kind CatalogProviderKind @unique name String isEnabled Boolean @default(false) config Json? lastSyncAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) projects CatalogProject[] @@map("catalog_providers") } model CatalogProject { id String @id @default(uuid()) providerId String externalId String slug String name String projectType String metadata Json? syncedAt DateTime @default(now()) @db.Timestamptz(3) provider CatalogProvider @relation(fields: [providerId], references: [id], onDelete: Cascade) versions CatalogVersion[] @@unique([providerId, externalId]) @@index([slug]) @@map("catalog_projects") } model CatalogVersion { id String @id @default(uuid()) projectId String externalId String versionNumber String gameVersions Json? loaders Json? sha512 String? downloadUrl String? syncedAt DateTime @default(now()) @db.Timestamptz(3) project CatalogProject @relation(fields: [projectId], references: [id], onDelete: Cascade) dependencies CatalogDependency[] @relation("VersionDependencies") @@unique([projectId, externalId]) @@map("catalog_versions") } model CatalogDependency { id String @id @default(uuid()) versionId String dependencyId String dependencyType String version CatalogVersion @relation("VersionDependencies", fields: [versionId], references: [id], onDelete: Cascade) @@index([versionId]) @@map("catalog_dependencies") } model World { id String @id @default(uuid()) serverId String name String isActive Boolean @default(false) sizeBytes BigInt? hasLevelDat Boolean @default(false) seed String? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade) archives WorldArchive[] @@unique([serverId, name]) @@map("worlds") } model WorldArchive { id String @id @default(uuid()) worldId String s3Key String sha256 String? sizeBytes BigInt? createdAt DateTime @default(now()) @db.Timestamptz(3) world World @relation(fields: [worldId], references: [id], onDelete: Cascade) @@index([worldId]) @@map("world_archives") } model Schedule { id String @id @default(uuid()) serverId String name String timezone String @default("UTC") cronExpr String enabled Boolean @default(true) 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) tasks ScheduledTask[] @@index([serverId]) @@map("schedules") } model ScheduledTask { id String @id @default(uuid()) scheduleId String taskType ScheduleTaskType payload Json? createdAt DateTime @default(now()) @db.Timestamptz(3) schedule Schedule @relation(fields: [scheduleId], references: [id], onDelete: Cascade) @@index([scheduleId]) @@map("scheduled_tasks") } model Notification { id String @id @default(uuid()) userId String type NotificationType channel NotificationChannel @default(IN_APP) title String body String metadata Json? readAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, readAt]) @@index([createdAt]) @@map("notifications") } model NotificationPreference { id String @id @default(uuid()) userId String type NotificationType channel NotificationChannel enabled Boolean @default(true) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, type, channel]) @@map("notification_preferences") } model AbuseReport { id String @id @default(uuid()) reporterId String? targetUserId String? serverId String? status AbuseReportStatus @default(OPEN) reason String details String? resolution String? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) reporter User? @relation(fields: [reporterId], references: [id], onDelete: SetNull) @@index([status]) @@index([serverId]) @@map("abuse_reports") } model SupportTicket { id String @id @default(uuid()) userId String serverId String? subject String status SupportTicketStatus @default(OPEN) priority SupportTicketPriority @default(NORMAL) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) messages SupportTicketMessage[] @@index([userId]) @@index([status]) @@map("support_tickets") } model SupportTicketMessage { id String @id @default(uuid()) ticketId String authorId String? isInternal Boolean @default(false) body String createdAt DateTime @default(now()) @db.Timestamptz(3) ticket SupportTicket @relation(fields: [ticketId], references: [id], onDelete: Cascade) @@index([ticketId]) @@map("support_ticket_messages") } model Subscription { id String @id @default(uuid()) userId String planId String status String startsAt DateTime @db.Timestamptz(3) endsAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) @@index([userId]) @@map("subscriptions") } model Invoice { id String @id @default(uuid()) userId String subscriptionId String? amountCents Int currency String @default("EUR") status String externalRef String? createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) payments Payment[] @@index([userId]) @@map("invoices") } model Payment { id String @id @default(uuid()) invoiceId String amountCents Int provider String externalRef String? status String createdAt DateTime @default(now()) @db.Timestamptz(3) invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade) @@index([invoiceId]) @@map("payments") } model ResourceQuota { id String @id @default(uuid()) userId String? planId String? key String limit Int used Int @default(0) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) @@unique([userId, planId, key]) @@map("resource_quotas") } model SecurityChangeLog { id String @id @default(uuid()) userId String action String metadata Json? ipAddress String? createdAt DateTime @default(now()) @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, createdAt]) @@map("security_change_logs") } model DataExportRequest { id String @id @default(uuid()) userId String status String @default("PENDING") downloadUrl String? expiresAt DateTime? @db.Timestamptz(3) completedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@map("data_export_requests") } model AccountDeletionRequest { id String @id @default(uuid()) userId String status String @default("PENDING") scheduledFor DateTime @db.Timestamptz(3) completedAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@map("account_deletion_requests") } model WhmcsUserLink { id String @id @default(uuid()) installationId String externalUserId String externalClientId String userId String 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, externalUserId]) @@index([userId]) @@map("whmcs_user_links") } model WhmcsProductMapping { id String @id @default(uuid()) installationId String externalProductId String planSlug String defaultEdition ServerEdition @default(JAVA) defaultSoftware SoftwareFamily @default(VANILLA) defaultVersion String @default("1.21.1") isActive Boolean @default(true) createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) @@unique([installationId, externalProductId]) @@map("whmcs_product_mappings") } model WhmcsOptionMapping { id String @id @default(uuid()) installationId String externalOptionId String externalValueId String resourceKey String normalizedValue String createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) @@unique([installationId, externalOptionId, externalValueId]) @@map("whmcs_option_mappings") } model WhmcsAddonMapping { id String @id @default(uuid()) installationId String externalAddonId String featureKey String deltaValue String createdAt DateTime @default(now()) @db.Timestamptz(3) updatedAt DateTime @updatedAt @db.Timestamptz(3) installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade) @@unique([installationId, externalAddonId]) @@map("whmcs_addon_mappings") }