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
+74
View File
@@ -0,0 +1,74 @@
/**
* 纯权限 reducer(契约 P6 / D11 / D8)。
*
* 设计约束(Metis 评审):本文件是纯函数层 —— 输入是"已解析好的" grant、祖先链
* 与用户组集合,不碰 DB / 网络。数据获取在 treeService。这样权限代数可以脱离
* 存储做密集单测与随机化不变量测试。
*/
import type { FileLibRole } from "./model.js";
import { ROLE_RANK } from "./model.js";
export interface FileLibGrantFact {
readonly nodeId: string;
readonly principalType: "USER" | "GROUP";
readonly principalId: string;
readonly role: FileLibRole;
readonly isCreatorGrant: boolean;
}
export interface EffectiveRoleInput {
/** 目标节点(self)。 */
readonly nodeId: string;
readonly nodeKind: "FOLDER" | "PROJECT";
/** 目标的全部祖先 id(不含 self,顺序无关)。 */
readonly ancestorIds: readonly string[];
/** 项目独立权限开关(D11/P5);文件夹忽略此值。 */
readonly independentPermissionsEnabled: boolean;
readonly userId: string;
/** C2 resolve 结果:用户直接所属 + 全部祖先 group 的 id 集合。 */
readonly groupIds: readonly string[];
/** self ancestors 上的全部活跃 grant(revokedAt 已由获取层过滤)。 */
readonly grants: readonly FileLibGrantFact[];
}
/**
* 契约 P6:effective(user, R) = max { grant.role | s ∈ {user} groups*(user),
* r ∈ {R} ancestors(R) };无匹配 → null(无任何权限)。
* "个人权限不能降权"在 max 语义下天然成立 —— 只取最高,不做减法。
*
* D11:目标为 PROJECT 且独立权限关闭时,项目级(挂在 self 上)非创建者 grant
* 冻结不参与计算;创建者的自动 grant(isCreatorGrant)始终生效。祖先链上的
* grant 不受开关影响。
*/
export function effectiveRole(input: EffectiveRoleInput): FileLibRole | null {
const onChain = new Set<string>([input.nodeId, ...input.ancestorIds]);
const groups = new Set(input.groupIds);
const freezeProjectGrants =
input.nodeKind === "PROJECT" && !input.independentPermissionsEnabled;
let best: FileLibRole | null = null;
for (const grant of input.grants) {
if (!onChain.has(grant.nodeId)) continue;
if (freezeProjectGrants && grant.nodeId === input.nodeId && !grant.isCreatorGrant) continue;
if (grant.principalType === "USER" && grant.principalId !== input.userId) continue;
if (grant.principalType === "GROUP" && !groups.has(grant.principalId)) continue;
if (best === null || ROLE_RANK[grant.role] > ROLE_RANK[best]) best = grant.role;
}
return best;
}
/**
* D8 可见性语义:
* - 完全无权限(effective === null)→ "not_found"(路由层映射 404,不泄露存在性);
* - 有权限但不足 → "forbidden"(路由层映射 403)。
*/
export type AccessVerdict =
| { readonly allowed: true; readonly role: FileLibRole }
| { readonly allowed: false; readonly reason: "not_found" | "forbidden" };
export function checkAccess(effective: FileLibRole | null, min: FileLibRole): AccessVerdict {
if (effective === null) return { allowed: false, reason: "not_found" };
if (ROLE_RANK[effective] < ROLE_RANK[min]) return { allowed: false, reason: "forbidden" };
return { allowed: true, role: effective };
}