All files / src/cron expireBans.js

90.9% Statements 40/44
72.22% Branches 13/18
100% Functions 3/3
97.43% Lines 38/39

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                1x 1x 1x     4x     4x   4x 3x 3x       4x   4x 2x 2x     4x   4x 2x       2x 2x 2x 2x 3x   2x     2x 2x     2x 2x 2x   1x 1x 1x   1x 1x 1x 1x 1x 1x   1x         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');
 
async function expireBans() {
  const nowIso = new Date().toISOString();
 
  // Query expired device bans
  const deviceSnap = await db.collection('deviceBans').where('expiresAt', '!=', null).get();
 
  const expiredDeviceDocs = deviceSnap.docs.filter((d) => {
    const expiresAt = d.data().expiresAt;
    return expiresAt && expiresAt < nowIso;
  });
 
  // Query expired network bans
  const networkSnap = await db.collection('networkBans').where('expiresAt', '!=', null).get();
 
  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 || [];
    Iif (recipientUserIds.length === 0) return;
 
    for (const userId of recipientUserIds) {
      const userSnap = await db.doc(`users/${userId}`).get();
      Iif (!userSnap.exists) continue;
      const userData = userSnap.data();
      const fcmTokens = userData.fcmTokens || [];
      Iif (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;