implement tasks

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-17 17:28:11 +08:00
parent bae079d5d8
commit 941590f51a
11 changed files with 327 additions and 103 deletions
@@ -8,12 +8,16 @@ const { rescheduleJob, getOrCreateSetting } = require('../../cron/cronRegistry.u
// ─── 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.' },
taskOverdue: { schedule: '0 * * * *', label: 'Task Alerts (Admin)', description: 'Automatically marks expired tasks as overdue or completed, and notifies admins.' },
userNotifications: { schedule: '5 * * * *', label: 'Task Alerts (Users)', description: 'Notifies affected users when their tasks are automatically marked overdue or completed.' },
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.' },
};
// Jobs whose behavior can be tuned via target_status, and the values each accepts.
const TARGET_STATUS_OPTIONS = ['overdue', 'completed'];
const TARGET_STATUS_JOBS = ['taskOverdue'];
// ─── GET ──────────────────────────────────────────────────────────────────────
exports.getSettings = async (req, res) => {
@@ -26,6 +30,9 @@ exports.getSettings = async (req, res) => {
enabled: row.enabled,
schedule: row.schedule,
preset: CRON_PRESET_BY_EXPRESSION[row.schedule] ?? null,
target_status: TARGET_STATUS_JOBS.includes(job_name)
? (row.target_status ?? 'completed')
: null,
label: meta.label,
description: meta.description,
updatedAt: row.updatedAt,
@@ -44,7 +51,7 @@ exports.getSettings = async (req, res) => {
exports.updateSetting = async (req, res) => {
try {
const { jobName } = req.params;
const { enabled, preset, updatedBy } = req.body;
const { enabled, preset, target_status, updatedBy } = req.body;
if (!JOBS[jobName]) return R.error(res, `Unknown job "${jobName}".`, 404);
@@ -53,6 +60,16 @@ exports.updateSetting = async (req, res) => {
if (enabled !== undefined) row.enabled = enabled === true || enabled === 'true';
if (target_status !== undefined) {
if (!TARGET_STATUS_JOBS.includes(jobName)) {
return R.error(res, `"target_status" is not configurable for job "${jobName}".`, 400);
}
if (!TARGET_STATUS_OPTIONS.includes(target_status)) {
return R.error(res, `Invalid target_status. Must be one of: ${TARGET_STATUS_OPTIONS.join(', ')}`, 400);
}
row.target_status = target_status;
}
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);
@@ -69,7 +86,7 @@ exports.updateSetting = async (req, res) => {
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 } });
logActivity(req.user?.user_id, 'update_notification_setting', { entityType: 'cron_notification_setting', entityId: jobName, details: { enabled: row.enabled, schedule: row.schedule, target_status: row.target_status } });
return R.success(res, 'Notification setting updated.', { data: row });
} catch (err) {
console.error('[NOTIFICATION SETTINGS][UPDATE]', err);