import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' const LOVE = 'love' interface SwipeEntry { action?: string swipedAt?: number } /** * Creates a revealed date match when both partners have swiped LOVE on the * same date idea. * * Trigger: couples/{coupleId}/date_swipes/{dateIdeaId} (onWrite) * * The `date_matches` collection is server-write-only — Firestore rules deny all * client writes (`allow create, update, delete: if false`). This trigger is * therefore the single source of truth for match creation. The client only * records swipes and observes `date_matches` for the result. * * Idempotency: the match document id is the date idea id and creation runs in a * transaction, so repeated swipes on the same idea and concurrent invocations * never produce a duplicate match. */ export const createDateMatchOnMutualLove = functions.firestore .document('couples/{coupleId}/date_swipes/{dateIdeaId}') .onWrite(async (change, context) => { const after = change.after.data() if (!after) return // swipe document was deleted const actions = (after.actions ?? {}) as Record const lovedBy = Object.entries(actions) .filter(([, entry]) => entry?.action === LOVE) .map(([uid]) => uid) .sort() // A match needs both partners to have loved the same idea. if (lovedBy.length < 2) return const { coupleId, dateIdeaId } = context.params as { coupleId: string dateIdeaId: string } const db = admin.firestore() const matchRef = db .collection('couples') .doc(coupleId) .collection('date_matches') .doc(dateIdeaId) await db.runTransaction(async (tx) => { const existing = await tx.get(matchRef) if (existing.exists) return // already matched — no-op tx.set(matchRef, { dateIdeaId, matchedBy: lovedBy, revealedAt: admin.firestore.FieldValue.serverTimestamp(), }) }) })