add features
This commit is contained in:
169
backend/src/routes/admin.routes.ts
Normal file
169
backend/src/routes/admin.routes.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Admin API Routes — Audit Log
|
||||
*
|
||||
* GET /api/admin/audit-log — paginated, filtered list
|
||||
* GET /api/admin/audit-log/export — CSV download of filtered results
|
||||
*
|
||||
* Both endpoints require authentication + admin:access permission.
|
||||
*
|
||||
* Register in app.ts:
|
||||
* import adminRoutes from './routes/admin.routes';
|
||||
* app.use('/api/admin', adminRoutes);
|
||||
*/
|
||||
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { authenticate } from '../middleware/auth.middleware';
|
||||
import { requirePermission } from '../middleware/rbac.middleware';
|
||||
import { auditExport } from '../middleware/audit.middleware';
|
||||
import auditService, { AuditAction, AuditResourceType, AuditFilters } from '../services/audit.service';
|
||||
import logger from '../utils/logger';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Input validation schemas (Zod)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const auditQuerySchema = z.object({
|
||||
userId: z.string().uuid().optional(),
|
||||
action: z.union([
|
||||
z.nativeEnum(Object.fromEntries(
|
||||
Object.entries(AuditAction).map(([k, v]) => [k, v])
|
||||
) as Record<string, string>),
|
||||
z.array(z.string()),
|
||||
]).optional(),
|
||||
resourceType: z.union([
|
||||
z.string(),
|
||||
z.array(z.string()),
|
||||
]).optional(),
|
||||
resourceId: z.string().optional(),
|
||||
dateFrom: z.string().datetime({ offset: true }).optional(),
|
||||
dateTo: z.string().datetime({ offset: true }).optional(),
|
||||
page: z.coerce.number().int().positive().default(1),
|
||||
pageSize: z.coerce.number().int().min(1).max(200).default(25),
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper — parse and validate query string
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function parseAuditQuery(query: Record<string, unknown>): AuditFilters {
|
||||
const parsed = auditQuerySchema.parse(query);
|
||||
|
||||
// Normalise action to array of AuditAction
|
||||
const actions = parsed.action
|
||||
? (Array.isArray(parsed.action) ? parsed.action : [parsed.action]) as AuditAction[]
|
||||
: undefined;
|
||||
|
||||
// Normalise resourceType to array of AuditResourceType
|
||||
const resourceTypes = parsed.resourceType
|
||||
? (Array.isArray(parsed.resourceType)
|
||||
? parsed.resourceType
|
||||
: [parsed.resourceType]) as AuditResourceType[]
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
userId: parsed.userId,
|
||||
action: actions && actions.length === 1 ? actions[0] : actions,
|
||||
resourceType: resourceTypes && resourceTypes.length === 1
|
||||
? resourceTypes[0]
|
||||
: resourceTypes,
|
||||
resourceId: parsed.resourceId,
|
||||
dateFrom: parsed.dateFrom ? new Date(parsed.dateFrom) : undefined,
|
||||
dateTo: parsed.dateTo ? new Date(parsed.dateTo) : undefined,
|
||||
page: parsed.page,
|
||||
pageSize: parsed.pageSize,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GET /api/admin/audit-log
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
router.get(
|
||||
'/audit-log',
|
||||
authenticate,
|
||||
requirePermission('admin:access'),
|
||||
async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const filters = parseAuditQuery(req.query as Record<string, unknown>);
|
||||
|
||||
const result = await auditService.getAuditLog(filters);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid query parameters',
|
||||
errors: error.errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error('Failed to fetch audit log', { error });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch audit log',
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GET /api/admin/audit-log/export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
router.get(
|
||||
'/audit-log/export',
|
||||
authenticate,
|
||||
requirePermission('admin:access'),
|
||||
async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
// For CSV exports we fetch up to 10,000 rows (no pagination).
|
||||
const filters = parseAuditQuery(req.query as Record<string, unknown>);
|
||||
const exportFilters: AuditFilters = {
|
||||
...filters,
|
||||
page: 1,
|
||||
pageSize: 10_000,
|
||||
};
|
||||
|
||||
// Audit the export action itself before streaming the response
|
||||
auditExport(req, AuditResourceType.SYSTEM, {
|
||||
export_format: 'csv',
|
||||
filters: JSON.stringify(exportFilters),
|
||||
});
|
||||
|
||||
const result = await auditService.getAuditLog(exportFilters);
|
||||
const csv = auditService.entriesToCsv(result.entries);
|
||||
|
||||
const filename = `audit_log_${new Date().toISOString().replace(/[:.]/g, '-')}.csv`;
|
||||
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
// Add BOM for Excel UTF-8 compatibility
|
||||
res.send('\uFEFF' + csv);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid query parameters',
|
||||
errors: error.errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error('Failed to export audit log', { error });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to export audit log',
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user