720 lines
26 KiB
Plaintext
720 lines
26 KiB
Plaintext
// This is your Prisma schema file,
|
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
// url configured in prisma.config.ts (Prisma 7+)
|
|
}
|
|
|
|
// ============================================================================
|
|
// ENUMS
|
|
// ============================================================================
|
|
|
|
enum UserStatus {
|
|
PENDING_VERIFICATION
|
|
ACTIVE
|
|
SUSPENDED
|
|
}
|
|
|
|
enum StaffRole {
|
|
OWNER // Full access, cannot be deleted
|
|
ADMIN // Full access, can manage staff
|
|
MANAGER // Orders + servers, no staff/settings
|
|
SUPPORT // View only + limited actions
|
|
}
|
|
|
|
enum StaffStatus {
|
|
ACTIVE
|
|
SUSPENDED
|
|
}
|
|
|
|
enum SubscriptionPlan {
|
|
TRIAL
|
|
STARTER
|
|
PRO
|
|
ENTERPRISE
|
|
}
|
|
|
|
enum SubscriptionTier {
|
|
HUB_DASHBOARD
|
|
ADVANCED
|
|
}
|
|
|
|
enum SubscriptionStatus {
|
|
TRIAL
|
|
ACTIVE
|
|
CANCELED
|
|
PAST_DUE
|
|
}
|
|
|
|
enum OrderStatus {
|
|
PAYMENT_CONFIRMED
|
|
AWAITING_SERVER
|
|
SERVER_READY
|
|
DNS_PENDING
|
|
DNS_READY
|
|
PROVISIONING
|
|
FULFILLED
|
|
EMAIL_CONFIGURED
|
|
FAILED
|
|
}
|
|
|
|
enum AutomationMode {
|
|
AUTO // Website orders - self-executing
|
|
MANUAL // Staff-created - step-by-step
|
|
PAUSED // Stopped for intervention
|
|
}
|
|
|
|
enum DnsRecordStatus {
|
|
PENDING
|
|
VERIFIED
|
|
MISMATCH
|
|
NOT_FOUND
|
|
ERROR
|
|
SKIPPED // For wildcard pass or manual override
|
|
}
|
|
|
|
enum JobStatus {
|
|
PENDING
|
|
CLAIMED
|
|
RUNNING
|
|
COMPLETED
|
|
FAILED
|
|
DEAD
|
|
}
|
|
|
|
enum LogLevel {
|
|
DEBUG
|
|
INFO
|
|
WARN
|
|
ERROR
|
|
}
|
|
|
|
enum ServerConnectionStatus {
|
|
PENDING // Awaiting orchestrator registration
|
|
REGISTERED // Orchestrator has registered
|
|
ONLINE // Recent heartbeat received
|
|
OFFLINE // No recent heartbeat
|
|
}
|
|
|
|
enum CommandStatus {
|
|
PENDING
|
|
SENT
|
|
EXECUTING
|
|
COMPLETED
|
|
FAILED
|
|
}
|
|
|
|
// ============================================================================
|
|
// SYSTEM SETTINGS
|
|
// ============================================================================
|
|
|
|
model SystemSetting {
|
|
id String @id @default(cuid())
|
|
key String @unique
|
|
value String // Encrypted for sensitive values
|
|
encrypted Boolean @default(false)
|
|
category String @default("general")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
@@map("system_settings")
|
|
}
|
|
|
|
// ============================================================================
|
|
// USER & STAFF MODELS
|
|
// ============================================================================
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
passwordHash String @map("password_hash")
|
|
name String?
|
|
company String?
|
|
status UserStatus @default(PENDING_VERIFICATION)
|
|
emailVerified DateTime? @map("email_verified")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
// 2FA fields
|
|
twoFactorEnabled Boolean @default(false) @map("two_factor_enabled")
|
|
twoFactorSecretEnc String? @map("two_factor_secret_enc")
|
|
twoFactorVerifiedAt DateTime? @map("two_factor_verified_at")
|
|
backupCodesEnc String? @map("backup_codes_enc")
|
|
|
|
subscriptions Subscription[]
|
|
orders Order[]
|
|
tokenUsage TokenUsage[]
|
|
|
|
@@map("users")
|
|
}
|
|
|
|
model Staff {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
passwordHash String @map("password_hash")
|
|
name String
|
|
role StaffRole @default(SUPPORT)
|
|
status StaffStatus @default(ACTIVE)
|
|
invitedBy String? @map("invited_by") // Staff ID who sent invite
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
// Profile
|
|
profilePhotoKey String? @map("profile_photo_key") // S3/MinIO key for profile photo
|
|
|
|
// 2FA fields
|
|
twoFactorEnabled Boolean @default(false) @map("two_factor_enabled")
|
|
twoFactorSecretEnc String? @map("two_factor_secret_enc")
|
|
twoFactorVerifiedAt DateTime? @map("two_factor_verified_at")
|
|
backupCodesEnc String? @map("backup_codes_enc")
|
|
|
|
@@map("staff")
|
|
}
|
|
|
|
model StaffInvitation {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
role StaffRole @default(SUPPORT)
|
|
token String @unique
|
|
expiresAt DateTime @map("expires_at")
|
|
invitedBy String @map("invited_by")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
@@map("staff_invitations")
|
|
}
|
|
|
|
// ============================================================================
|
|
// SUBSCRIPTION & BILLING
|
|
// ============================================================================
|
|
|
|
model Subscription {
|
|
id String @id @default(cuid())
|
|
userId String @map("user_id")
|
|
plan SubscriptionPlan @default(TRIAL)
|
|
tier SubscriptionTier @default(HUB_DASHBOARD)
|
|
tokenLimit Int @default(10000) @map("token_limit")
|
|
tokensUsed Int @default(0) @map("tokens_used")
|
|
trialEndsAt DateTime? @map("trial_ends_at")
|
|
stripeCustomerId String? @map("stripe_customer_id")
|
|
stripeSubscriptionId String? @map("stripe_subscription_id")
|
|
status SubscriptionStatus @default(TRIAL)
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("subscriptions")
|
|
}
|
|
|
|
// ============================================================================
|
|
// ORDERS & PROVISIONING
|
|
// ============================================================================
|
|
|
|
model Order {
|
|
id String @id @default(cuid())
|
|
userId String @map("user_id")
|
|
status OrderStatus @default(PAYMENT_CONFIRMED)
|
|
tier SubscriptionTier
|
|
domain String
|
|
tools String[]
|
|
configJson Json @map("config_json")
|
|
|
|
// Automation mode
|
|
automationMode AutomationMode @default(MANUAL)
|
|
automationPausedAt DateTime? @map("automation_paused_at")
|
|
automationPausedReason String? @map("automation_paused_reason")
|
|
source String? // "website" | "staff" | "api"
|
|
|
|
// Customer/provisioning config
|
|
customer String? @map("customer") // Short name for subdomains (e.g., "acme")
|
|
companyName String? @map("company_name") // Display name (e.g., "Acme Corporation")
|
|
licenseKey String? @map("license_key") // Generated: lb_inst_xxx
|
|
|
|
// Server credentials (entered by staff)
|
|
serverIp String? @map("server_ip")
|
|
serverPasswordEncrypted String? @map("server_password_encrypted")
|
|
sshPort Int @default(22) @map("ssh_port")
|
|
netcupServerId String? @map("netcup_server_id") // Netcup API server ID for linking
|
|
|
|
// Generated after provisioning
|
|
portainerUrl String? @map("portainer_url")
|
|
dashboardUrl String? @map("dashboard_url")
|
|
failureReason String? @map("failure_reason")
|
|
|
|
// Portainer credentials (encrypted, synced from agent)
|
|
portainerUsername String? @map("portainer_username") // e.g., "admin-xyz123"
|
|
portainerPasswordEnc String? @map("portainer_password_enc") // AES-256-CBC encrypted
|
|
credentialsSyncedAt DateTime? @map("credentials_synced_at") // Last sync from agent
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
serverReadyAt DateTime? @map("server_ready_at")
|
|
provisioningStartedAt DateTime? @map("provisioning_started_at")
|
|
completedAt DateTime? @map("completed_at")
|
|
dnsVerifiedAt DateTime? @map("dns_verified_at")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
provisioningLogs ProvisioningLog[]
|
|
jobs ProvisioningJob[]
|
|
serverConnection ServerConnection?
|
|
dnsVerification DnsVerification?
|
|
|
|
@@map("orders")
|
|
}
|
|
|
|
// ============================================================================
|
|
// DNS VERIFICATION
|
|
// ============================================================================
|
|
|
|
model DnsVerification {
|
|
id String @id @default(cuid())
|
|
orderId String @unique @map("order_id")
|
|
wildcardPassed Boolean @default(false) @map("wildcard_passed")
|
|
manualOverride Boolean @default(false) @map("manual_override") // Staff skipped check
|
|
allPassed Boolean @default(false) @map("all_passed")
|
|
totalSubdomains Int @default(0) @map("total_subdomains")
|
|
passedCount Int @default(0) @map("passed_count")
|
|
lastCheckedAt DateTime? @map("last_checked_at")
|
|
verifiedAt DateTime? @map("verified_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
|
records DnsRecord[]
|
|
|
|
@@map("dns_verifications")
|
|
}
|
|
|
|
model DnsRecord {
|
|
id String @id @default(cuid())
|
|
dnsVerificationId String @map("dns_verification_id")
|
|
subdomain String // "cloud"
|
|
fullDomain String @map("full_domain") // "cloud.example.com"
|
|
expectedIp String @map("expected_ip")
|
|
resolvedIp String? @map("resolved_ip")
|
|
status DnsRecordStatus @default(PENDING)
|
|
errorMessage String? @map("error_message")
|
|
checkedAt DateTime? @map("checked_at")
|
|
|
|
dnsVerification DnsVerification @relation(fields: [dnsVerificationId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([dnsVerificationId])
|
|
@@map("dns_records")
|
|
}
|
|
|
|
model ProvisioningLog {
|
|
id String @id @default(cuid())
|
|
orderId String @map("order_id")
|
|
level LogLevel @default(INFO)
|
|
message String
|
|
step String?
|
|
timestamp DateTime @default(now())
|
|
|
|
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([orderId, timestamp])
|
|
@@map("provisioning_logs")
|
|
}
|
|
|
|
// ============================================================================
|
|
// JOB QUEUE
|
|
// ============================================================================
|
|
|
|
model ProvisioningJob {
|
|
id String @id @default(cuid())
|
|
orderId String @map("order_id")
|
|
jobType String @map("job_type")
|
|
status JobStatus @default(PENDING)
|
|
priority Int @default(0)
|
|
claimedAt DateTime? @map("claimed_at")
|
|
claimedBy String? @map("claimed_by")
|
|
containerName String? @map("container_name")
|
|
attempt Int @default(1)
|
|
maxAttempts Int @default(3) @map("max_attempts")
|
|
nextRetryAt DateTime? @map("next_retry_at")
|
|
configSnapshot Json @map("config_snapshot")
|
|
runnerTokenHash String? @map("runner_token_hash")
|
|
result Json?
|
|
error String?
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
completedAt DateTime? @map("completed_at")
|
|
|
|
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
|
logs JobLog[]
|
|
|
|
@@index([status, priority, createdAt])
|
|
@@index([orderId])
|
|
@@map("provisioning_jobs")
|
|
}
|
|
|
|
model JobLog {
|
|
id String @id @default(cuid())
|
|
jobId String @map("job_id")
|
|
level LogLevel @default(INFO)
|
|
message String
|
|
step String?
|
|
progress Int?
|
|
timestamp DateTime @default(now())
|
|
|
|
job ProvisioningJob @relation(fields: [jobId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([jobId, timestamp])
|
|
@@map("job_logs")
|
|
}
|
|
|
|
// ============================================================================
|
|
// TOKEN USAGE (AI Tracking)
|
|
// ============================================================================
|
|
|
|
model TokenUsage {
|
|
id String @id @default(cuid())
|
|
userId String @map("user_id")
|
|
instanceId String? @map("instance_id")
|
|
operation String // chat, analysis, setup
|
|
tokensInput Int @map("tokens_input")
|
|
tokensOutput Int @map("tokens_output")
|
|
model String
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId, createdAt])
|
|
@@map("token_usage")
|
|
}
|
|
|
|
// ============================================================================
|
|
// RUNNER TOKENS
|
|
// ============================================================================
|
|
|
|
model RunnerToken {
|
|
id String @id @default(cuid())
|
|
tokenHash String @unique @map("token_hash")
|
|
name String
|
|
isActive Boolean @default(true) @map("is_active")
|
|
lastUsed DateTime? @map("last_used")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
@@map("runner_tokens")
|
|
}
|
|
|
|
// ============================================================================
|
|
// SERVER CONNECTION (Phone-Home System)
|
|
// ============================================================================
|
|
|
|
model ServerConnection {
|
|
id String @id @default(cuid())
|
|
orderId String @unique @map("order_id")
|
|
|
|
// Registration token (generated during provisioning, used by orchestrator to register)
|
|
registrationToken String @unique @map("registration_token")
|
|
|
|
// Hub API key (issued after successful registration, used for heartbeats/commands)
|
|
hubApiKey String? @unique @map("hub_api_key")
|
|
|
|
// Orchestrator connection info (provided during registration)
|
|
orchestratorUrl String? @map("orchestrator_url")
|
|
agentVersion String? @map("agent_version")
|
|
|
|
// Status tracking
|
|
status ServerConnectionStatus @default(PENDING)
|
|
registeredAt DateTime? @map("registered_at")
|
|
lastHeartbeat DateTime? @map("last_heartbeat")
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
|
remoteCommands RemoteCommand[]
|
|
|
|
@@map("server_connections")
|
|
}
|
|
|
|
// ============================================================================
|
|
// REMOTE COMMANDS (Support Backdoor)
|
|
// ============================================================================
|
|
|
|
model RemoteCommand {
|
|
id String @id @default(cuid())
|
|
serverConnectionId String @map("server_connection_id")
|
|
|
|
// Command details
|
|
type String // SHELL, RESTART_SERVICE, UPDATE, ECHO, etc.
|
|
payload Json // Command-specific payload
|
|
|
|
// Execution tracking
|
|
status CommandStatus @default(PENDING)
|
|
result Json? // Command result
|
|
errorMessage String? @map("error_message")
|
|
|
|
// Timestamps
|
|
queuedAt DateTime @default(now()) @map("queued_at")
|
|
sentAt DateTime? @map("sent_at")
|
|
executedAt DateTime? @map("executed_at")
|
|
completedAt DateTime? @map("completed_at")
|
|
|
|
// Staff who initiated (for audit)
|
|
initiatedBy String? @map("initiated_by")
|
|
|
|
serverConnection ServerConnection @relation(fields: [serverConnectionId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([serverConnectionId, status])
|
|
@@index([status, queuedAt])
|
|
@@map("remote_commands")
|
|
}
|
|
|
|
// ============================================================================
|
|
// ENTERPRISE CLIENTS
|
|
// ============================================================================
|
|
|
|
enum ErrorSeverity {
|
|
INFO
|
|
WARNING
|
|
ERROR
|
|
CRITICAL
|
|
}
|
|
|
|
model EnterpriseClient {
|
|
id String @id @default(cuid())
|
|
name String
|
|
companyName String? @map("company_name")
|
|
contactEmail String @map("contact_email") // For security codes
|
|
contactPhone String? @map("contact_phone")
|
|
notes String? @db.Text
|
|
isActive Boolean @default(true) @map("is_active")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
// Relations
|
|
servers EnterpriseServer[]
|
|
errorRules ErrorDetectionRule[]
|
|
securityCodes SecurityVerificationCode[]
|
|
statsHistory ServerStatsSnapshot[]
|
|
notificationSetting NotificationSetting?
|
|
|
|
@@map("enterprise_clients")
|
|
}
|
|
|
|
model EnterpriseServer {
|
|
id String @id @default(cuid())
|
|
clientId String @map("client_id")
|
|
netcupServerId String @map("netcup_server_id") // Link to Netcup server
|
|
nickname String? // Optional friendly name
|
|
purpose String? // e.g., "Production", "Staging", "Database"
|
|
isActive Boolean @default(true) @map("is_active")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
// Portainer credentials (encrypted)
|
|
portainerUrl String? @map("portainer_url")
|
|
portainerUsername String? @map("portainer_username")
|
|
portainerPasswordEnc String? @map("portainer_password_enc")
|
|
|
|
// Relations
|
|
client EnterpriseClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
statsSnapshots ServerStatsSnapshot[]
|
|
errorLogs DetectedError[]
|
|
logScanPositions LogScanPosition[]
|
|
stateSnapshots ContainerStateSnapshot[]
|
|
containerEvents ContainerEvent[]
|
|
|
|
@@unique([clientId, netcupServerId])
|
|
@@index([clientId])
|
|
@@map("enterprise_servers")
|
|
}
|
|
|
|
// ============================================================================
|
|
// ENTERPRISE STATS HISTORY (90-day retention)
|
|
// ============================================================================
|
|
|
|
model ServerStatsSnapshot {
|
|
id String @id @default(cuid())
|
|
serverId String @map("server_id")
|
|
clientId String @map("client_id")
|
|
timestamp DateTime @default(now())
|
|
|
|
// Server metrics (from Netcup)
|
|
cpuPercent Float? @map("cpu_percent")
|
|
memoryUsedMb Float? @map("memory_used_mb")
|
|
memoryTotalMb Float? @map("memory_total_mb")
|
|
diskReadMbps Float? @map("disk_read_mbps")
|
|
diskWriteMbps Float? @map("disk_write_mbps")
|
|
networkInMbps Float? @map("network_in_mbps")
|
|
networkOutMbps Float? @map("network_out_mbps")
|
|
|
|
// Container summary
|
|
containersRunning Int? @map("containers_running")
|
|
containersStopped Int? @map("containers_stopped")
|
|
|
|
// Relations
|
|
server EnterpriseServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
|
client EnterpriseClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([serverId, timestamp])
|
|
@@index([clientId, timestamp])
|
|
@@map("server_stats_snapshots")
|
|
}
|
|
|
|
// ============================================================================
|
|
// ERROR DETECTION
|
|
// ============================================================================
|
|
|
|
model ErrorDetectionRule {
|
|
id String @id @default(cuid())
|
|
clientId String @map("client_id")
|
|
name String // e.g., "Database Connection Failed"
|
|
pattern String // Regex pattern
|
|
severity ErrorSeverity @default(WARNING)
|
|
isActive Boolean @default(true) @map("is_active")
|
|
description String? // What this rule detects
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
// Relations
|
|
client EnterpriseClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
detectedErrors DetectedError[]
|
|
|
|
@@index([clientId])
|
|
@@map("error_detection_rules")
|
|
}
|
|
|
|
model DetectedError {
|
|
id String @id @default(cuid())
|
|
serverId String @map("server_id")
|
|
ruleId String @map("rule_id")
|
|
containerId String? @map("container_id") // Optional: which container
|
|
containerName String? @map("container_name")
|
|
logLine String @db.Text @map("log_line") // The actual log line that matched
|
|
context String? @db.Text // Surrounding log context
|
|
timestamp DateTime @default(now())
|
|
acknowledgedAt DateTime? @map("acknowledged_at")
|
|
acknowledgedBy String? @map("acknowledged_by") // User ID who acknowledged
|
|
|
|
// Relations
|
|
server EnterpriseServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
|
rule ErrorDetectionRule @relation(fields: [ruleId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([serverId, timestamp])
|
|
@@index([ruleId, timestamp])
|
|
@@map("detected_errors")
|
|
}
|
|
|
|
// ============================================================================
|
|
// SECURITY VERIFICATION (for destructive actions)
|
|
// ============================================================================
|
|
|
|
model SecurityVerificationCode {
|
|
id String @id @default(cuid())
|
|
clientId String @map("client_id")
|
|
code String // 6-digit code
|
|
action String // "WIPE" | "REINSTALL"
|
|
targetServerId String @map("target_server_id") // Which server
|
|
expiresAt DateTime @map("expires_at")
|
|
usedAt DateTime? @map("used_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
// Relations
|
|
client EnterpriseClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clientId, code])
|
|
@@map("security_verification_codes")
|
|
}
|
|
|
|
// ============================================================================
|
|
// INTELLIGENT ERROR TRACKING
|
|
// ============================================================================
|
|
|
|
enum ContainerEventType {
|
|
CRASH // Was running, now exited with non-zero exit code
|
|
OOM_KILLED // Out of memory kill
|
|
RESTART // Container restarted
|
|
STOPPED // Intentional stop (exit code 0 or manual)
|
|
}
|
|
|
|
// Track log scanning position to avoid re-scanning same content
|
|
model LogScanPosition {
|
|
id String @id @default(cuid())
|
|
serverId String @map("server_id")
|
|
containerId String @map("container_id")
|
|
lastLineCount Int @default(0) @map("last_line_count")
|
|
lastLogHash String? @map("last_log_hash") // Detect log rotation
|
|
lastScannedAt DateTime @default(now()) @map("last_scanned_at")
|
|
|
|
server EnterpriseServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([serverId, containerId])
|
|
@@map("log_scan_positions")
|
|
}
|
|
|
|
// Track container state over time for crash detection
|
|
model ContainerStateSnapshot {
|
|
id String @id @default(cuid())
|
|
serverId String @map("server_id")
|
|
containerId String @map("container_id")
|
|
containerName String @map("container_name")
|
|
state String // "running", "exited", "dead"
|
|
exitCode Int? @map("exit_code")
|
|
capturedAt DateTime @default(now()) @map("captured_at")
|
|
|
|
server EnterpriseServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([serverId, containerId, capturedAt])
|
|
@@map("container_state_snapshots")
|
|
}
|
|
|
|
// Record significant container lifecycle events
|
|
model ContainerEvent {
|
|
id String @id @default(cuid())
|
|
serverId String @map("server_id")
|
|
containerId String @map("container_id")
|
|
containerName String @map("container_name")
|
|
eventType ContainerEventType @map("event_type")
|
|
exitCode Int? @map("exit_code")
|
|
details String? @db.Text
|
|
acknowledgedAt DateTime? @map("acknowledged_at")
|
|
acknowledgedBy String? @map("acknowledged_by")
|
|
timestamp DateTime @default(now())
|
|
|
|
server EnterpriseServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([serverId, timestamp])
|
|
@@index([eventType, acknowledgedAt])
|
|
@@map("container_events")
|
|
}
|
|
|
|
// Email notification settings per client
|
|
model NotificationSetting {
|
|
id String @id @default(cuid())
|
|
clientId String @unique @map("client_id")
|
|
enabled Boolean @default(false)
|
|
criticalErrorsOnly Boolean @default(true) @map("critical_errors_only")
|
|
containerCrashes Boolean @default(true) @map("container_crashes")
|
|
recipients String[] @default([])
|
|
cooldownMinutes Int @default(30) @map("cooldown_minutes")
|
|
lastNotifiedAt DateTime? @map("last_notified_at")
|
|
|
|
client EnterpriseClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("notification_settings")
|
|
}
|
|
|
|
// ============================================================================
|
|
// SYSTEM-WIDE NOTIFICATION COOLDOWN
|
|
// ============================================================================
|
|
|
|
// Track last notification time per notification type for system-wide cooldown
|
|
model NotificationCooldown {
|
|
id String @id @default(cuid())
|
|
type String @unique // e.g., "container_crash", "critical_error", "stats_cpu"
|
|
lastSentAt DateTime @map("last_sent_at")
|
|
|
|
@@map("notification_cooldowns")
|
|
}
|