Files
gameno-api/middlewares/activityLogger.js
T
kavehhn 72be01fee3 feat: add messaging toggles, password reset SMS, and payment discounts
Allow SuperAdmin to disable SMS, email, and bot from dashboard settings on top of env flags. Add admin password reset with credentials SMS, plus payment discounts, notes, and payable amount handling.
2026-08-16 06:58:08 +03:30

106 lines
3.1 KiB
JavaScript

// /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;