All files / src/utils block-check.js

100% Statements 10/10
100% Branches 12/12
100% Functions 2/2
100% Lines 9/9

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                                                                32x 30x 30x                                   51x 51x 51x 11x   40x     22x  
/**
 * Block-list helpers — predicates for enforcing user-level block rules.
 *
 * ShyTalk stores blocks as a `blockedUserIds: number[]` field on the
 * `users/{uniqueId}` Firestore doc. Block/unblock operations are written
 * directly to Firestore by the iOS/Android clients (no Express endpoint),
 * so the source of truth is always the user doc this module is handed.
 *
 * Two predicates live here:
 *
 *   - `viewerIsBlocked(viewerId, targetUser)` — one-direction, used by
 *     read endpoints (profile-view, gift-wall) where only the target's
 *     doc is already loaded. Returns true if the target has blocked the
 *     viewer, so the read should be refused.
 *
 *   - `checkBlockRelationship(sender, recipient, senderId, recipientId)`
 *     — symmetric, used by interaction endpoints (gift-send, backpack-send)
 *     where both docs are already loaded for other reasons (balance read
 *     + recipient existence). Returns an error string or null. The
 *     symmetric form is correct for interactions: if either side has
 *     blocked the other, the interaction must fail; a one-way block
 *     would otherwise let the blocker continue to push gifts.
 */
 
/**
 * One-direction block check for read endpoints.
 *
 * @param {number|string} viewerId  The caller's uniqueId (from req.auth.uniqueId)
 * @param {object|null} targetUser  The target user doc (or null/undefined)
 * @returns {boolean}                True if the target has blocked the viewer
 */
function viewerIsBlocked(viewerId, targetUser) {
  if (!targetUser) return false;
  const blocked = (targetUser.blockedUserIds || []).map(String);
  return blocked.includes(String(viewerId));
}
 
/**
 * Symmetric block check for interaction endpoints. Returns an error
 * string suitable for `res.status(403).json({ error })`, or null when
 * neither side blocks the other.
 *
 * Kept compatible with the original gift-route helper so existing
 * call sites do not need to change.
 *
 * @param {object} sender         Sender's user doc
 * @param {object} recipient      Recipient's user doc
 * @param {number|string} senderId
 * @param {number|string} recipientId
 * @returns {string|null}         Error string, or null if OK
 */
function checkBlockRelationship(sender, recipient, senderId, recipientId) {
  const senderBlocked = (sender?.blockedUserIds || []).map(String);
  const recipientBlocked = (recipient?.blockedUserIds || []).map(String);
  if (senderBlocked.includes(String(recipientId)) || recipientBlocked.includes(String(senderId))) {
    return 'Cannot send gifts to or from blocked users';
  }
  return null;
}
 
module.exports = { viewerIsBlocked, checkBlockRelationship };