import type { FastifyRequest } from "fastify"; import { prisma } from "../lib/prisma"; import type { AuditAction, AuditCategory, AuditLogFilters } from "@library/shared-types"; interface AuditLogData { action: AuditAction; category: AuditCategory; userId?: string; targetUserId?: string; resourceType?: string; resourceId?: string; details?: string; success?: boolean; } export const AuditService = { async log(data: AuditLogData, request?: FastifyRequest) { const userAgent = request?.headers["user-agent"] ?? undefined; return prisma.auditLog.create({ data: { action: data.action, category: data.category, userId: data.userId, targetUserId: data.targetUserId, resourceType: data.resourceType, resourceId: data.resourceId, details: data.details, userAgent, success: data.success ?? true, }, }); }, async logFromRequest( request: FastifyRequest, data: Omit ) { const userId = ((request as any).user as { id?: string } | undefined)?.id; return this.log( { ...data, userId, }, request ); }, async getLogs(filters: AuditLogFilters = {}) { const { action, category, userId, success, startDate, endDate, page = 1, limit = 50 } = filters; const where: Record = {}; if (action) { where.action = action; } if (category) { where.category = category; } if (userId) { where.userId = userId; } if (success !== undefined) { where.success = success; } if (startDate || endDate) { where.createdAt = {}; if (startDate) { (where.createdAt as Record).gte = startDate; } if (endDate) { (where.createdAt as Record).lte = endDate; } } const [rawLogs, total] = await Promise.all([ prisma.auditLog.findMany({ where, orderBy: { createdAt: "desc" }, skip: (page - 1) * limit, take: limit, }), prisma.auditLog.count({ where }), ]); // Collect all unique user IDs to fetch const userIds = new Set(); for (const log of rawLogs) { if (log.userId) { userIds.add(log.userId); } if (log.targetUserId) { userIds.add(log.targetUserId); } } // Fetch all users in one query const users = userIds.size > 0 ? await prisma.user.findMany({ where: { id: { in: Array.from(userIds) } }, select: { id: true, username: true, avatar: true }, }) : []; // Create a lookup map const userMap = new Map(users.map(u => [u.id, { id: u.id, username: u.username, avatar: u.avatar ?? undefined }])); // Map logs with user info const logs = rawLogs.map(log => ({ id: log.id, action: log.action, category: log.category, userId: log.userId ?? undefined, user: log.userId ? userMap.get(log.userId) : undefined, targetUserId: log.targetUserId ?? undefined, targetUser: log.targetUserId ? userMap.get(log.targetUserId) : undefined, resourceType: log.resourceType ?? undefined, resourceId: log.resourceId ?? undefined, details: log.details ?? undefined, userAgent: log.userAgent ?? undefined, success: log.success, createdAt: log.createdAt, })); return { logs, total, page, limit, totalPages: Math.ceil(total / limit), }; }, async getLogsByUser(userId: string, page = 1, limit = 50) { return this.getLogs({ userId, page, limit }); }, async getSecurityLogs(page = 1, limit = 50) { return this.getLogs({ category: "SECURITY" as AuditCategory, page, limit }); }, };