import { z } from 'zod' import { router, adminProcedure } from '../trpc' export const auditRouter = router({ /** * List audit logs with filtering and pagination */ list: adminProcedure .input( z.object({ userId: z.string().optional(), action: z.string().optional(), entityType: z.string().optional(), entityId: z.string().optional(), startDate: z.date().optional(), endDate: z.date().optional(), page: z.number().int().min(1).default(1), perPage: z.number().int().min(1).max(100).default(50), }) ) .query(async ({ ctx, input }) => { const { userId, action, entityType, entityId, startDate, endDate, page, perPage } = input const skip = (page - 1) * perPage const where: Record = {} if (userId) where.userId = userId if (action) where.action = { contains: action, mode: 'insensitive' } if (entityType) where.entityType = entityType if (entityId) where.entityId = entityId if (startDate || endDate) { where.timestamp = {} if (startDate) (where.timestamp as Record).gte = startDate if (endDate) (where.timestamp as Record).lte = endDate } const [logs, total] = await Promise.all([ ctx.prisma.auditLog.findMany({ where, skip, take: perPage, orderBy: { timestamp: 'desc' }, include: { user: { select: { name: true, email: true } }, }, }), ctx.prisma.auditLog.count({ where }), ]) return { logs, total, page, perPage, totalPages: Math.ceil(total / perPage), } }), /** * Get audit logs for a specific entity */ getByEntity: adminProcedure .input( z.object({ entityType: z.string(), entityId: z.string(), }) ) .query(async ({ ctx, input }) => { return ctx.prisma.auditLog.findMany({ where: { entityType: input.entityType, entityId: input.entityId, }, orderBy: { timestamp: 'desc' }, include: { user: { select: { name: true, email: true } }, }, }) }), /** * Get audit logs for a specific user */ getByUser: adminProcedure .input( z.object({ userId: z.string(), limit: z.number().int().min(1).max(100).default(50), }) ) .query(async ({ ctx, input }) => { return ctx.prisma.auditLog.findMany({ where: { userId: input.userId }, orderBy: { timestamp: 'desc' }, take: input.limit, }) }), /** * Get recent activity summary */ getRecentActivity: adminProcedure .input(z.object({ limit: z.number().int().min(1).max(50).default(20) })) .query(async ({ ctx, input }) => { return ctx.prisma.auditLog.findMany({ orderBy: { timestamp: 'desc' }, take: input.limit, include: { user: { select: { name: true, email: true } }, }, }) }), /** * Get action statistics */ getStats: adminProcedure .input( z.object({ startDate: z.date().optional(), endDate: z.date().optional(), }) ) .query(async ({ ctx, input }) => { const where: Record = {} if (input.startDate || input.endDate) { where.timestamp = {} if (input.startDate) (where.timestamp as Record).gte = input.startDate if (input.endDate) (where.timestamp as Record).lte = input.endDate } const [byAction, byEntity, byUser] = await Promise.all([ ctx.prisma.auditLog.groupBy({ by: ['action'], where, _count: true, orderBy: { _count: { action: 'desc' } }, }), ctx.prisma.auditLog.groupBy({ by: ['entityType'], where, _count: true, orderBy: { _count: { entityType: 'desc' } }, }), ctx.prisma.auditLog.groupBy({ by: ['userId'], where, _count: true, orderBy: { _count: { userId: 'desc' } }, take: 10, }), ]) // Get user names for top users const userIds = byUser .map((u) => u.userId) .filter((id): id is string => id !== null) const users = await ctx.prisma.user.findMany({ where: { id: { in: userIds } }, select: { id: true, name: true, email: true }, }) const userMap = new Map(users.map((u) => [u.id, u])) return { byAction: byAction.map((a) => ({ action: a.action, count: a._count, })), byEntity: byEntity.map((e) => ({ entityType: e.entityType, count: e._count, })), byUser: byUser.map((u) => ({ userId: u.userId, user: u.userId ? userMap.get(u.userId) : null, count: u._count, })), } }), })