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
+89
View File
@@ -0,0 +1,89 @@
/**
* /database/api/* 路由共享件:依赖装配、actor 门禁、统一错误映射。
* 约定(与 admin 面一致):绝对路径;guard 前置 fail closed;查询 scope 到 silo org。
*/
import type { FastifyReply, FastifyRequest } from "fastify";
import { Prisma } from "@prisma/client";
import type { PrismaClient } from "@prisma/client";
import { FileLibError } from "./model.js";
import { requireFileLibActor } from "./guards.js";
import type { FileLibActor, TreeServiceDeps } from "./treeService.js";
import type { GroupResolver } from "./groupResolver.js";
import type { VersionStore } from "./versionStore.js";
import type { ExportAdapter } from "./exportService.js";
export interface FileLibRouteDeps {
readonly prisma: PrismaClient;
readonly sessionSecret: string;
readonly organizationId: string;
readonly storageRoot: string;
readonly groupResolver: GroupResolver;
readonly versionStore: VersionStore;
readonly exportAdapters: readonly ExportAdapter[];
}
/** 组装 treeService 依赖(路由处理内直接使用)。 */
export function treeDeps(deps: FileLibRouteDeps): TreeServiceDeps {
return {
prisma: deps.prisma,
groupResolver: deps.groupResolver,
versionStore: deps.versionStore,
organizationId: deps.organizationId,
storageRoot: deps.storageRoot,
};
}
/** 端点第一行调用;null = 响应已发(401/403),fail closed。 */
export async function actorOrNull(
request: FastifyRequest,
reply: FastifyReply,
deps: FileLibRouteDeps,
): Promise<FileLibActor | null> {
return requireFileLibActor(request, reply, {
prisma: deps.prisma,
sessionSecret: deps.sessionSecret,
organizationId: deps.organizationId,
});
}
/** 统一错误出口:FileLibError → 语义码;P2002 → 409;其余 → 500(不泄露内部)。 */
export async function sendRouteError(reply: FastifyReply, error: unknown): Promise<void> {
if (error instanceof FileLibError) {
await reply.status(error.statusCode).send({
error: { code: error.code, message: error.message, ...(error.details ?? {}) },
});
return;
}
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
await reply.status(409).send({ error: { code: "conflict", message: "uniqueness conflict" } });
return;
}
reply.log.error({ err: error }, "filelib route: unexpected error");
await reply.status(500).send({ error: { code: "internal", message: "internal error" } });
}
/** 请求体轻量校验(抛 FileLibError 400)。 */
export function bodyObject(body: unknown): Record<string, unknown> {
if (typeof body !== "object" || body === null || Array.isArray(body)) {
throw new FileLibError(400, "invalid_request", "request body must be a JSON object");
}
return body as Record<string, unknown>;
}
export function requireString(obj: Record<string, unknown>, key: string): string {
const value = obj[key];
if (typeof value !== "string" || value === "") {
throw new FileLibError(400, "invalid_request", `missing or invalid field: ${key}`);
}
return value;
}
export function optionalString(obj: Record<string, unknown>, key: string): string | undefined {
const value = obj[key];
if (value === undefined || value === null) return undefined;
if (typeof value !== "string") {
throw new FileLibError(400, "invalid_request", `invalid field: ${key}`);
}
return value;
}