// /middlewares/activityLogger.js 'use strict'; const activityLogService = require('../components/activityLogs/activityLogService'); const logger = require('../utils/logger'); const SKIP_PREFIXES = [ '/api/health', '/api/activity-logs' ]; const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']); const resolveResource = (path = '') => { const clean = path.split('?')[0].replace(/^\/api\//, ''); const [resource] = clean.split('/').filter(Boolean); return resource || 'unknown'; }; const resolveResourceId = (req) => { if (req.params?.id) return String(req.params.id); if (req.params?.userId) return String(req.params.userId); if (req.body?._id) return String(req.body._id); if (req.body?.id) return String(req.body.id); return null; }; const resolveAction = (method, path = '') => { const p = path.toLowerCase(); if (p.includes('/auth/login')) return 'login'; if (p.includes('/auth/logout')) return 'logout'; if (p.includes('/auth/refresh')) return 'other'; if (p.includes('reset-password')) return 'update'; if (p.includes('enroll') || p.includes('register')) return 'enroll'; if (p.includes('attendance')) return 'attendance'; if (p.includes('upload')) return 'upload'; if (p.includes('retry')) return 'retry'; switch (method) { case 'POST': return 'create'; case 'PUT': case 'PATCH': return 'update'; case 'DELETE': return 'delete'; default: return 'other'; } }; const shouldSkip = (req) => { if (!MUTATING_METHODS.has(req.method)) return true; const path = req.originalUrl || req.url || ''; if (SKIP_PREFIXES.some((prefix) => path.startsWith(prefix))) return true; if (path.includes('/auth/refresh')) return true; return false; }; const activityLogger = (req, res, next) => { if (shouldSkip(req)) { return next(); } res.on('finish', () => { // Only persist completed mutating requests (success or business failure) if (res.statusCode < 200 || res.statusCode >= 600) return; const path = (req.originalUrl || req.url || '').split('?')[0]; const action = resolveAction(req.method, path); const actor = req.user || null; const payload = { actor: actor?._id || null, actorUsername: actor?.username || req.body?.username || null, actorName: actor ? `${actor.name || ''}`.trim() || actor.username : req.body?.username || null, action, resource: resolveResource(path), resourceId: resolveResourceId(req), method: req.method, path, statusCode: res.statusCode, ip: req.ip || req.headers['x-forwarded-for'] || req.connection?.remoteAddress || null, userAgent: req.headers['user-agent'] || null, metadata: { params: activityLogService.sanitizeValue(req.params || {}), query: activityLogService.sanitizeValue(req.query || {}), body: activityLogService.sanitizeValue(req.body || {}) } }; activityLogService.createActivityLog(payload).catch((err) => { logger.error(`[ActivityLog] Failed to write log: ${err.message}`); }); }); next(); }; module.exports = activityLogger;