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 | 2x 2x 2x 2x 1x 2x 2x 7x 7x 5x 5x 3x 3x 2x 2x 1x 1x 1x 1x | import { getApps, initializeApp } from "firebase-admin/app";
import { FieldValue, getFirestore } from "firebase-admin/firestore";
import { HttpsError, onCall } from "firebase-functions/https";
if (getApps().length === 0) {
initializeApp();
}
const db = getFirestore();
/**
* Pair the caller with the partner who minted `code`. Couples are created
* server-side only (security rules forbid client writes to `couples`), so
* membership can never be forged. A couple is two symmetric members.
*
* Skeleton: expects an `invites/{code}` doc `{ inviterId }`. Hardening to do —
* single-use codes, expiry, rate limiting, already-paired guards.
*/
export const pairWithCode = onCall<{ code: string }>({ region: "europe-west2" }, async (request) => {
const uid = request.auth?.uid;
if (!uid) throw new HttpsError("unauthenticated", "Sign in first.");
const code = request.data.code?.trim().toUpperCase();
if (!code) throw new HttpsError("invalid-argument", "Missing invite code.");
const inviteSnap = await db.doc(`invites/${code}`).get();
if (!inviteSnap.exists) throw new HttpsError("not-found", "That code doesn't look right.");
const inviterId = inviteSnap.get("inviterId") as string;
if (inviterId === uid) throw new HttpsError("failed-precondition", "You can't pair with yourself.");
const coupleRef = db.collection("couples").doc();
await coupleRef.set({
members: [inviterId, uid],
createdAt: FieldValue.serverTimestamp(),
togetherSince: FieldValue.serverTimestamp(),
});
await inviteSnap.ref.delete();
return { coupleId: coupleRef.id };
});
|