rules_version = '2'; // Hogar compartido de MealMood (issue #33). Ver docs/household-sync.md. // // Regla de oro: el contenido de un hogar solo lo ven y lo escriben sus // miembros. Lo único legible desde fuera es un código de invitación concreto, // y solo por id — nunca listando la colección. service cloud.firestore { match /databases/{database}/documents { function signedIn() { return request.auth != null; } function uid() { return request.auth.uid; } function isMember(householdId) { return signedIn() && uid() in get(/databases/$(database)/documents/households/$(householdId)).data.memberIds; } // Perfil mínimo: a qué hogar pertenece cada persona y su nombre visible. match /users/{userId} { allow read, write: if signedIn() && uid() == userId; } // Códigos de invitación. `get` abierto a cualquier persona autenticada — // es la única forma de resolver un código antes de ser miembro. `list` // queda prohibido para que nadie pueda barrer códigos ajenos. match /invites/{code} { allow get: if signedIn(); allow list: if false; allow create, delete: if signedIn() && request.resource.data.createdBy == uid(); allow update: if false; } match /households/{householdId} { allow get: if isMember(householdId); allow list: if false; // Al crear, la persona queda como dueña y único miembro. allow create: if signedIn() && request.resource.data.ownerId == uid() && request.resource.data.memberIds == [uid()]; // Dos actualizaciones legítimas: // - un miembro cambia datos del hogar (nombre, código de invitación), // - alguien de fuera se añade a sí mismo con un código válido. allow update: if isMember(householdId) || joiningWithValidInvite(householdId); allow delete: if signedIn() && resource.data.ownerId == uid(); function joiningWithValidInvite(householdId) { let code = request.resource.data.lastJoinCode; let invitePath = /databases/$(database)/documents/invites/$(code); return signedIn() && exists(invitePath) && get(invitePath).data.householdId == householdId && get(invitePath).data.expiresAt > request.time // Solo puede añadirse a sí misma, y sin tocar a nadie más. && request.resource.data.memberIds == resource.data.memberIds.concat([uid()]); } match /members/{memberId} { allow read: if isMember(householdId); // Cada persona escribe su propia ficha; nadie edita la de otro. allow write: if signedIn() && uid() == memberId; } // Contenido del hogar: platos, etiquetas, semanas, slots y compra. match /dishes/{dishId} { allow read, write: if isMember(householdId); } match /tags/{tagId} { allow read, write: if isMember(householdId); } match /weekPlans/{weekKey} { allow read, write: if isMember(householdId); match /slots/{slotId} { allow read, write: if isMember(householdId); } } match /shoppingItems/{itemId} { allow read, write: if isMember(householdId); } } } }