// /components/activityLogs/activityLogService.js 'use strict'; const ActivityLog = require('./activityLogModel'); const { ACTIVITY_ACTIONS } = require('./activityLogModel'); const { parsePaginationAndSort, calculateMeta } = require('../../utils/pagination'); const SENSITIVE_KEYS = new Set([ 'password', 'passwordHash', 'refreshToken', 'accessToken', 'token', 'secret', 'smtp_pass', 'SMTP_PASS' ]); const sanitizeValue = (value, depth = 0) => { if (value == null) return value; if (depth > 3) return '[truncated]'; if (Array.isArray(value)) { return value.slice(0, 20).map((item) => sanitizeValue(item, depth + 1)); } if (typeof value === 'object') { const cleaned = {}; Object.keys(value).slice(0, 30).forEach((key) => { if (SENSITIVE_KEYS.has(key)) { cleaned[key] = '[redacted]'; } else { cleaned[key] = sanitizeValue(value[key], depth + 1); } }); return cleaned; } if (typeof value === 'string' && value.length > 300) { return `${value.slice(0, 300)}…`; } return value; }; const buildDescription = ({ action, resource, actorName, actorUsername, method, path }) => { const ACTION_LABELS = { create: 'ایجاد', update: 'ویرایش', delete: 'حذف', login: 'ورود', logout: 'خروج', enroll: 'ثبت‌نام', attendance: 'حضور و غیاب', upload: 'آپلود', retry: 'تلاش مجدد', other: 'عملیات' }; const RESOURCE_LABELS = { auth: 'احراز هویت', users: 'کاربران', professors: 'اساتید', courses: 'دوره‌ها', classes: 'کلاس‌ها', sessions: 'جلسات', payments: 'پرداخت‌ها', roles: 'نقش‌ها', notifications: 'اطلاعیه‌ها', certificates: 'گواهینامه‌ها', files: 'فایل‌ها', dashboard: 'داشبورد', 'contact-inquiries': 'درخواست‌های تماس', 'activity-logs': 'گزارش فعالیت‌ها' }; const who = actorName || actorUsername || 'سیستم'; const actionLabel = ACTION_LABELS[action] || action || 'عملیات'; const resourceLabel = RESOURCE_LABELS[resource] || resource || 'منبع'; return `${who} — ${actionLabel} روی ${resourceLabel} (${method} ${path})`; }; const createActivityLog = async (payload) => { const doc = { ...payload, metadata: sanitizeValue(payload.metadata || {}), description: payload.description || buildDescription(payload) }; return ActivityLog.create(doc); }; const getAllActivityLogs = async (queryParams = {}) => { const { page, limit, skip, sort } = parsePaginationAndSort(queryParams); const filter = {}; if (queryParams.action) { filter.action = queryParams.action; } if (queryParams.resource) { filter.resource = queryParams.resource; } if (queryParams.actor) { filter.actor = queryParams.actor; } if (queryParams.q || queryParams.search) { const searchRegex = new RegExp(String(queryParams.q || queryParams.search).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); filter.$or = [ { description: searchRegex }, { actorUsername: searchRegex }, { actorName: searchRegex }, { path: searchRegex }, { resource: searchRegex }, { resourceId: searchRegex } ]; } const [logs, totalCount] = await Promise.all([ ActivityLog.find(filter) .populate('actor', 'name username') .sort(sort) .skip(skip) .limit(limit) .lean(), ActivityLog.countDocuments(filter) ]); return { data: logs, meta: calculateMeta(totalCount, page, limit) }; }; const getActivityActions = () => ACTIVITY_ACTIONS; module.exports = { createActivityLog, getAllActivityLogs, getActivityActions, sanitizeValue, buildDescription };