60 lines
2.0 KiB
TypeScript
60 lines
2.0 KiB
TypeScript
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 }
|
|
})
|