Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 19x 19x 2x 2x 17x 2x 2x 1x 17x 2x 2x 1x 2x 2x 17x 2x 17x 1x 1x 1x 17x 1x 1x 17x 1x 1x 17x 1x 1x 17x 17x 17x 17x 1x 17x 17x 3x 3x 2x 2x 1x 17x 1x | const cron = require('node-cron');
const log = require('../utils/log');
const archiveReports = require('./archiveReports');
const subscriptions = require('./subscriptions');
const staleRooms = require('./staleRooms');
const backpackCleanup = require('./backpackCleanup');
const backups = require('./backups');
const closedRooms = require('./closedRooms');
const orphanedStorage = require('./orphanedStorage');
const rotateLogs = require('./rotateLogs');
const expireBans = require('./expireBans');
const expireTempIds = require('./expireTempIds');
const serverHealth = require('./serverHealth');
const accountDeletion = require('./accountDeletion');
const expireDataExports = require('./expireDataExports');
const alertManager = require('../utils/alertManagerInstance');
const dispatchNotifications = require('./notification-dispatch');
const ageVerificationAuditReconcile = require('./ageVerificationAuditReconcile');
const backfillRoadmapOptedIn = require('./backfillRoadmapOptedIn');
function startCronJobs() {
const isProd = process.env.NODE_ENV === 'production';
// All cron jobs are prod-only. Dev testing happens against local emulators
// which have no quota limits, so cron cleanup is unnecessary. Running cron
// on dev was burning ~4k Firestore deletes/day for no benefit.
if (!isProd) {
log.info('cron', 'Cron jobs disabled (non-production environment)');
return;
}
// Archive old reports — Sunday 03:00 UTC
cron.schedule('0 3 * * 0', () => {
log.info('cron', 'Running archiveReports');
archiveReports().catch((err) =>
log.error('cron', 'archiveReports failed', { error: err.message }),
);
});
// Check expired subscriptions + clean expired backpack items + expire temp IDs — daily midnight UTC
cron.schedule('0 0 * * *', () => {
log.info('cron', 'Running subscriptions + backpackCleanup + expireTempIds');
subscriptions().catch((err) =>
log.error('cron', 'subscriptions failed', { error: err.message }),
);
backpackCleanup().catch((err) =>
log.error('cron', 'backpackCleanup failed', { error: err.message }),
);
expireTempIds().catch((err) =>
log.error('cron', 'expireTempIds failed', { error: err.message }),
);
});
// Close stale OWNER_AWAY rooms — every 5 minutes
cron.schedule('*/5 * * * *', () => {
staleRooms().catch((err) => log.error('cron', 'staleRooms failed', { error: err.message }));
});
// Backup user profiles + cleanup old closed rooms — daily 02:00 UTC
cron.schedule('0 2 * * *', () => {
log.info('cron', 'Running backups + closedRooms');
backups().catch((err) => log.error('cron', 'backups failed', { error: err.message }));
closedRooms().catch((err) => log.error('cron', 'closedRooms failed', { error: err.message }));
});
// Cleanup orphaned storage — daily 04:00 UTC
cron.schedule('0 4 * * *', () => {
log.info('cron', 'Running orphanedStorage');
orphanedStorage().catch((err) =>
log.error('cron', 'orphanedStorage failed', { error: err.message }),
);
});
// Rotate logs — every hour
cron.schedule('0 * * * *', () => {
log.info('cron', 'Running rotateLogs');
rotateLogs().catch((err) => log.error('cron', 'rotateLogs failed', { error: err.message }));
});
// Expire bans — every 15 minutes
cron.schedule('*/15 * * * *', () => {
log.info('cron', 'Running expireBans');
expireBans().catch((err) => log.error('cron', 'expireBans failed', { error: err.message }));
});
// Account deletion — daily 03:00 UTC
cron.schedule('0 3 * * *', () => {
log.info('cron', 'Running accountDeletion');
accountDeletion().catch((err) =>
log.error('cron', 'accountDeletion failed', { error: err.message }),
);
});
// Expire data exports — daily 04:00 UTC
cron.schedule('0 4 * * *', () => {
log.info('cron', 'Running expireDataExports');
expireDataExports().catch((err) =>
log.error('cron', 'expireDataExports failed', { error: err.message }),
);
});
// Backfill roadmapUpdateOptedIn flag on legacy subscriptions — daily 04:30
// UTC. Idempotent + self-stopping (no-op once every doc has the field
// populated). Phase 2A finding #2 — see backfillRoadmapOptedIn.js header
// for the migration rationale.
cron.schedule('30 4 * * *', () => {
log.info('cron', 'Running backfillRoadmapOptedIn');
backfillRoadmapOptedIn().catch((err) =>
log.error('cron', 'backfillRoadmapOptedIn failed', { error: err.message }),
);
});
// Server health check — every 5 minutes
cron.schedule('*/5 * * * *', () => {
serverHealth(alertManager).catch((err) =>
log.error('cron', 'serverHealth failed', { error: err.message }),
);
});
// Dispatch queued suggestion notifications — every 2 minutes
cron.schedule('*/2 * * * *', () => {
dispatchNotifications().catch((err) =>
log.error('cron', 'notification-dispatch failed', { error: err.message }),
);
});
// Age-verification audit-log reconciliation — daily 05:00 UTC.
// Back-fills missing audit entries for decisions whose post-commit
// audit write failed (compliance gap fix). 7-day scan window so a
// multi-day Firestore outage is fully covered. Idempotent on
// re-runs via `details.fromSubmissionId` markers.
//
// Per-doc failures are isolated inside the job (see `failed`
// counter); only a catastrophic crash (Firestore unreachable,
// permission revoked, code bug) reaches this `.catch`. That kind
// of failure leaves the OSA/GDPR remediation gap unresolved for
// 24+ h, so route it through alertManager instead of relying on
// log-grep.
cron.schedule('0 5 * * *', () => {
log.info('cron', 'Running ageVerificationAuditReconcile');
ageVerificationAuditReconcile().catch((err) => {
log.error('cron', 'ageVerificationAuditReconcile failed', { error: err.message });
alertManager
.createAlert(
'compliance_cron_failed',
'critical',
'Age-verification audit reconcile cron crashed',
'Daily back-fill of missing age-verification audit-log entries did not complete. OSA/GDPR remediation paused until the next successful run. Investigate immediately.',
{ error: err.message, cron: 'ageVerificationAuditReconcile' },
)
.catch((alertErr) =>
log.error('cron', 'alertManager.createAlert failed', { error: alertErr.message }),
);
});
});
log.info('cron', 'Cron jobs scheduled');
}
module.exports = { startCronJobs };
|