mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -23,7 +23,6 @@
|
||||
* Currently registered:
|
||||
* - taskOverdue (cron/jobs/task_overdue.cron.js) — settings-backed
|
||||
* - liftExpiredBans (cron/jobs/lift_expired_bans.cron.js)
|
||||
* - dispatchEmailBroadcasts (cron/jobs/dispatch_email_broadcasts.cron.js)
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 17, 2026
|
||||
@@ -31,7 +30,6 @@
|
||||
const cron = require('node-cron');
|
||||
const taskOverdue = require('./jobs/task_overdue.cron');
|
||||
const liftExpiredBans = require('./jobs/lift_expired_bans.cron');
|
||||
const dispatchEmailBroadcasts = require('./jobs/dispatch_email_broadcasts.cron');
|
||||
const { startSettingsBackedJobs } = require('./cronRegistry.util');
|
||||
|
||||
// ─── Registry — add future admin-side cron jobs here ─────────────────────────
|
||||
@@ -42,7 +40,6 @@ const settingsBackedJobs = [
|
||||
// Plain hardcoded-schedule jobs (not tied to any notification setting).
|
||||
const plainJobs = [
|
||||
liftExpiredBans,
|
||||
dispatchEmailBroadcasts,
|
||||
];
|
||||
|
||||
// ─── Boot all registered admin-side jobs ──────────────────────────────────────
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name : dispatch_email_broadcasts.cron.js
|
||||
* Type : Cron Job
|
||||
* Description : Sends real, paced SMTP email for queued email_broadcasts —
|
||||
* the actual delivery half of controllers/admin/email_broadcasts
|
||||
* .controller.js's createEmailBroadcast(), which only ever
|
||||
* enqueues rows and returns immediately.
|
||||
*
|
||||
* Every tick, picks up to BATCH_SIZE 'pending' recipient rows
|
||||
* (oldest broadcast first, FIFO within it) and sends them one
|
||||
* at a time with a short delay between each — this is the "one
|
||||
* by one, not a blocking for-loop in the API request" behavior:
|
||||
* it's fine to block *here* because nothing is waiting on an
|
||||
* HTTP response, and the delay keeps us well under Gmail SMTP's
|
||||
* practical sustained-send pacing.
|
||||
*
|
||||
* Resumable by construction — if the process restarts mid-
|
||||
* broadcast, the next tick just keeps consuming whatever rows
|
||||
* are still 'pending'. Single-instance only: this does not use
|
||||
* row-level locking, so running more than one app instance
|
||||
* would let two ticks grab the same batch. Fine for the
|
||||
* current single-process deployment; would need SELECT ... FOR
|
||||
* UPDATE SKIP LOCKED before scaling horizontally.
|
||||
*
|
||||
* Schedule : Every minute ("* * * * *"). Registered by cron/admin.cron.js.
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jul. 3, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const mdl_EmailBroadcast = require('../../models/email_templates/email_broadcast.mdl');
|
||||
const mdl_EmailBroadcastRecipient = require('../../models/email_templates/email_broadcast_recipient.mdl');
|
||||
const mdl_EmailTemplate = require('../../models/email_templates/email_templates.mdl');
|
||||
const { sendEmail } = require('../../services/email.service');
|
||||
|
||||
const BATCH_SIZE = 25;
|
||||
const DELAY_MS = 600; // pacing between individual sends — keeps us well under Gmail's throttling threshold
|
||||
|
||||
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const recipients = await mdl_EmailBroadcastRecipient.findAll({
|
||||
where: { status: 'pending' },
|
||||
include: [{
|
||||
model: mdl_EmailBroadcast,
|
||||
as: 'broadcast',
|
||||
where: { status: ['queued', 'sending'] },
|
||||
include: [{ model: mdl_EmailTemplate, as: 'template' }],
|
||||
}],
|
||||
order: [['email_broadcast_id', 'ASC'], ['email_broadcast_recipient_id', 'ASC']],
|
||||
limit: BATCH_SIZE,
|
||||
});
|
||||
|
||||
if (!recipients.length) return;
|
||||
|
||||
const touchedBroadcastIds = new Set();
|
||||
|
||||
for (const recipient of recipients) {
|
||||
const broadcast = recipient.broadcast;
|
||||
const template = broadcast?.template;
|
||||
|
||||
if (!broadcast || !template) {
|
||||
await recipient.update({ status: 'failed', error: 'Broadcast or template no longer exists.', sent_at: new Date() });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (broadcast.status === 'queued') {
|
||||
await broadcast.update({ status: 'sending', started_at: broadcast.started_at ?? new Date() });
|
||||
}
|
||||
touchedBroadcastIds.add(broadcast.email_broadcast_id);
|
||||
|
||||
try {
|
||||
await sendEmail({
|
||||
to: recipient.email,
|
||||
type: template.type,
|
||||
data: { name: recipient.name || 'there', email: recipient.email },
|
||||
});
|
||||
await recipient.update({ status: 'sent', sent_at: new Date() });
|
||||
await broadcast.increment('sent_count');
|
||||
} catch (err) {
|
||||
await recipient.update({ status: 'failed', error: err.message, sent_at: new Date() });
|
||||
await broadcast.increment('failed_count');
|
||||
console.error('[CRON][EMAIL BROADCAST] Send failed:', recipient.email, err.message);
|
||||
}
|
||||
|
||||
await delay(DELAY_MS);
|
||||
}
|
||||
|
||||
// Close out any broadcast that has no pending recipients left.
|
||||
for (const broadcastId of touchedBroadcastIds) {
|
||||
const remaining = await mdl_EmailBroadcastRecipient.count({ where: { email_broadcast_id: broadcastId, status: 'pending' } });
|
||||
if (remaining === 0) {
|
||||
await mdl_EmailBroadcast.update(
|
||||
{ status: 'completed', completed_at: new Date() },
|
||||
{ where: { email_broadcast_id: broadcastId, status: ['queued', 'sending'] } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[CRON][EMAIL BROADCAST] Processed ${recipients.length} recipient(s) across ${touchedBroadcastIds.size} broadcast(s).`);
|
||||
} catch (err) {
|
||||
console.error('[CRON][EMAIL BROADCAST] Failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'dispatchEmailBroadcasts',
|
||||
schedule: '* * * * *',
|
||||
run,
|
||||
};
|
||||
Reference in New Issue
Block a user