Initial commit: teaching institution management API.
Express/MongoDB backend with JWT auth, RBAC, S3 uploads, notifications, and scheduled jobs.
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
// /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) {
|
||||
const searchRegex = new RegExp(queryParams.q, '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 surname 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
|
||||
};
|
||||
Reference in New Issue
Block a user