文件
hyb-oa/server/src/routes/audit.js
T

128 行
3.5 KiB
JavaScript

const { z } = require("zod");
const { AppError } = require("../utils/AppError");
const { sendData } = require("../utils/reply");
const QUERY_SCHEMA = z.object({
page: z.coerce.number().int().min(1).optional(),
pageSize: z.coerce.number().int().min(1).max(100).optional(),
actorId: z.string().uuid().optional(),
action: z.string().min(1).max(100).optional(),
targetType: z.string().min(1).max(100).optional(),
dateFrom: z.string().datetime().optional(),
dateTo: z.string().datetime().optional(),
});
function parseQuery(query) {
const parsed = QUERY_SCHEMA.safeParse(query ?? {});
if (!parsed.success) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: parsed.error.flatten(),
});
}
const page = parsed.data.page ?? 1;
const pageSize = parsed.data.pageSize ?? 20;
const dateFrom = parsed.data.dateFrom ? new Date(parsed.data.dateFrom) : null;
const dateTo = parsed.data.dateTo ? new Date(parsed.data.dateTo) : null;
if (dateFrom && dateTo && dateFrom > dateTo) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: { dateTo: "must_be_after_dateFrom" },
});
}
return {
page,
pageSize,
actorId: parsed.data.actorId,
action: parsed.data.action,
targetType: parsed.data.targetType,
dateFrom,
dateTo,
};
}
async function auditRoutes(fastify) {
fastify.get(
"/audit-logs",
{
preHandler: fastify.requirePermission("system.audit.view"),
},
async (request, reply) => {
const query = parseQuery(request.query);
const skip = (query.page - 1) * query.pageSize;
const where = {
...(query.actorId ? { actor_id: query.actorId } : {}),
...(query.action ? { action: query.action } : {}),
...(query.targetType ? { target_type: query.targetType } : {}),
...(query.dateFrom || query.dateTo
? {
created_at: {
...(query.dateFrom ? { gte: query.dateFrom } : {}),
...(query.dateTo ? { lte: query.dateTo } : {}),
},
}
: {}),
};
const [items, total] = await Promise.all([
fastify.prisma.auditLog.findMany({
where,
orderBy: { created_at: "desc" },
skip,
take: query.pageSize,
select: {
id: true,
request_id: true,
actor_id: true,
action: true,
target_type: true,
target_id: true,
ip_address: true,
user_agent: true,
extra: true,
created_at: true,
actor: {
select: {
id: true,
name: true,
email: true,
},
},
},
}),
fastify.prisma.auditLog.count({ where }),
]);
return sendData(reply, request, {
page: query.page,
pageSize: query.pageSize,
total,
items: items.map((item) => ({
id: item.id,
requestId: item.request_id,
actorId: item.actor_id,
actorName: item.actor?.name ?? "",
actorEmail: item.actor?.email ?? "",
action: item.action,
targetType: item.target_type,
targetId: item.target_id,
ipAddress: item.ip_address,
userAgent: item.user_agent,
extra: item.extra,
createdAt: item.created_at.toISOString(),
})),
});
},
);
}
module.exports = auditRoutes;