Initial commit: Horux Despachos project
This commit is contained in:
87
apps/api/src/controllers/audit-log.controller.ts
Normal file
87
apps/api/src/controllers/audit-log.controller.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import { prisma } from '../config/database.js';
|
||||
import { isGlobalAdmin } from '../utils/global-admin.js';
|
||||
|
||||
async function requireGlobalAdmin(req: Request, res: Response): Promise<boolean> {
|
||||
const isAdmin = await isGlobalAdmin(req.user!.tenantId, req.user!.role);
|
||||
if (!isAdmin) {
|
||||
res.status(403).json({ message: 'Solo el administrador global puede consultar el audit log' });
|
||||
}
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista eventos de audit con filtros opcionales. Admin global only.
|
||||
*
|
||||
* Query params:
|
||||
* action — filtra por action prefix (ej: "subscription." matches todas las subs)
|
||||
* tenantId — filtra a un tenant específico
|
||||
* userId — filtra a un user específico
|
||||
* from, to — rango de fechas (ISO)
|
||||
* page, limit — paginación (default: 1, 50; max limit 200)
|
||||
*/
|
||||
export async function listAuditLog(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
if (!(await requireGlobalAdmin(req, res))) return;
|
||||
|
||||
const action = String(req.query.action || '').trim();
|
||||
const tenantId = String(req.query.tenantId || '').trim();
|
||||
const userId = String(req.query.userId || '').trim();
|
||||
const from = String(req.query.from || '').trim();
|
||||
const to = String(req.query.to || '').trim();
|
||||
const page = Math.max(1, parseInt(String(req.query.page || '1'), 10) || 1);
|
||||
const limit = Math.min(200, Math.max(1, parseInt(String(req.query.limit || '50'), 10) || 50));
|
||||
|
||||
const where: any = {};
|
||||
if (action) where.action = { startsWith: action };
|
||||
if (tenantId) where.tenantId = tenantId;
|
||||
if (userId) where.userId = userId;
|
||||
if (from || to) {
|
||||
where.createdAt = {};
|
||||
if (from) where.createdAt.gte = new Date(from);
|
||||
if (to) where.createdAt.lte = new Date(to);
|
||||
}
|
||||
|
||||
const [total, rows] = await Promise.all([
|
||||
prisma.auditLog.count({ where }),
|
||||
prisma.auditLog.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
}),
|
||||
]);
|
||||
|
||||
// Enriquecer con user.email y tenant.nombre para display
|
||||
const userIds = [...new Set(rows.map(r => r.userId).filter(Boolean))] as string[];
|
||||
const tenantIds = [...new Set(rows.map(r => r.tenantId).filter(Boolean))] as string[];
|
||||
|
||||
const [users, tenants] = await Promise.all([
|
||||
userIds.length
|
||||
? prisma.user.findMany({ where: { id: { in: userIds } }, select: { id: true, email: true, nombre: true } })
|
||||
: [],
|
||||
tenantIds.length
|
||||
? prisma.tenant.findMany({ where: { id: { in: tenantIds } }, select: { id: true, nombre: true, rfc: true } })
|
||||
: [],
|
||||
]);
|
||||
|
||||
const userMap = new Map(users.map(u => [u.id, u]));
|
||||
const tenantMap = new Map(tenants.map(t => [t.id, t]));
|
||||
|
||||
const data = rows.map(r => ({
|
||||
...r,
|
||||
user: r.userId ? userMap.get(r.userId) || null : null,
|
||||
tenant: r.tenantId ? tenantMap.get(r.tenantId) || null : null,
|
||||
}));
|
||||
|
||||
res.json({
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user