mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
83 lines
3.8 KiB
JavaScript
83 lines
3.8 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* File Name : cronRegistry.util.js
|
|
* Type : Utility
|
|
* Description : Shared machinery for settings-backed cron jobs (the 4 jobs
|
|
* that emit notifications and are configurable from
|
|
* /admin/notifications/settings). Not every cron job in the
|
|
* app goes through this — jobs with no notification tied to
|
|
* them (e.g. lift_expired_bans) keep using node-cron directly.
|
|
*
|
|
* startSettingsBackedJobs() reads each job's schedule from
|
|
* cron_notification_settings (falling back to — and seeding —
|
|
* the job's own hardcoded default on first boot), then keeps
|
|
* a live reference to the scheduled task so it can be swapped
|
|
* out later via rescheduleJob() without a server restart.
|
|
*
|
|
* Author: Kenneth Obsequio (@lash0000)
|
|
* Date Created: Jul. 2, 2026
|
|
***********************************************************************************************************************************************************************/
|
|
'use strict';
|
|
|
|
const cron = require('node-cron');
|
|
const CronNotificationSetting = require('../models/notifications/cron_notification_setting.mdl');
|
|
|
|
// job_name -> { task: ScheduledTask, run: fn }
|
|
const runningTasks = new Map();
|
|
|
|
// CockroachDB can't run Sequelize's findOrCreate() — it wraps the insert in a
|
|
// pg_temp PL/pgSQL function to atomically catch unique_violation, which
|
|
// CockroachDB rejects ("cannot create user-defined functions under a temporary
|
|
// schema"). Plain findOne-then-create sidesteps it; the race window (two boots
|
|
// racing to seed the same job_name) is a non-issue here — jobs are seeded once.
|
|
async function getOrCreateSetting(jobName, defaultSchedule) {
|
|
let row = await CronNotificationSetting.findOne({ where: { job_name: jobName } });
|
|
if (row) return row;
|
|
|
|
try {
|
|
row = await CronNotificationSetting.create({ job_name: jobName, enabled: true, schedule: defaultSchedule });
|
|
} catch (err) {
|
|
row = await CronNotificationSetting.findOne({ where: { job_name: jobName } });
|
|
if (!row) throw err;
|
|
}
|
|
return row;
|
|
}
|
|
|
|
async function startSettingsBackedJobs(jobs, scopeLabel) {
|
|
const registered = [];
|
|
|
|
for (const { name, schedule: defaultSchedule, run } of jobs) {
|
|
let schedule = defaultSchedule;
|
|
try {
|
|
const settings = await getOrCreateSetting(name, defaultSchedule);
|
|
schedule = settings.schedule || defaultSchedule;
|
|
} catch (err) {
|
|
console.error(`[CRON][${scopeLabel}] Failed to load settings for "${name}", using hardcoded default:`, err);
|
|
}
|
|
|
|
if (!cron.validate(schedule)) {
|
|
console.error(`[CRON][${scopeLabel}] Invalid schedule for "${name}": "${schedule}" — skipped.`);
|
|
continue;
|
|
}
|
|
|
|
const task = cron.schedule(schedule, run);
|
|
runningTasks.set(name, { task, run });
|
|
registered.push({ name, scope: scopeLabel, schedule });
|
|
}
|
|
|
|
return registered;
|
|
}
|
|
|
|
// Live-swap a running job's schedule — used by notificationSettings.controller.js
|
|
// after an admin picks a new preset. No server restart required.
|
|
function rescheduleJob(jobName, newSchedule) {
|
|
const entry = runningTasks.get(jobName);
|
|
if (!entry) throw new Error(`No running cron task found for "${jobName}".`);
|
|
if (!cron.validate(newSchedule)) throw new Error(`Invalid cron schedule: "${newSchedule}".`);
|
|
|
|
entry.task.stop();
|
|
const task = cron.schedule(newSchedule, entry.run);
|
|
runningTasks.set(jobName, { task, run: entry.run });
|
|
}
|
|
|
|
module.exports = { startSettingsBackedJobs, rescheduleJob, getOrCreateSetting };
|