import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' /** * HTTPS callable that atomically unlinks a couple. * * The client cannot do this directly because Firestore rules prevent a user * from writing the partner's user document. The Admin SDK bypasses those rules. * * Steps: * 1. Verify the caller is a member of their current couple. * 2. Clear coupleId on both user docs (batch — atomic). * 3. Recursively delete the couple doc and all its subcollections. * * The existing onCoupleLeave Firestore trigger fires after step 2 and handles * partner notification, so we don't duplicate that here. */ export const leaveCoupleCallable = functions.https.onCall(async (_data, context) => { const callerId = context.auth?.uid if (!callerId) { throw new functions.https.HttpsError('unauthenticated', 'Must be signed in.') } const db = admin.firestore() const userDoc = await db.collection('users').doc(callerId).get() const coupleId = userDoc.data()?.coupleId as string | undefined if (!coupleId) { // Already unpaired — idempotent success. return { success: true } } const coupleRef = db.collection('couples').doc(coupleId) const coupleDoc = await coupleRef.get() if (!coupleDoc.exists) { // Couple doc gone — just clear caller's field. await db.collection('users').doc(callerId).update({ coupleId: null }) return { success: true } } const userIds = (coupleDoc.data()?.userIds ?? []) as string[] if (!userIds.includes(callerId)) { throw new functions.https.HttpsError('permission-denied', 'Not a member of this couple.') } // Clear coupleId for all members atomically. const batch = db.batch() for (const uid of userIds) { batch.update(db.collection('users').doc(uid), { coupleId: null }) } await batch.commit() // Recursively delete the couple document and every subcollection beneath it. await db.recursiveDelete(coupleRef) console.log(`[leaveCoupleCallable] user ${callerId} left couple ${coupleId}`) return { success: true } })