forked from EduCraft/curriculum-project-hub
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
/**
|
|
* 文件库审计 sink(契约 C3 的入驻适配)。
|
|
*
|
|
* 契约原文:本地 outbox 表(与业务同事务)→ 中继 POST 到独立审计服务。
|
|
* 入驻 hub 后的适配:审计同事 = 本库 AuditEntry,与业务写在同一 Prisma 事务
|
|
* 内落库 —— 同库同事务天然满足"操作成功则日志必存在",比 outbox+relay 更强。
|
|
* 若审计团队日后独立成服务,只换本文件的实现,action 词汇表保持不变。
|
|
*/
|
|
|
|
import type { Prisma } from "@prisma/client";
|
|
|
|
/** C3 §6.3:文件库审计动作词汇表(与契约文档逐条对应,改词需升契约版本)。 */
|
|
export const FILE_LIB_AUDIT_ACTIONS = {
|
|
folderCreate: "folder.create",
|
|
folderRename: "folder.rename",
|
|
folderMove: "folder.move",
|
|
folderDelete: "folder.delete",
|
|
projectCreate: "project.create",
|
|
projectRename: "project.rename",
|
|
projectMove: "project.move",
|
|
projectDelete: "project.delete",
|
|
// ADR-0031:回收站。restore 与 delete 对称(都只动本节点);purge 是整支硬删。
|
|
folderRestore: "folder.restore",
|
|
projectRestore: "project.restore",
|
|
nodePurge: "node.purge",
|
|
permissionGrant: "permission.grant",
|
|
permissionUpdate: "permission.update",
|
|
permissionRevoke: "permission.revoke",
|
|
independentEnable: "project.independent_permission.enable",
|
|
independentDisable: "project.independent_permission.disable",
|
|
independentChange: "project.independent_permission.change",
|
|
fileUpload: "file.upload",
|
|
fileRename: "file.rename",
|
|
fileDelete: "file.delete",
|
|
fileCommit: "file.commit",
|
|
fileConflictDetected: "file.conflict_detected",
|
|
exportRun: "export.run",
|
|
adminForceAdjust: "admin.force_adjust",
|
|
// ADR-0038:成员组内置进 hub,组动作在本地审计(契约 C3 §6.3 原委托外部 Group 服务)。
|
|
groupCreate: "group.create",
|
|
groupUpdate: "group.update",
|
|
groupDelete: "group.delete",
|
|
groupRestore: "group.restore",
|
|
groupMemberAdd: "group.member_add",
|
|
groupMemberRemove: "group.member_remove",
|
|
} as const;
|
|
|
|
export type FileLibAuditObjectType = "folder" | "project" | "file" | "grant" | "export_job" | "group";
|
|
|
|
export interface FileLibAuditEntry {
|
|
readonly action: string;
|
|
readonly actorUserId: string;
|
|
readonly organizationId: string;
|
|
readonly objectType: FileLibAuditObjectType;
|
|
readonly objectId: string;
|
|
/** 节点 id 路径(pathIds)或项目内文件路径,便于按路径检索。 */
|
|
readonly objectPath: string;
|
|
readonly detail?: Record<string, unknown> | undefined;
|
|
}
|
|
|
|
/**
|
|
* 在调用方的事务里写一条审计。刻意不吞错:写不出来整个业务操作回滚
|
|
* (需求 5.1"操作成功则日志必存在"的强保证)。
|
|
*/
|
|
export async function writeFileLibAudit(
|
|
tx: Prisma.TransactionClient,
|
|
entry: FileLibAuditEntry,
|
|
): Promise<void> {
|
|
const metadata: Record<string, unknown> = {
|
|
objectType: entry.objectType,
|
|
objectId: entry.objectId,
|
|
objectPath: entry.objectPath,
|
|
...(entry.detail ?? {}),
|
|
};
|
|
await tx.auditEntry.create({
|
|
data: {
|
|
action: entry.action,
|
|
actorUserId: entry.actorUserId,
|
|
organizationId: entry.organizationId,
|
|
metadata: metadata as Prisma.InputJsonValue,
|
|
},
|
|
});
|
|
}
|