mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
+33
-15
@@ -5,40 +5,58 @@
|
||||
* Same shape as admin.cron.js — each job module exports
|
||||
* { name, schedule, run }, listed in the `jobs` array below.
|
||||
*
|
||||
* All three are settings-backed (see cronRegistry.util.js) —
|
||||
* The settings-backed jobs emit notifications, so their
|
||||
* schedule/enabled state lives in cron_notification_settings
|
||||
* and is configurable from /admin/notifications/settings
|
||||
* without a restart.
|
||||
* without a restart. expireAdvertisements has no notification
|
||||
* tied to it, so it stays on a plain hardcoded schedule (same
|
||||
* reasoning as liftExpiredBans in admin.cron.js).
|
||||
*
|
||||
* Currently registered:
|
||||
* - userNotifications (cron/jobs/user_notifications.cron.js)
|
||||
* - issueCertificates (cron/jobs/issue_certificates.cron.js)
|
||||
* - expireUserTiers (cron/jobs/expire_user_tiers.cron.js)
|
||||
* - userNotifications (cron/jobs/user_notifications.cron.js)
|
||||
* - issueCertificates (cron/jobs/issue_certificates.cron.js)
|
||||
* - expireUserTiers (cron/jobs/expire_user_tiers.cron.js)
|
||||
* - taskDueSoon (cron/jobs/task_due_soon.cron.js)
|
||||
* - expireAdvertisements (cron/jobs/expire_advertisements.cron.js) — plain
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 17, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const userNotifications = require('./jobs/user_notifications.cron');
|
||||
const issueCertificates = require('./jobs/issue_certificates.cron');
|
||||
const expireUserTiers = require('./jobs/expire_user_tiers.cron');
|
||||
const taskDueSoon = require('./jobs/task_due_soon.cron');
|
||||
const cron = require('node-cron');
|
||||
const userNotifications = require('./jobs/user_notifications.cron');
|
||||
const issueCertificates = require('./jobs/issue_certificates.cron');
|
||||
const expireUserTiers = require('./jobs/expire_user_tiers.cron');
|
||||
const taskDueSoon = require('./jobs/task_due_soon.cron');
|
||||
const expireAdvertisements = require('./jobs/expire_advertisements.cron');
|
||||
const { startSettingsBackedJobs } = require('./cronRegistry.util');
|
||||
|
||||
// TODO(ads-7): Add an `expire_advertisements.cron.js` job (same shape as
|
||||
// expire_user_tiers.cron.js) that auto-archives (soft-deletes) advertisements
|
||||
// once their end_date has passed, instead of just leaving them at derived
|
||||
// status "expired" forever. Register it in the `jobs` array below.
|
||||
// ─── Registry — add future client-side cron jobs here ────────────────────────
|
||||
const jobs = [
|
||||
const settingsBackedJobs = [
|
||||
userNotifications,
|
||||
issueCertificates,
|
||||
expireUserTiers,
|
||||
taskDueSoon,
|
||||
];
|
||||
|
||||
// Plain hardcoded-schedule jobs (not tied to any notification setting).
|
||||
const plainJobs = [
|
||||
expireAdvertisements,
|
||||
];
|
||||
|
||||
// ─── Boot all registered client-side jobs ─────────────────────────────────────
|
||||
async function startClientCronJobs() {
|
||||
return startSettingsBackedJobs(jobs, 'CLIENT');
|
||||
const registered = await startSettingsBackedJobs(settingsBackedJobs, 'CLIENT');
|
||||
|
||||
for (const job of plainJobs) {
|
||||
if (!cron.validate(job.schedule)) {
|
||||
console.error(`[CRON][CLIENT] Invalid schedule for "${job.name}": "${job.schedule}" — skipped.`);
|
||||
continue;
|
||||
}
|
||||
cron.schedule(job.schedule, job.run);
|
||||
registered.push({ name: job.name, scope: 'CLIENT', schedule: job.schedule });
|
||||
}
|
||||
|
||||
return registered;
|
||||
}
|
||||
|
||||
module.exports = { startClientCronJobs };
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name : expire_advertisements.cron.js
|
||||
* Type : Cron Job
|
||||
* Description : Auto-archives (soft-deletes) advertisements once their
|
||||
* end_date has passed, so expired ads don't sit indefinitely
|
||||
* in the active Advertisements list — they fall through to
|
||||
* the Archived Advertisements table, same path as a manual
|
||||
* archive action.
|
||||
*
|
||||
* Only touches rows with end_date IS NOT NULL so ads with no
|
||||
* end date (run indefinitely) are never auto-archived.
|
||||
*
|
||||
* Schedule : Every minute ("* * * * *"). Registered by cron/client.cron.js.
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jul. 11, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
'use strict';
|
||||
|
||||
const { Op } = require('sequelize');
|
||||
const mdl_Advertisements = require('../../models/advertisements/advertisements.mdl');
|
||||
|
||||
async function run() {
|
||||
let expired;
|
||||
try {
|
||||
expired = await mdl_Advertisements.findAll({
|
||||
where: {
|
||||
deletedAt: null,
|
||||
end_date: { [Op.ne]: null, [Op.lt]: new Date() },
|
||||
},
|
||||
attributes: ['advertisement_id'],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[CRON][EXPIRE ADVERTISEMENTS] Failed to query advertisements:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expired.length) return;
|
||||
|
||||
const ids = expired.map((a) => a.advertisement_id);
|
||||
|
||||
try {
|
||||
await mdl_Advertisements.update({ status: 'expired' }, { where: { advertisement_id: { [Op.in]: ids } } });
|
||||
await mdl_Advertisements.destroy({ where: { advertisement_id: { [Op.in]: ids } } });
|
||||
} catch (err) {
|
||||
console.error('[CRON][EXPIRE ADVERTISEMENTS] Archive failed:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[CRON][EXPIRE ADVERTISEMENTS] Auto-archived ${ids.length} expired advertisement(s).`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'expireAdvertisements',
|
||||
schedule: '* * * * *',
|
||||
run,
|
||||
};
|
||||
@@ -28,7 +28,7 @@ const mdl_UserTiers = require('../../models/tiers/user_tiers.mdl');
|
||||
const mdl_TierPlans = require('../../models/tiers/tier_plans.mdl');
|
||||
const UserNotification = require('../../models/notifications/user_notification.mdl');
|
||||
const CronNotificationSetting = require('../../models/notifications/cron_notification_setting.mdl');
|
||||
const { getNotificationTemplate, renderNotificationContent } = require('../../services/notificationTemplate.service');
|
||||
const { NOTIFICATION_REGISTRY } = require('../../data/notifications.data');
|
||||
|
||||
require('../../models/tiers/tier.associations');
|
||||
|
||||
@@ -74,10 +74,9 @@ async function run() {
|
||||
const settings = await CronNotificationSetting.findOne({ where: { job_name: 'expireUserTiers' } });
|
||||
if (!settings || settings.enabled) {
|
||||
try {
|
||||
const template = await getNotificationTemplate('tier_expired');
|
||||
const notifications = expired.map((t) => ({
|
||||
user_id: t.user_id,
|
||||
...renderNotificationContent(template, {
|
||||
...NOTIFICATION_REGISTRY.tier_expired.build({
|
||||
tier: t.tier,
|
||||
label: t.plan?.label ?? null,
|
||||
planId: t.plan?.plan_id ?? null,
|
||||
|
||||
@@ -32,7 +32,7 @@ const PendingCertificate = require('../../models/courses/pending_certificate.mdl
|
||||
const mdl_Achievements = require('../../models/users/achievements.mdl');
|
||||
const UserNotification = require('../../models/notifications/user_notification.mdl');
|
||||
const CronNotificationSetting = require('../../models/notifications/cron_notification_setting.mdl');
|
||||
const { getNotificationTemplate, renderNotificationContent } = require('../../services/notificationTemplate.service');
|
||||
const { NOTIFICATION_REGISTRY } = require('../../data/notifications.data');
|
||||
const { ensureCertificateRecord } = require('../../services/certificate-record.service');
|
||||
|
||||
async function run() {
|
||||
@@ -59,10 +59,6 @@ async function run() {
|
||||
|
||||
console.log(`[CRON][ISSUE CERTS] Processing ${rows.length} pending certificate(s).`);
|
||||
|
||||
// Fetched once outside the loop — content differs per row (courseTitle),
|
||||
// but there's no need to re-query the template for every row.
|
||||
const certificateTemplate = notificationsEnabled ? await getNotificationTemplate('certificate_issued') : null;
|
||||
|
||||
for (const row of rows) {
|
||||
const { pending_id, user_id, course_uuid, course_title } = row;
|
||||
const achKey = `course_completed_${course_uuid}`;
|
||||
@@ -91,7 +87,7 @@ async function run() {
|
||||
if (notificationsEnabled) {
|
||||
await UserNotification.create({
|
||||
user_id,
|
||||
...renderNotificationContent(certificateTemplate, {
|
||||
...NOTIFICATION_REGISTRY.certificate_issued.build({
|
||||
courseTitle: course_title ?? '',
|
||||
courseUuid: course_uuid,
|
||||
}),
|
||||
|
||||
@@ -25,7 +25,7 @@ const sequelize = require('../../config/db.config');
|
||||
const { Task } = require('../../models/task/task.mdl');
|
||||
const UserNotification = require('../../models/notifications/user_notification.mdl');
|
||||
const CronNotificationSetting = require('../../models/notifications/cron_notification_setting.mdl');
|
||||
const { renderNotification } = require('../../services/notificationTemplate.service');
|
||||
const { NOTIFICATION_REGISTRY } = require('../../data/notifications.data');
|
||||
const { checkTaskCompletion } = require('../../controllers/client/task.controller');
|
||||
|
||||
const WINDOW_START_MS = 23 * 60 * 60 * 1000;
|
||||
@@ -91,9 +91,9 @@ async function run() {
|
||||
}
|
||||
if (!incompleteUserIds.length) continue;
|
||||
|
||||
const notify = await renderNotification({ type: 'task_reminder', data: {
|
||||
const notify = NOTIFICATION_REGISTRY.task_reminder.build({
|
||||
taskName: task.name, deadline: task.deadline,
|
||||
} });
|
||||
});
|
||||
await UserNotification.bulkCreate(
|
||||
incompleteUserIds.map((user_id) => ({ user_id, ...notify, seen: false, createdAt: now2, updatedAt: now2 })),
|
||||
{ validate: false }
|
||||
|
||||
@@ -26,7 +26,7 @@ const { Op } = require('sequelize');
|
||||
const { Task } = require('../../models/task/task.mdl');
|
||||
const AdminNotification = require('../../models/notifications/admin_notification.mdl');
|
||||
const CronNotificationSetting = require('../../models/notifications/cron_notification_setting.mdl');
|
||||
const { renderNotification } = require('../../services/notificationTemplate.service');
|
||||
const { NOTIFICATION_REGISTRY } = require('../../data/notifications.data');
|
||||
|
||||
// ─── The actual sweep ────────────────────────────────────────────────────────
|
||||
async function run() {
|
||||
@@ -59,7 +59,7 @@ async function run() {
|
||||
if (settings && !settings.enabled) return;
|
||||
|
||||
await AdminNotification.create(
|
||||
await renderNotification({ type: 'task_overdue', data: { count: affectedCount } })
|
||||
NOTIFICATION_REGISTRY.task_overdue.build({ count: affectedCount })
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('[CRON][TASK OVERDUE] Failed to insert admin notification:', err);
|
||||
|
||||
@@ -25,7 +25,7 @@ const sequelize = require('../../config/db.config');
|
||||
const { Task } = require('../../models/task/task.mdl');
|
||||
const UserNotification = require('../../models/notifications/user_notification.mdl');
|
||||
const CronNotificationSetting = require('../../models/notifications/cron_notification_setting.mdl');
|
||||
const { renderNotification } = require('../../services/notificationTemplate.service');
|
||||
const { NOTIFICATION_REGISTRY } = require('../../data/notifications.data');
|
||||
|
||||
const WINDOW_MS = 65 * 60 * 1000; // 65-minute lookback
|
||||
|
||||
@@ -71,7 +71,7 @@ async function run() {
|
||||
|
||||
const count = recentlyOverdue.length;
|
||||
const now = new Date();
|
||||
const notify = await renderNotification({ type: 'user_task_overdue', data: { count, task_list_ids: taskListIds } });
|
||||
const notify = NOTIFICATION_REGISTRY.user_task_overdue.build({ count, task_list_ids: taskListIds });
|
||||
|
||||
await UserNotification.bulkCreate(
|
||||
affectedUsers.map(({ user_id }) => ({
|
||||
|
||||
Reference in New Issue
Block a user