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[] triggeredUpgrades InstanceUpgrade[] acknowledgedEvents InstanceEvent[] agentInviteCodes AgentInviteCode[] @@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 @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") // Remote agent management isRemote Boolean @default(false) @map("is_remote") agentUrl String? @map("agent_url") agentFingerprint String? @map("agent_fingerprint") agentVersion String? @map("agent_version") agentLastSeen DateTime? @map("agent_last_seen") // 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") enableAnalytics Boolean @default(false) @map("enable_analytics") 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") pangolinSubdomainPrefix String? @map("pangolin_subdomain_prefix") // 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[] restores InstanceRestore[] auditLogs AuditLog[] upgrades InstanceUpgrade[] events InstanceEvent[] agentCert IssuedAgentCert? @@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) restores InstanceRestore[] @@index([instanceId, startedAt]) @@map("backups") } // ─── Restore ─────────────────────────────────────────────── enum RestoreStatus { PENDING UPLOADING RUNNING COMPLETED FAILED } model InstanceRestore { id String @id @default(uuid()) instanceId String @map("instance_id") backupId String @map("backup_id") status RestoreStatus @default(PENDING) uploadId String? @map("upload_id") progressJson Json? @map("progress_json") logTail String? @map("log_tail") errorMessage String? @map("error_message") triggeredById String? @map("triggered_by_id") startedAt DateTime @default(now()) @map("started_at") completedAt DateTime? @map("completed_at") instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) backup Backup @relation(fields: [backupId], references: [id], onDelete: Cascade) @@index([instanceId, startedAt]) @@index([backupId]) @@map("instance_restores") } // ─── Audit Log ───────────────────────────────────────────── enum AuditAction { INSTANCE_CREATE INSTANCE_UPDATE INSTANCE_DELETE INSTANCE_START INSTANCE_STOP INSTANCE_RESTART INSTANCE_UPGRADE SECRETS_VIEWED BACKUP_CREATE BACKUP_DELETE BACKUP_RESTORE PANGOLIN_SETUP PANGOLIN_TEARDOWN PANGOLIN_SYNC AGENT_CONNECT AGENT_REGISTER AGENT_APPROVE AGENT_REJECT INVITE_CREATE INVITE_REVOKE CERT_ISSUE CERT_REVOKE 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") } // ─── Instance Upgrades ──────────────────────────────────── enum UpgradeStatus { PENDING IN_PROGRESS COMPLETED FAILED ROLLED_BACK } model InstanceUpgrade { id String @id @default(uuid()) instanceId String @map("instance_id") status UpgradeStatus @default(PENDING) previousCommit String? @map("previous_commit") newCommit String? @map("new_commit") commitCount Int @default(0) @map("commit_count") branch String @default("v2") // Progress tracking (updated from progress.json polling) currentPhase Int @default(0) @map("current_phase") phaseName String? @map("phase_name") percentage Int @default(0) progressMessage String? @map("progress_message") // Result durationSeconds Int? @map("duration_seconds") errorMessage String? @map("error_message") warnings Json? log String? triggeredById String? @map("triggered_by_id") startedAt DateTime @default(now()) @map("started_at") completedAt DateTime? @map("completed_at") instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) triggeredBy CcpUser? @relation(fields: [triggeredById], references: [id], onDelete: SetNull) @@index([instanceId, startedAt]) @@map("instance_upgrades") } // ─── Instance Events (Errors/Warnings) ─────────────────── enum EventSeverity { ERROR WARNING INFO } model InstanceEvent { id String @id @default(uuid()) instanceId String @map("instance_id") severity EventSeverity source String // 'health_check', 'upgrade', 'container', 'provisioning' title String message String metadata Json? acknowledged Boolean @default(false) acknowledgedAt DateTime? @map("acknowledged_at") acknowledgedById String? @map("acknowledged_by_id") createdAt DateTime @default(now()) @map("created_at") instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) acknowledgedBy CcpUser? @relation(fields: [acknowledgedById], references: [id], onDelete: SetNull) @@index([instanceId, createdAt]) @@index([severity, acknowledged]) @@map("instance_events") } // ─── CCP Settings ────────────────────────────────────────── model CcpSetting { key String @id value Json updatedAt DateTime @updatedAt @map("updated_at") @@map("ccp_settings") } // ─── Remote Agent Management ────────────────────────────── model CcpCertificateAuthority { id String @id @default(uuid()) commonName String @map("common_name") encryptedKey String @map("encrypted_key") certPem String @map("cert_pem") fingerprint String createdAt DateTime @default(now()) @map("created_at") expiresAt DateTime @map("expires_at") issuedCerts IssuedAgentCert[] @@map("ccp_certificate_authority") } model IssuedAgentCert { id String @id @default(uuid()) caId String @map("ca_id") instanceId String @unique @map("instance_id") commonName String @map("common_name") encryptedKey String @map("encrypted_key") certPem String @map("cert_pem") fingerprint String issuedAt DateTime @default(now()) @map("issued_at") expiresAt DateTime @map("expires_at") revokedAt DateTime? @map("revoked_at") ca CcpCertificateAuthority @relation(fields: [caId], references: [id]) instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) @@index([instanceId]) @@map("issued_agent_certs") } model AgentInviteCode { id String @id @default(uuid()) code String @unique createdById String @map("created_by_id") usedById String? @map("used_by_id") expiresAt DateTime @map("expires_at") usedAt DateTime? @map("used_at") createdAt DateTime @default(now()) @map("created_at") createdBy CcpUser @relation(fields: [createdById], references: [id]) @@map("agent_invite_codes") } enum AgentRegistrationStatus { PENDING APPROVED REJECTED EXPIRED } model AgentRegistration { id String @id @default(uuid()) inviteCodeId String @map("invite_code_id") slug String name String domain String agentUrl String @map("agent_url") basePath String @map("base_path") composeProject String @map("compose_project") metadata Json? status AgentRegistrationStatus @default(PENDING) instanceId String? @map("instance_id") approvedById String? @map("approved_by_id") approvedAt DateTime? @map("approved_at") rejectedAt DateTime? @map("rejected_at") certBundle Json? @map("cert_bundle") createdAt DateTime @default(now()) @map("created_at") @@index([status]) @@map("agent_registrations") }