feat(database): init database folder frontend and permission

This commit is contained in:
ymy
2026-07-23 23:41:11 +08:00
parent 5df1900ca8
commit 4021e58d5d
67 changed files with 9436 additions and 150 deletions
@@ -0,0 +1,92 @@
-- File library (文件库) — 语义锚定:仓库根《文件库-接口契约.md》、.omo/文件库-开工计划.md (D11D19)。
-- 本地无 PG 时手写;有 PG 后可用 `prisma migrate diff` 核对与 schema 的一致性。
-- CreateEnum
CREATE TYPE "FileLibNodeKind" AS ENUM ('FOLDER', 'PROJECT');
CREATE TYPE "FileLibProvisionStatus" AS ENUM ('PROVISIONING', 'READY', 'FAILED');
CREATE TYPE "FileLibRole" AS ENUM ('VIEW', 'EDIT', 'MANAGE');
CREATE TYPE "FileLibPrincipalType" AS ENUM ('USER', 'GROUP');
CREATE TYPE "FileLibExportStatus" AS ENUM ('QUEUED', 'RUNNING', 'DONE', 'FAILED');
-- CreateTable
CREATE TABLE "FileLibNode" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"parentId" TEXT,
"kind" "FileLibNodeKind" NOT NULL,
"name" TEXT NOT NULL,
"nameLower" TEXT NOT NULL,
"pathIds" TEXT NOT NULL,
"creatorId" TEXT NOT NULL,
"provisionStatus" "FileLibProvisionStatus" NOT NULL DEFAULT 'READY',
"storageDir" TEXT,
"deletedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "FileLibNode_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "FileLibGrant" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"nodeId" TEXT NOT NULL,
"principalType" "FileLibPrincipalType" NOT NULL,
"principalId" TEXT NOT NULL,
"role" "FileLibRole" NOT NULL,
"isCreatorGrant" BOOLEAN NOT NULL DEFAULT false,
"createdByUserId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"revokedAt" TIMESTAMP(3),
CONSTRAINT "FileLibGrant_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "FileLibProjectSettings" (
"nodeId" TEXT NOT NULL,
"independentPermissionsEnabled" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "FileLibProjectSettings_pkey" PRIMARY KEY ("nodeId")
);
CREATE TABLE "FileLibExportJob" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"nodeId" TEXT NOT NULL,
"target" TEXT NOT NULL,
"params" JSONB NOT NULL,
"status" "FileLibExportStatus" NOT NULL DEFAULT 'QUEUED',
"downloadUrl" TEXT,
"error" TEXT,
"createdByUserId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "FileLibExportJob_pkey" PRIMARY KEY ("id")
);
-- CreateIndex (schema-declared)
CREATE INDEX "FileLibNode_organizationId_parentId_deletedAt_idx" ON "FileLibNode"("organizationId", "parentId", "deletedAt");
CREATE INDEX "FileLibNode_organizationId_pathIds_idx" ON "FileLibNode"("organizationId", "pathIds");
CREATE INDEX "FileLibNode_creatorId_idx" ON "FileLibNode"("creatorId");
CREATE INDEX "FileLibGrant_organizationId_revokedAt_idx" ON "FileLibGrant"("organizationId", "revokedAt");
CREATE INDEX "FileLibGrant_principalType_principalId_revokedAt_idx" ON "FileLibGrant"("principalType", "principalId", "revokedAt");
CREATE INDEX "FileLibGrant_nodeId_revokedAt_idx" ON "FileLibGrant"("nodeId", "revokedAt");
CREATE INDEX "FileLibExportJob_organizationId_status_idx" ON "FileLibExportJob"("organizationId", "status");
-- D14:活跃兄弟节点大小写不敏感唯一。root 的 parentId 为 NULL,用 COALESCE 归入同一键空间。
CREATE UNIQUE INDEX "FileLibNode_active_sibling_name_key"
ON "FileLibNode"("organizationId", COALESCE("parentId", ''), "nameLower")
WHERE "deletedAt" IS NULL;
-- 契约 2.3:同一节点上同一 principal 至多一条活跃授权(Postgres 原生 UNIQUE 无法约束 NULL revokedAt)。
CREATE UNIQUE INDEX "FileLibGrant_active_unique"
ON "FileLibGrant"("nodeId", "principalType", "principalId")
WHERE "revokedAt" IS NULL;
-- AddForeignKey
ALTER TABLE "FileLibNode" ADD CONSTRAINT "FileLibNode_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "FileLibNode" ADD CONSTRAINT "FileLibNode_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "FileLibNode"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "FileLibGrant" ADD CONSTRAINT "FileLibGrant_nodeId_fkey" FOREIGN KEY ("nodeId") REFERENCES "FileLibNode"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "FileLibProjectSettings" ADD CONSTRAINT "FileLibProjectSettings_nodeId_fkey" FOREIGN KEY ("nodeId") REFERENCES "FileLibNode"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "FileLibExportJob" ADD CONSTRAINT "FileLibExportJob_nodeId_fkey" FOREIGN KEY ("nodeId") REFERENCES "FileLibNode"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- 为 FileLibNode 加简介字段,老师在创建/概览页填写。
ALTER TABLE "FileLibNode" ADD COLUMN "description" TEXT;
+125
View File
@@ -50,6 +50,7 @@ model Organization {
projectGroupBindings ProjectGroupBinding[]
auditEntries AuditEntry[] @relation("organizationAudit")
projectSearchDocuments ProjectSearchDocument[]
fileLibNodes FileLibNode[]
@@index([status])
}
@@ -923,3 +924,127 @@ model CapabilityCredentialVersion {
@@index([keyId])
@@index([createdByUserId])
}
// --- File library (文件库) -------------------------------------------------
//
// 独立模块,语义由仓库根《文件库-接口契约.md》(C/D 编号)与 .omo/文件库-开工计划.md
// (D11D19)锚定。与上面的 Folder/Project(hub 自己的 explorer,ADR-0021)是两套
// 体系,不复用、不互相引用。
/// 文件库目录树节点:文件夹(容器)或项目(叶子,关联 git 仓库)。
/// parentId 是树的权威关系;pathIds 是 id 编码的物化路径(派生),随 create/move
/// 在事务内维护(计划 D12;name 不入路径,rename 不重写后代)。
model FileLibNode {
id String @id
organizationId String
parentId String?
kind FileLibNodeKind
name String
/// D14:NFC+trim 的小写形式;活跃兄弟节点大小写不敏感唯一(部分唯一索引在迁移 SQL)。
nameLower String
/// id 编码物化路径,形如 "/rootId/childId/selfId"。祖先展开与前缀查询都用它。
pathIds String
/// D11:创建者不可变,自动持有 isCreatorGrant=true 的 MANAGE grant。
/// 故意不建 FK:这是不可变历史事实,不随 User 生命周期变化。
creatorId String
/// 老师可填写的简介,创建/概览页展示,通俗易懂地说明这个节点的用途。
description String?
/// 项目 provisioning 状态机(DB/git 双写协调,Metis 风险#1);文件夹恒 READY。
provisionStatus FileLibProvisionStatus @default(READY)
/// 项目仓库目录(绝对路径);文件夹为 null。
storageDir String?
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
parent FileLibNode? @relation("fileLibTree", fields: [parentId], references: [id], onDelete: Restrict)
children FileLibNode[] @relation("fileLibTree")
grants FileLibGrant[]
projectSettings FileLibProjectSettings?
exportJobs FileLibExportJob[]
@@index([organizationId, parentId, deletedAt])
@@index([organizationId, pathIds])
@@index([creatorId])
}
enum FileLibNodeKind {
FOLDER
PROJECT
}
enum FileLibProvisionStatus {
PROVISIONING
READY
FAILED
}
/// 文件库权限级别:MANAGE > EDIT > VIEW(契约 2.2,只取最高、无降权)。
enum FileLibRole {
VIEW
EDIT
MANAGE
}
enum FileLibPrincipalType {
USER
GROUP
}
/// 契约 2.3:grant 直接挂在节点上,最终权限 = max(个人, 递归 Group, 祖先继承)。
/// 活跃授权唯一性由迁移里的部分唯一索引保证(revokedAt IS NULL)。
model FileLibGrant {
id String @id @default(cuid())
organizationId String
nodeId String
principalType FileLibPrincipalType
principalId String
role FileLibRole
/// D11:创建者自动 grant;独立权限开关关闭时,项目级 grant 里只有它仍生效。
isCreatorGrant Boolean @default(false)
createdByUserId String?
createdAt DateTime @default(now())
revokedAt DateTime?
node FileLibNode @relation(fields: [nodeId], references: [id], onDelete: Cascade)
@@index([organizationId, revokedAt])
@@index([principalType, principalId, revokedAt])
@@index([nodeId, revokedAt])
}
/// 契约 P5/D11:项目独立权限开关。默认关闭(仅继承);关闭时项目级非创建者
/// grant 冻结不删除,重新开启即恢复。
model FileLibProjectSettings {
nodeId String @id
independentPermissionsEnabled Boolean @default(false)
node FileLibNode @relation(fields: [nodeId], references: [id], onDelete: Cascade)
}
enum FileLibExportStatus {
QUEUED
RUNNING
DONE
FAILED
}
/// 契约 D10:导出为异步任务。外部导出工具参数 OPEN-6,adapter 就位前先建模型。
model FileLibExportJob {
id String @id @default(cuid())
organizationId String
nodeId String
target String
params Json
status FileLibExportStatus @default(QUEUED)
downloadUrl String?
error String?
createdByUserId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
node FileLibNode @relation(fields: [nodeId], references: [id], onDelete: Cascade)
@@index([organizationId, status])
}