forked from EduCraft/curriculum-project-hub
feat(database): init database folder frontend and permission
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user