mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
chore: relocate backend into apps/api ahead of monorepo merge
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name : task_overdue.cron.js
|
||||
* Type : Cron Job
|
||||
* Description : Flips Task.status to a configurable target status — 'overdue'
|
||||
* (default before Jul 2026) or 'completed' (current default) —
|
||||
* once its deadline has passed, provided it isn't already
|
||||
* 'completed' or 'overdue'. The target status is an admin-
|
||||
* configurable setting (cron_notification_settings.target_status
|
||||
* for job_name 'taskOverdue'; NULL is treated as 'completed').
|
||||
* Purely an admin-facing lifecycle label — does NOT touch
|
||||
* TaskCompletion/TaskLinkVisit/TaskProgress, does NOT affect
|
||||
* per-user completion signals or client-side Ongoing/Done/
|
||||
* Overdue bucketing, and does NOT block late submissions.
|
||||
*
|
||||
* Every task this job touches also gets auto_marked_at set to
|
||||
* the current time — this is the ONLY writer of that column,
|
||||
* so downstream consumers (e.g. cron/jobs/user_notifications.cron.js)
|
||||
* can distinguish "the system just did this" from a user's own
|
||||
* legitimate completion. A task already sitting in 'overdue' or
|
||||
* 'completed' is never reclaimed by this sweep even if the
|
||||
* target status changes later — this job only ever moves tasks
|
||||
* OUT of 'pending'/'in_progress', never between the two terminal
|
||||
* states.
|
||||
*
|
||||
* Safety pattern:
|
||||
* - Task.update is the primary operation and must always succeed.
|
||||
* - AdminNotification.create is secondary — wrapped in its own
|
||||
* try/catch so a notification failure never rolls back or
|
||||
* suppresses the status flip. If it fails once, the next
|
||||
* hourly run will insert its own summary for that batch.
|
||||
*
|
||||
* Schedule : Every hour, on the hour ("0 * * * *"). Registered by
|
||||
* cron/admin.cron.js, not scheduled here directly.
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 17, 2026
|
||||
* Modified : Jun. 19, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
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 { NOTIFICATION_REGISTRY } = require('../../data/notifications.data');
|
||||
|
||||
// ─── The actual sweep ────────────────────────────────────────────────────────
|
||||
async function run() {
|
||||
// ── 0. Load configured target status (defaults to 'completed') ───────────
|
||||
let settings = null;
|
||||
let targetStatus = 'completed';
|
||||
try {
|
||||
settings = await CronNotificationSetting.findOne({ where: { job_name: 'taskOverdue' } });
|
||||
if (settings?.target_status === 'overdue' || settings?.target_status === 'completed') {
|
||||
targetStatus = settings.target_status;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[CRON][TASK OVERDUE] Failed to load target_status setting, defaulting to "completed":', err);
|
||||
}
|
||||
|
||||
// ── 1. Primary: flip task statuses ───────────────────────────────────────
|
||||
let affectedCount = 0;
|
||||
|
||||
try {
|
||||
[affectedCount] = await Task.update(
|
||||
{ status: targetStatus, auto_marked_at: new Date() },
|
||||
{
|
||||
where: {
|
||||
deadline: { [Op.lt]: new Date() },
|
||||
status: { [Op.notIn]: ['completed', 'overdue'] },
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('[CRON][TASK OVERDUE] Failed to update task statuses:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (affectedCount === 0) return;
|
||||
|
||||
console.log(`[CRON][TASK OVERDUE] Marked ${affectedCount} task(s) as ${targetStatus}.`);
|
||||
|
||||
// ── 2. Secondary: admin notification — isolated, never blocks step 1 ─────
|
||||
// Skippable via /admin/notifications/settings — the status flip above always happens either way.
|
||||
try {
|
||||
if (settings && !settings.enabled) return;
|
||||
|
||||
await AdminNotification.create(
|
||||
NOTIFICATION_REGISTRY.task_overdue.build({ count: affectedCount, targetStatus })
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('[CRON][TASK OVERDUE] Failed to insert admin notification:', err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'taskOverdue',
|
||||
schedule: '0 * * * *',
|
||||
run,
|
||||
};
|
||||
Reference in New Issue
Block a user