Files
HexaHost-GameCloud/packages/database/prisma/schema.prisma
smueller b335f6a497
Some checks failed
CI / Node — lint, typecheck, test, build (push) Failing after 9s
CI / Go — node-agent tests (push) Failing after 9s
CI / Go — edge-gateway build (push) Successful in 17s
Phase8
2026-06-26 13:33:03 +02:00

687 lines
19 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
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
}
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?
@@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")
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)
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[]
@@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")
}