Files

1576 lines
44 KiB
Plaintext

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")
}