All files / src/cron staleRooms.js

100% Statements 40/40
100% Branches 15/15
100% Functions 5/5
100% Lines 32/32

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                2x 2x     18x 17x 106x         19x   19x   19x     18x 21x   21x 18x 5x     18x   14x 14x 14x 112x     14x   14x 15x                       15x 15x 16x               14x 14x 14x 14x 31x   14x     14x     2x  
/**
 * Cron: Close rooms in OWNER_AWAY state for >10 minutes.
 *
 * Queries rooms with state=='OWNER_AWAY', filters by ownerLeftAt < 10min ago,
 * updates state to CLOSED, clears seats and participantIds,
 * and clears users' currentRoomId.
 */
 
const { db } = require('../utils/firebase');
const log = require('../utils/log');
 
function hasNonOwnerSeated(room) {
  if (!room.seats) return false;
  return Object.values(room.seats).some(
    (seat) => seat.userId && seat.userId !== room.ownerId && seat.state === 'OCCUPIED',
  );
}
 
async function staleRooms() {
  const tenMinutesAgo = Date.now() - 10 * 60 * 1000;
 
  const snapshot = await db.collection('rooms').where('state', '==', 'OWNER_AWAY').limit(100).get();
 
  if (snapshot.empty) return;
 
  // Close immediately if no non-owner seats occupied; otherwise wait 10 minutes
  const toClose = snapshot.docs
    .map((d) => ({ id: d.id, ...d.data() }))
    .filter((r) => {
      if (!r.ownerLeftAt) return false;
      if (!hasNonOwnerSeated(r)) return true;
      return r.ownerLeftAt < tenMinutesAgo;
    });
 
  if (toClose.length === 0) return;
 
  const timestamp = Date.now();
  const emptySeat = { userId: null, state: 'EMPTY', isMuted: false };
  const emptySeats = {};
  for (let i = 0; i < 8; i++) emptySeats[String(i)] = { ...emptySeat };
 
  // Collect all writes (room updates + participant currentRoomId clears)
  const writes = [];
 
  for (const room of toClose) {
    writes.push({
      ref: db.doc(`rooms/${room.id}`),
      data: {
        state: 'CLOSED',
        closedAt: timestamp,
        ownerLeftAt: null,
        seats: emptySeats,
        participantIds: [],
      },
    });
 
    // Clear currentRoomId for all participants
    const pids = room.participantIds || [];
    for (const pid of pids) {
      writes.push({
        ref: db.doc(`users/${pid}`),
        data: { currentRoomId: null },
      });
    }
  }
 
  // Batch write in chunks of 500
  for (let i = 0; i < writes.length; i += 500) {
    const batch = db.batch();
    const chunk = writes.slice(i, i + 500);
    for (const w of chunk) {
      batch.set(w.ref, w.data, { merge: true });
    }
    await batch.commit();
  }
 
  log.info('cron', 'staleRooms: closed stale OWNER_AWAY rooms', { count: toClose.length });
}
 
module.exports = staleRooms;