rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // ── Helpers ────────────────────────────────────────────────────────────── function isSignedIn() { return request.auth != null; } function isOwner(uid) { return isSignedIn() && request.auth.uid == uid; } function isCouplesMember(coupleId) { return isSignedIn() && request.auth.uid in get(/databases/$(database)/documents/couples/$(coupleId)).data.userIds; } // ── Users ───────────────────────────────────────────────────────────────── // Each user owns exactly their own document. match /users/{uid} { allow read, write: if isOwner(uid); } // ── Invite codes ────────────────────────────────────────────────────────── // Any authenticated user can create or read an invite code. // Only status + acceptor fields may be updated (no re-writing the code). match /invites/{code} { allow read: if isSignedIn(); allow create: if isSignedIn(); allow update: if isSignedIn() && resource.data.status == 'pending' && request.resource.data.keys().hasOnly( ['status', 'acceptorUserId', 'acceptedAt', 'coupleId']); } // ── Couples ─────────────────────────────────────────────────────────────── // Only the two members of a couple may read or write couple data. match /couples/{coupleId} { allow read, write: if isCouplesMember(coupleId); match /sessions/{sessionId} { allow read, write: if isCouplesMember(coupleId); } // Question threads live under the couple document. match /question_threads/{threadId} { allow read, write: if isCouplesMember(coupleId); // Answers: each user writes their own; both members can read all answers. match /answers/{userId} { allow write: if isOwner(userId); allow read: if isCouplesMember(coupleId); } // Discussion messages: any couple member can write and read. match /messages/{messageId} { allow read, write: if isCouplesMember(coupleId); } // Reactions: any couple member can write and read. match /reactions/{reactionId} { allow read, write: if isCouplesMember(coupleId); } } } } }