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 | 1x 1x 1x 1x 15x 15x 15x 1x 15x 1111x 1111x 15x 15x 1x 15x 502x 502x 15x 15x 4x 11x 12x 12x 12x 611x 12x 11x 11x 11x 11x 10x 8x 8x 8x 6x 7x 7x 6x 6x 6x 4x 4x 1x 1x | /**
* Cron job: expire bans whose expiresAt has passed.
*
* Queries deviceBans and networkBans for docs with a non-null expiresAt
* that is in the past, deletes them via batch writes, and optionally
* notifies admin via FCM using the shared utility.
*/
const { db } = require('../utils/firebase');
const { sendFcmToTokens, cleanupInvalidTokens } = require('../utils/fcm');
const log = require('../utils/log');
// Cap per-tick reads to keep Spark-tier quota bounded. The cron runs
// every 15min; if there are >CRON_LIMIT non-null-expiresAt bans, the
// remainder are processed on the next tick. We log a truncation warning
// so operators see the backlog. Pattern matches subscriptions.js cron.
const CRON_LIMIT = 500;
async function expireBans() {
const nowIso = new Date().toISOString();
// Query expired device bans (capped — see CRON_LIMIT comment)
const deviceSnap = await db
.collection('deviceBans')
.where('expiresAt', '!=', null)
.limit(CRON_LIMIT)
.get();
if (deviceSnap.size === CRON_LIMIT) {
log.warn('cron', 'expireBans: deviceBans hit CRON_LIMIT — possible truncation', {
limit: CRON_LIMIT,
});
}
const expiredDeviceDocs = deviceSnap.docs.filter((d) => {
const expiresAt = d.data().expiresAt;
return expiresAt && expiresAt < nowIso;
});
// Query expired network bans (capped — see CRON_LIMIT comment)
const networkSnap = await db
.collection('networkBans')
.where('expiresAt', '!=', null)
.limit(CRON_LIMIT)
.get();
if (networkSnap.size === CRON_LIMIT) {
log.warn('cron', 'expireBans: networkBans hit CRON_LIMIT — possible truncation', {
limit: CRON_LIMIT,
});
}
const expiredNetworkDocs = networkSnap.docs.filter((d) => {
const expiresAt = d.data().expiresAt;
return expiresAt && expiresAt < nowIso;
});
const allExpired = [...expiredDeviceDocs, ...expiredNetworkDocs];
if (allExpired.length === 0) {
return;
}
// Batch delete expired bans (max 500 per batch)
for (let i = 0; i < allExpired.length; i += 500) {
const batch = db.batch();
const chunk = allExpired.slice(i, i + 500);
for (const doc of chunk) {
batch.delete(doc.ref);
}
await batch.commit();
}
const removed = allExpired.length;
log.info('cron', 'expireBans: removed expired bans', { count: removed });
// Notify admin users via FCM
try {
const configSnap = await db.doc('alertConfig/settings').get();
if (!configSnap.exists) return;
const config = configSnap.data();
const recipientUserIds = config.fcmRecipientUserIds || [];
if (recipientUserIds.length === 0) return;
for (const userId of recipientUserIds) {
const userSnap = await db.doc(`users/${userId}`).get();
if (!userSnap.exists) continue;
const userData = userSnap.data();
const fcmTokens = userData.fcmTokens || [];
if (fcmTokens.length === 0) continue;
const invalidTokens = await sendFcmToTokens(fcmTokens, {
type: 'admin_notification',
title: 'Bans Expired',
body: `${removed} ban(s) have expired and been removed.`,
});
await cleanupInvalidTokens(invalidTokens, userId);
}
} catch (err) {
log.error('cron', 'expireBans notification error', { error: err.message });
}
}
module.exports = expireBans;
|