Files
starr-philproperties/cron/jobs/user_notifications.cron.js
T
kennethobsequio 439bb33f77 ready to test
Testing

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-06-22 10:06:58 +08:00

93 lines
3.9 KiB
JavaScript

/***********************************************************************************************************************************************************************
* File Name : user_task_overdue_notify.cron.js
* Type : Cron Job
* Description : Emits a UserNotification for every user who belongs to a group
* assigned to a task list that contains a task that JUST flipped
* to 'overdue' in the last hour.
*
* Runs 5 minutes after the admin taskOverdue cron (which fires at
* the top of each hour) so the status flips are already committed
* before this job queries them.
*
* "Just flipped" = status is 'overdue' AND updatedAt is within
* the last 65 minutes (1-hour window + 5-min drift buffer).
* This prevents re-notifying users for tasks that were already
* overdue before this run.
*
* Schedule : 5 minutes past every hour ("5 * * * *"). Registered by
* cron/client.cron.js.
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jun. 19, 2026
***********************************************************************************************************************************************************************/
const { Op, QueryTypes } = require('sequelize');
const sequelize = require('../../config/db.config');
const { Task } = require('../../models/task/task.mdl');
const UserNotification = require('../../models/notifications/user_notification.mdl');
const { NOTIFICATION_REGISTRY } = require('../../data/notifications.data');
const WINDOW_MS = 65 * 60 * 1000; // 65-minute lookback
async function run() {
// ── 1. Find tasks that flipped to overdue in the last 65 minutes ──────────
let recentlyOverdue;
try {
recentlyOverdue = await Task.findAll({
attributes: ['task_id', 'task_list_id', 'name'],
where: {
status: 'overdue',
updatedAt: { [Op.gte]: new Date(Date.now() - WINDOW_MS) },
},
raw: true,
});
} catch (err) {
console.error('[CRON][USER NOTIFY] Failed to query recently overdue tasks:', err);
return;
}
if (recentlyOverdue.length === 0) return;
console.log(`[CRON][USER NOTIFY] ${recentlyOverdue.length} recently overdue task(s) — resolving affected users.`);
// ── 2. Resolve affected users via task_list_groups → user_group_members ───
try {
const taskListIds = [...new Set(recentlyOverdue.map(t => t.task_list_id))];
const affectedUsers = await sequelize.query(
`SELECT DISTINCT ugm.user_id
FROM task_list_groups tlg
JOIN user_group_members ugm ON ugm.group_id = tlg.group_id
AND ugm."deletedAt" IS NULL
WHERE tlg.task_list_id IN (:taskListIds)`,
{ replacements: { taskListIds }, type: QueryTypes.SELECT }
);
if (affectedUsers.length === 0) return;
const count = recentlyOverdue.length;
const now = new Date();
const notify = NOTIFICATION_REGISTRY.user_task_overdue.build({ count, task_list_ids: taskListIds });
await UserNotification.bulkCreate(
affectedUsers.map(({ user_id }) => ({
user_id,
...notify,
seen: false,
createdAt: now,
updatedAt: now,
})),
{ validate: false }
);
console.log(`[CRON][USER NOTIFY] Notified ${affectedUsers.length} user(s) about ${count} overdue task(s).`);
} catch (err) {
console.error('[CRON][USER NOTIFY] Failed to emit user notifications:', err);
}
}
module.exports = {
name: 'userNotifications',
schedule: '5 * * * *',
run,
};