feat: 完成 R13/R14/R15/R16 小团队管理核心能力
这个提交包含在:
@@ -0,0 +1,80 @@
|
||||
const { z } = require("zod");
|
||||
|
||||
const { AppError } = require("../utils/AppError");
|
||||
const { sendData } = require("../utils/reply");
|
||||
const { buildAuditLogData } = require("../utils/audit");
|
||||
const {
|
||||
DEFAULT_MODULE_VISIBILITY,
|
||||
getWorkspaceSetting,
|
||||
getSystemNavigation,
|
||||
normalizeModuleVisibilityJson,
|
||||
upsertWorkspaceSetting,
|
||||
} = require("../services/workspaceSettings");
|
||||
|
||||
const MODULE_VISIBILITY_SCHEMA = z.record(z.string(), z.enum(["main", "admin", "hidden"])).optional();
|
||||
const PATCH_SCHEMA = z
|
||||
.object({
|
||||
moduleVisibility: MODULE_VISIBILITY_SCHEMA,
|
||||
})
|
||||
.strict()
|
||||
.refine((value) => Object.keys(value).length > 0, { message: "empty_patch" });
|
||||
|
||||
function buildValidationError(details, statusCode = 400) {
|
||||
return new AppError({ code: "VALID_001", message: "参数校验失败", statusCode, details });
|
||||
}
|
||||
|
||||
function parseBody(schema, body) {
|
||||
const parsed = schema.safeParse(body ?? {});
|
||||
if (!parsed.success) {
|
||||
throw buildValidationError(parsed.error.flatten());
|
||||
}
|
||||
return parsed.data;
|
||||
}
|
||||
|
||||
async function writeWorkspaceAuditLog(fastify, request, workspace) {
|
||||
try {
|
||||
await fastify.prisma.auditLog.create({
|
||||
data: buildAuditLogData(request, {
|
||||
actorId: request.employee.id,
|
||||
action: "WORKSPACE_SETTING_UPDATED",
|
||||
targetType: "workspace_setting",
|
||||
targetId: workspace.id,
|
||||
extra: { moduleVisibility: workspace.moduleVisibility },
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
request.log.error({ err: error, workspaceId: workspace.id }, "workspace setting audit failed");
|
||||
}
|
||||
}
|
||||
|
||||
async function systemWorkspaceRoutes(fastify) {
|
||||
fastify.get("/system/workspace", { preHandler: fastify.authenticate }, async (request, reply) => {
|
||||
const workspace = await getWorkspaceSetting(fastify.prisma);
|
||||
return sendData(reply, request, {
|
||||
...workspace,
|
||||
defaults: DEFAULT_MODULE_VISIBILITY,
|
||||
});
|
||||
});
|
||||
|
||||
fastify.patch("/system/workspace", { preHandler: fastify.requirePermission("system.settings") }, async (request, reply) => {
|
||||
const body = parseBody(PATCH_SCHEMA, request.body);
|
||||
const workspace = await upsertWorkspaceSetting(fastify.prisma, request.employee.id, {
|
||||
moduleVisibility: normalizeModuleVisibilityJson(body.moduleVisibility ?? null),
|
||||
});
|
||||
|
||||
await writeWorkspaceAuditLog(fastify, request, workspace);
|
||||
return sendData(reply, request, { item: workspace });
|
||||
});
|
||||
|
||||
fastify.get("/system/navigation", { preHandler: fastify.authenticate }, async (request, reply) => {
|
||||
const data = await getSystemNavigation({
|
||||
prisma: fastify.prisma,
|
||||
config: fastify.config,
|
||||
employee: request.employee,
|
||||
hasPermission: fastify.hasPermission,
|
||||
});
|
||||
return sendData(reply, request, data);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = systemWorkspaceRoutes;
|
||||
在新工单中引用
屏蔽一个用户