new commits

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-03 16:20:58 +08:00
parent 1372f4e975
commit 1d12f04967
93 changed files with 3849 additions and 1063 deletions
@@ -0,0 +1,78 @@
// controllers/admin/notificationSettings.controller.js
const CronNotificationSetting = require('../../models/notifications/cron_notification_setting.mdl');
const R = require('../../utils/response.util');
const logActivity = require('../../utils/logActivity.util');
const { CRON_PRESETS, CRON_PRESET_BY_EXPRESSION } = require('../../data/cronPresets.data');
const { rescheduleJob, getOrCreateSetting } = require('../../cron/cronRegistry.util');
// ─── Job registry — which cron scope owns each job (for defaults + labels) ────
const JOBS = {
taskOverdue: { schedule: '0 * * * *', label: 'Task Overdue Alerts (Admin)', description: 'Notifies admins when tasks flip to overdue.' },
userNotifications: { schedule: '5 * * * *', label: 'Task Overdue Alerts (Users)', description: 'Notifies affected users when their tasks are marked overdue.' },
issueCertificates: { schedule: '0 * * * *', label: 'Certificate Issued', description: 'Notifies users when a course certificate is ready.' },
expireUserTiers: { schedule: '* * * * *', label: 'Tier Expired', description: 'Notifies users when their subscription tier expires.' },
};
// ─── GET ──────────────────────────────────────────────────────────────────────
exports.getSettings = async (req, res) => {
try {
const rows = [];
for (const [job_name, meta] of Object.entries(JOBS)) {
const row = await getOrCreateSetting(job_name, meta.schedule);
rows.push({
job_name,
enabled: row.enabled,
schedule: row.schedule,
preset: CRON_PRESET_BY_EXPRESSION[row.schedule] ?? null,
label: meta.label,
description: meta.description,
updatedAt: row.updatedAt,
});
}
return R.success(res, 'Notification settings retrieved.', rows);
} catch (err) {
console.error('[NOTIFICATION SETTINGS][GET]', err);
return R.error(res, 'Could not retrieve notification settings.', 500);
}
};
// ─── UPDATE ───────────────────────────────────────────────────────────────────
exports.updateSetting = async (req, res) => {
try {
const { jobName } = req.params;
const { enabled, preset, updatedBy } = req.body;
if (!JOBS[jobName]) return R.error(res, `Unknown job "${jobName}".`, 404);
const row = await CronNotificationSetting.findOne({ where: { job_name: jobName } });
if (!row) return R.error(res, 'Setting not found.', 404);
if (enabled !== undefined) row.enabled = enabled === true || enabled === 'true';
if (preset !== undefined) {
const schedule = CRON_PRESETS[preset];
if (!schedule) return R.error(res, `Invalid preset. Must be one of: ${Object.keys(CRON_PRESETS).join(', ')}`, 400);
row.schedule = schedule;
try {
rescheduleJob(jobName, schedule);
} catch (err) {
console.error('[NOTIFICATION SETTINGS][RESCHEDULE]', err);
return R.error(res, `Saved, but failed to reschedule the live job: ${err.message}`, 500);
}
}
row.updatedBy = updatedBy ?? null;
await row.save();
logActivity(req.user?.user_id, 'update_notification_setting', { entityType: 'cron_notification_setting', entityId: jobName, details: { enabled: row.enabled, schedule: row.schedule } });
return R.success(res, 'Notification setting updated.', { data: row });
} catch (err) {
console.error('[NOTIFICATION SETTINGS][UPDATE]', err);
return R.error(res, 'Internal server error.', 500);
}
};