// DevOS v1 — PostgreSQL + Prisma schema // 设计原则(04-DATABASE-SCHEMA.md): // - resources 表承担统一身份/发现/图谱公共字段; // - 具备大量运行状态的资源使用类型化领域表(servers/websites/...),通过 resourceId 与 resources 一对一关联; // - 资源关系独立成表(resource_relations),图谱可演进而非堆在一张大表; // - 凭据密文独立存储,绝不与普通元数据混放。 generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ============ 枚举 ============ enum ResourceType { SERVER DOMAIN WEBSITE REPOSITORY CERTIFICATE DATABASE CONTAINER EMAIL_ACCOUNT PROJECT TASK NOTE DOCUMENT BACKUP DEPLOYMENT MONITOR CREDENTIAL AUTOMATION AI_RUN } enum ResourceStatus { PENDING ACTIVE ERROR OFFLINE ARCHIVED UNKNOWN } enum WorkspaceRole { OWNER ADMIN MEMBER VIEWER } enum RiskLevel { LOW MEDIUM HIGH CRITICAL } enum ActorType { USER SYSTEM AGENT MCP } enum JobStatus { PENDING RUNNING SUCCEEDED FAILED CANCELLED DEAD } enum DeploymentStatus { QUEUED RUNNING SUCCEEDED FAILED CANCELLED } enum CredentialType { SSH_PASSWORD SSH_KEY API_TOKEN DNS_PROVIDER GIT_TOKEN } enum ServerAuthType { PASSWORD SSH_KEY } enum TriggerType { MANUAL CRON EVENT WEBHOOK } enum AiRunStatus { PLANNING AWAITING_APPROVAL RUNNING SUCCEEDED FAILED REJECTED } // ============ 身份与工作区 ============ /// # 数据库表 users:平台用户 model User { /// # 数据库字段 邮箱(登录名,唯一) id String @id @default(uuid()) email String @unique /// # 数据库字段 密码哈希(argon2id/scrypt,绝不明文) passwordHash String name String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt memberships WorkspaceMember[] @@map("users") } /// # 数据库表 workspaces:租户/安全边界 model Workspace { id String @id @default(uuid()) name String slug String @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt members WorkspaceMember[] resources Resource[] @@map("workspaces") } /// # 数据库表 workspace_members:用户-工作区关系与角色 model WorkspaceMember { id String @id @default(uuid()) workspaceId String userId String role WorkspaceRole @default(MEMBER) createdAt DateTime @default(now()) workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([workspaceId, userId]) @@index([userId]) @@map("workspace_members") } // ============ 统一资源模型 ============ /// # 数据库表 resources:统一资源身份/发现/图谱公共字段 model Resource { id String @id @default(uuid()) workspaceId String type ResourceType name String provider String? status ResourceStatus @default(PENDING) /// # 数据库字段 扩展元数据(敏感信息禁止写入) metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) outgoingRelations ResourceRelation[] @relation("source") incomingRelations ResourceRelation[] @relation("target") credential Credential? server Server? container Container? website Website? domain Domain? deployment Deployment? monitor Monitor? automation Automation? aiRun AiRun? gitConnection GitConnection? repository Repository? task Task? note Note? certificate Certificate? @@index([workspaceId]) @@index([type, status]) @@index([workspaceId, type]) @@map("resources") } /// # 数据库表 resource_relations:资源关系图(有向边) model ResourceRelation { id String @id @default(uuid()) workspaceId String sourceId String targetId String /// # 数据库字段 关系类型,如 deployed_on / has_domain / resolves_to / monitors relationType String metadata Json @default("{}") createdAt DateTime @default(now()) source Resource @relation("source", fields: [sourceId], references: [id], onDelete: Cascade) target Resource @relation("target", fields: [targetId], references: [id], onDelete: Cascade) @@unique([sourceId, targetId, relationType]) @@index([sourceId]) @@index([targetId]) @@index([workspaceId]) @@map("resource_relations") } // ============ 凭据保险库 ============ /// # 数据库表 credentials:加密凭据(信封加密后的密文,禁止记录明文) model Credential { id String @id @default(uuid()) workspaceId String resourceId String? @unique name String type CredentialType provider String? /// # 数据库字段 AES-256-GCM 密文 JSON(iv/tag/ciphertext,base64) encryptedData String metadata Json @default("{}") lastUsedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource? @relation(fields: [resourceId], references: [id], onDelete: SetNull) @@index([workspaceId]) @@map("credentials") } // ============ 审计 / 事件 / 任务 ============ /// # 数据库表 audit_logs:审计日志(每个变更必写) model AuditLog { id String @id @default(uuid()) workspaceId String actorType ActorType actorId String? action String resourceType String? resourceId String? riskLevel RiskLevel @default(LOW) status String @default("SUCCESS") requestId String? ip String? metadata Json @default("{}") createdAt DateTime @default(now()) @@index([workspaceId, createdAt]) @@index([resourceType, resourceId]) @@map("audit_logs") } /// # 数据库表 events:领域事件(持久化 + 分发) model EventRecord { id String @id @default(uuid()) workspaceId String eventType String resourceId String? payload Json @default("{}") correlationId String? createdAt DateTime @default(now()) @@index([eventType, createdAt]) @@index([workspaceId, createdAt]) @@map("events") } /// # 数据库表 jobs:异步任务(持久化状态机 + Redis 派发) model Job { id String @id @default(uuid()) workspaceId String type String status JobStatus @default(PENDING) payload Json @default("{}") result Json? error String? attempts Int @default(0) maxAttempts Int @default(3) runAt DateTime @default(now()) startedAt DateTime? finishedAt DateTime? correlationId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status, runAt]) @@index([workspaceId]) @@map("jobs") } // ============ 基础设施领域表 ============ /// # 数据库表 servers:受管计算目标(SSH) model Server { id String @id @default(uuid()) workspaceId String resourceId String @unique name String host String port Int @default(22) username String authType ServerAuthType @default(SSH_KEY) provider String @default("ssh") credentialId String? osInfo String? labels Json @default("{}") lastSeenAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) containers Container[] websites Website[] monitors Monitor[] @@index([workspaceId]) @@map("servers") } /// # 数据库表 containers:Docker 容器发现结果 model Container { id String @id @default(uuid()) workspaceId String resourceId String @unique serverId String containerId String name String image String state String ports Json @default("{}") labels Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) server Server @relation(fields: [serverId], references: [id], onDelete: Cascade) @@index([serverId]) @@index([workspaceId]) @@map("containers") } /// # 数据库表 websites:可部署应用资源 model Website { id String @id @default(uuid()) workspaceId String resourceId String @unique name String primaryDomain String? serverId String? repositoryUrl String? /// # 数据库字段 关联仓库(Git Provider 凭据克隆;优先于 repositoryUrl) repositoryId String? /// # 数据库字段 部署环境:development / staging / production environment String @default("production") /// # 数据库字段 部署类型:DOCKER / STATIC / NODE deployType String @default("DOCKER") /// # 数据库字段 容器对外端口 port Int @default(80) healthPath String @default("/") env Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) server Server? @relation(fields: [serverId], references: [id], onDelete: SetNull) deployments Deployment[] monitors Monitor[] repository Repository? @relation(fields: [repositoryId], references: [id]) @@index([workspaceId]) @@map("websites") } /// # 数据库表 domains:域名与 DNS 提供商绑定 model Domain { id String @id @default(uuid()) workspaceId String resourceId String @unique name String provider String @default("MANUAL") credentialId String? expiresAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) records DomainRecord[] @@index([workspaceId]) @@map("domains") } /// # 数据库表 domain_records:域名记录(手动 provider 时为本地事实源) model DomainRecord { id String @id @default(uuid()) workspaceId String domainId String type String name String value String ttl Int @default(300) proxied Boolean @default(false) providerRecordId String? updatedAt DateTime @updatedAt createdAt DateTime @default(now()) domain Domain @relation(fields: [domainId], references: [id], onDelete: Cascade) @@unique([domainId, type, name, value]) @@index([workspaceId]) @@map("domain_records") } /// # 数据库表 deployments:部署执行记录 model Deployment { id String @id @default(uuid()) workspaceId String resourceId String @unique websiteId String status DeploymentStatus @default(QUEUED) trigger String @default("MANUAL") progress Int @default(0) /// # 数据库字段 失败原因(截断存储,不含敏感信息) error String? commit String? /// # 数据库字段 部署环境:development / staging / production environment String? /// # 数据库字段 构建策略:DOCKERFILE / STATIC_COPY buildStrategy String? /// # 数据库字段 部署策略:DOCKER_CONTAINER / STATIC_DIR deployStrategy String? /// # 数据库字段 健康验证证据 {ok,status,latencyMs,checkedAt} verification Json? /// # 数据库字段 部署参数快照(分支、镜像 tag 等) config Json @default("{}") createdAt DateTime @default(now()) finishedAt DateTime? resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) website Website @relation(fields: [websiteId], references: [id], onDelete: Cascade) logs DeploymentLog[] @@index([websiteId, createdAt]) @@index([workspaceId, createdAt]) @@map("deployments") } /// # 数据库表 deployment_logs:部署日志行(顺序) model DeploymentLog { id String @id @default(uuid()) deploymentId String seq Int stream String @default("stdout") line String createdAt DateTime @default(now()) deployment Deployment @relation(fields: [deploymentId], references: [id], onDelete: Cascade) @@index([deploymentId, seq]) @@map("deployment_logs") } /// # 数据库表 monitors:健康检查配置 model Monitor { id String @id @default(uuid()) workspaceId String resourceId String @unique websiteId String? serverId String? name String /// # 数据库字段 检查类型:HTTP / TCP kind String @default("HTTP") target String intervalSec Int @default(60) timeoutMs Int @default(10000) enabled Boolean @default(true) lastStatus String @default("UNKNOWN") consecutiveFailures Int @default(0) lastCheckedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) website Website? @relation(fields: [websiteId], references: [id], onDelete: Cascade) server Server? @relation(fields: [serverId], references: [id], onDelete: Cascade) results MonitorResult[] @@index([enabled, lastCheckedAt]) @@index([workspaceId]) @@map("monitors") } /// # 数据库表 monitor_results:监控结果历史 model MonitorResult { id String @id @default(uuid()) monitorId String ok Boolean status String? latencyMs Int? error String? createdAt DateTime @default(now()) monitor Monitor @relation(fields: [monitorId], references: [id], onDelete: Cascade) @@index([monitorId, createdAt]) @@map("monitor_results") } // ============ 自动化 ============ /// # 数据库表 automations:Trigger → Condition → Action 工作流 model Automation { id String @id @default(uuid()) workspaceId String resourceId String @unique name String enabled Boolean @default(false) triggerType TriggerType /// # 数据库字段 触发器配置(cron 表达式 / 事件名 / webhook token) triggerConfig Json @default("{}") /// # 数据库字段 条件列表 [{path,op,value}] conditions Json @default("[]") /// # 数据库字段 动作列表 [{type,args,timeoutMs,retries}] actions Json @default("[]") lastTriggeredAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) runs AutomationRun[] @@index([workspaceId]) @@index([triggerType, enabled]) @@map("automations") } /// # 数据库表 automation_runs:自动化执行历史 model AutomationRun { id String @id @default(uuid()) automationId String workspaceId String status String @default("RUNNING") trigger Json @default("{}") stepIndex Int @default(0) logs Json @default("[]") error String? correlationId String? startedAt DateTime @default(now()) finishedAt DateTime? automation Automation @relation(fields: [automationId], references: [id], onDelete: Cascade) @@index([automationId, startedAt]) @@map("automation_runs") } // ============ AI Gateway ============ /// # 数据库表 ai_runs:AI 运行(计划→风控→审批→执行→验证) model AiRun { id String @id @default(uuid()) workspaceId String resourceId String @unique objective String status AiRunStatus @default(PLANNING) /// # 数据库字段 来源:UI / CHAT / MCP / AUTOMATION source String @default("UI") riskLevel RiskLevel @default(LOW) /// # 数据库字段 计划步骤 [{tool,args,reason,risk}] plan Json @default("[]") /// # 数据库字段 执行历史 [{tool,args,result,ok}] toolCalls Json @default("[]") /// # 数据库字段 验证结果 {ok, checks[]} verification Json? result Json? error String? approvedBy String? approvedAt DateTime? correlationId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) @@index([workspaceId, createdAt]) @@map("ai_runs") } // ============ Phase 2:Git / 生产力 / 通知 / 证书 ============ /// # 数据库表 git_connections:Git Provider 连接(GitHub/GitLab/Gitea + 凭据引用) model GitConnection { id String @id @default(uuid()) workspaceId String resourceId String @unique name String /// # 数据库字段 提供商:GITHUB / GITLAB / GITEA provider String /// # 数据库字段 自建实例 API 基址(Gitea/GitLab 自托管时填写) apiBase String? credentialId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) repositories Repository[] @@index([workspaceId]) } /// # 数据库表 repositories:外部代码仓库聚合(不实现 Git 托管) model Repository { id String @id @default(uuid()) workspaceId String resourceId String @unique connectionId String? provider String @default("GENERIC") /// # 数据库字段 全名 owner/repo fullName String /// # 数据库字段 HTTPS 克隆地址(不含凭据) cloneUrl String defaultBranch String @default("main") /// # 数据库字段 private 标记 visibility String @default("private") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) connection GitConnection? @relation(fields: [connectionId], references: [id], onDelete: SetNull) websites Website[] @@unique([workspaceId, cloneUrl]) @@index([workspaceId]) } /// # 数据库表 tasks:工作项(backlog/todo/doing/review/done) model Task { id String @id @default(uuid()) workspaceId String resourceId String @unique title String description String @default("") /// # 数据库字段 状态:BACKLOG / TODO / DOING / REVIEW / DONE status String @default("TODO") /// # 数据库字段 优先级:LOW / MEDIUM / HIGH / URGENT priority String @default("MEDIUM") labels Json @default("[]") dueAt DateTime? /// # 数据库字段 关联项目资源 ID(可空) projectId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) @@index([workspaceId, status]) @@index([projectId]) } /// # 数据库表 notes:笔记/知识(markdown 存储) model Note { id String @id @default(uuid()) workspaceId String resourceId String @unique title String /// # 数据库字段 正文(HTML,TipTap 富文本) content String @default("") tags Json @default("[]") projectId String? /// # 数据库字段 置顶 pinned Boolean @default(false) /// # 数据库字段 文件夹(分组) folder String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) @@index([workspaceId]) } /// # 数据库表 notifications:站内通知(自动化/事件投递) model Notification { id String @id @default(uuid()) workspaceId String resourceId String? title String body String @default("") /// # 数据库字段 级别:INFO / WARN / ERROR level String @default("INFO") readAt DateTime? createdAt DateTime @default(now()) @@index([workspaceId, createdAt]) } /// # 数据库表 certificates:TLS 证书统一模型(探针检测) model Certificate { id String @id @default(uuid()) workspaceId String resourceId String @unique /// # 数据库字段 证书主域名 domain String domainId String? /// # 数据库字段 状态:PENDING / ACTIVE / EXPIRED / ERROR status String @default("PENDING") issuer String? notAfter DateTime? lastCheckedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) @@index([workspaceId]) }