generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ─── CCP Users (control panel operators) ─────────────────── enum CcpRole { SUPER_ADMIN OPERATOR VIEWER } model CcpUser { id String @id @default(uuid()) email String @unique password String name String role CcpRole @default(OPERATOR) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") refreshTokens CcpRefreshToken[] auditLogs AuditLog[] @@map("ccp_users") } model CcpRefreshToken { id String @id @default(uuid()) token String @unique userId String @map("user_id") expiresAt DateTime @map("expires_at") createdAt DateTime @default(now()) @map("created_at") user CcpUser @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([expiresAt]) @@map("ccp_refresh_tokens") } // ─── Managed Instances ───────────────────────────────────── enum InstanceStatus { PROVISIONING RUNNING STOPPED ERROR DESTROYING } model Instance { id String @id @default(uuid()) slug String @unique name String domain String @unique status InstanceStatus @default(PROVISIONING) statusMessage String? @map("status_message") basePath String @map("base_path") composeProject String @unique @map("compose_project") gitBranch String @default("v2") @map("git_branch") gitCommit String? @map("git_commit") // Allocated host ports (JSON: { api: 14001, admin: 13001, postgres: 15401, nginx: 10001 }) portConfig Json @map("port_config") // AES-256-GCM encrypted JSON blob of all instance secrets (null for registered instances) encryptedSecrets String? @map("encrypted_secrets") // True if this instance was registered externally (not provisioned by CCP) isRegistered Boolean @default(false) @map("is_registered") // Feature flags enableMedia Boolean @default(false) @map("enable_media") enableChat Boolean @default(false) @map("enable_chat") enableGancio Boolean @default(false) @map("enable_gancio") enableListmonk Boolean @default(false) @map("enable_listmonk") enableMonitoring Boolean @default(false) @map("enable_monitoring") enableDevTools Boolean @default(false) @map("enable_dev_tools") enablePayments Boolean @default(false) @map("enable_payments") enableMeet Boolean @default(false) @map("enable_meet") enableSms Boolean @default(false) @map("enable_sms") enableSocial Boolean @default(false) @map("enable_social") enablePeople Boolean @default(false) @map("enable_people") jvbAdvertiseIp String? @map("jvb_advertise_ip") // Admin config adminEmail String @map("admin_email") // Pangolin tunnel pangolinEndpoint String? @map("pangolin_endpoint") pangolinSiteId String? @map("pangolin_site_id") pangolinNewtId String? @map("pangolin_newt_id") pangolinNewtSecret String? @map("pangolin_newt_secret") // SMTP smtpHost String? @map("smtp_host") smtpPort Int? @map("smtp_port") smtpUser String? @map("smtp_user") smtpFrom String? @map("smtp_from") emailTestMode Boolean @default(true) @map("email_test_mode") notes String? createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") lastHealthCheck DateTime? @map("last_health_check") portAllocations PortAllocation[] healthChecks HealthCheck[] backups Backup[] auditLogs AuditLog[] @@map("instances") } // ─── Port Allocation ─────────────────────────────────────── model PortAllocation { id String @id @default(uuid()) port Int @unique instanceId String @map("instance_id") service String notes String? createdAt DateTime @default(now()) @map("created_at") instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) @@index([instanceId]) @@map("port_allocations") } // ─── Health Checks ───────────────────────────────────────── enum HealthStatus { HEALTHY DEGRADED UNHEALTHY UNKNOWN } model HealthCheck { id String @id @default(uuid()) instanceId String @map("instance_id") status HealthStatus serviceStatus Json @map("service_status") totalServices Int @map("total_services") healthyServices Int @map("healthy_services") responseTimeMs Int? @map("response_time_ms") checkedAt DateTime @default(now()) @map("checked_at") instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) @@index([instanceId, checkedAt]) @@map("health_checks") } // ─── Backups ─────────────────────────────────────────────── enum BackupStatus { PENDING IN_PROGRESS COMPLETED FAILED } model Backup { id String @id @default(uuid()) instanceId String @map("instance_id") status BackupStatus @default(PENDING) archivePath String? @map("archive_path") sizeBytes BigInt? @map("size_bytes") manifest Json? startedAt DateTime @default(now()) @map("started_at") completedAt DateTime? @map("completed_at") errorMessage String? @map("error_message") s3Uploaded Boolean @default(false) @map("s3_uploaded") s3Key String? @map("s3_key") instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) @@index([instanceId, startedAt]) @@map("backups") } // ─── Audit Log ───────────────────────────────────────────── enum AuditAction { INSTANCE_CREATE INSTANCE_UPDATE INSTANCE_DELETE INSTANCE_START INSTANCE_STOP INSTANCE_RESTART INSTANCE_UPGRADE SECRETS_VIEWED BACKUP_CREATE BACKUP_DELETE PANGOLIN_SETUP PANGOLIN_SYNC USER_LOGIN USER_CREATE USER_UPDATE USER_DELETE SETTINGS_UPDATE } model AuditLog { id String @id @default(uuid()) userId String? @map("user_id") instanceId String? @map("instance_id") action AuditAction details Json? ipAddress String? @map("ip_address") createdAt DateTime @default(now()) @map("created_at") user CcpUser? @relation(fields: [userId], references: [id], onDelete: SetNull) instance Instance? @relation(fields: [instanceId], references: [id], onDelete: SetNull) @@index([instanceId, createdAt]) @@index([userId, createdAt]) @@index([action, createdAt]) @@map("audit_logs") } // ─── CCP Settings ────────────────────────────────────────── model CcpSetting { key String @id value Json updatedAt DateTime @updatedAt @map("updated_at") @@map("ccp_settings") }