128 行
3.5 KiB
JavaScript
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;
|