All files reveal.ts

100% Statements 26/26
100% Branches 8/8
100% Functions 4/4
100% Lines 22/22

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 582x 2x 2x 2x   2x 1x   2x                     2x             4x   4x 4x           4x 1x     3x 3x   3x 4x 2x   1x 2x     1x       1x      
import { getApps, initializeApp } from "firebase-admin/app";
import { FieldValue, getFirestore } from "firebase-admin/firestore";
import { onDocumentWritten } from "firebase-functions/firestore";
import { logger } from "firebase-functions";
 
if (getApps().length === 0) {
  initializeApp();
}
const db = getFirestore();
 
/**
 * Server-authoritative reveal for the daily question.
 *
 * Each partner writes their own answer to `.../pulses/{pulseId}/answers/{uid}`,
 * which (per security rules) only they can read. When BOTH members have
 * answered, this trigger copies the answers into the parent pulse's `revealed`
 * map — the only place either partner can read the other's answer. There is no
 * client-side unlock (decision record §3).
 */
export const revealPulse = onDocumentWritten(
  {
    // Must match the Firestore database region (europe-west2) for the Eventarc trigger.
    document: "couples/{coupleId}/pulses/{pulseId}/answers/{answerUid}",
    region: "europe-west2",
  },
  async (event) => {
    const { coupleId, pulseId } = event.params;
 
    const pulseRef = db.doc(`couples/${coupleId}/pulses/${pulseId}`);
    const [coupleSnap, answersSnap, pulseSnap] = await Promise.all([
      db.doc(`couples/${coupleId}`).get(),
      pulseRef.collection("answers").get(),
      pulseRef.get(),
    ]);
 
    if (pulseSnap.get("revealedAt")) {
      return; // already revealed; nothing to do
    }
 
    const members: string[] = coupleSnap.get("members") ?? [];
    if (members.length < 2) return;
 
    const answered = new Set(answersSnap.docs.map((d) => d.id));
    const bothAnswered = members.every((m) => answered.has(m));
    if (!bothAnswered) return;
 
    const revealed = Object.fromEntries(
      answersSnap.docs.map((d) => [d.id, d.get("text") ?? null]),
    );
 
    await pulseRef.set(
      { revealed, revealedAt: FieldValue.serverTimestamp() },
      { merge: true },
    );
    logger.info(`Revealed pulse ${pulseId} for couple ${coupleId}`);
  },
);