88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
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);
|
|
}
|
|
}
|