Closer/functions/src/dates/createDateMatch.ts

63 lines
2.0 KiB
TypeScript

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<string, SwipeEntry>
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(),
})
})
})