I'am creating a chat app with the following requirements. Each chat is related to an event in real life.
- anyone can join/leave a chat which are multi-users chats (provided he knows the chat Id)
- anyone can post messages after joining
- anyone can indicate if he is attending/waiting/rejecting the related event =>
attendance
- User can only list chats they have an attendance to.
The idea is: if someone gave me the ID of a chat I can join him and afterwards it will be in my list but I cannot list all chats
Option 1This solution seems a bit dirty but the model would be (not all fields)
- /chat(collection)--- creator_id--- members [Array of userId]--- attendance [Array of user attendances Objects]- /messages (sub collection)--- sender_id
Rules:
match /chat/{chatId} { allow create : request.resource.data.creator_id == request.auth.uid; allow read : if request.auth != null allow list : if request.auth.uid in resource.data.members allow update : editingOnlyAllowedFields(["members", "attendance"]); match /messages/{messageId} { // Use same as chat for get/list allow create : request.resource.data.sender_id == request.auth.uid; allow update : resource.data.sender_id == request.auth.uid;}}function editingOnlyAllowedFields(allowedFields) { let editedKeys = request.resource.data.diff(resource.data).affectedKeys(); return editedKeys.hasOnly(allowedFields);}// Taken from https://stackoverflow.com/a/69140909/3979236
This makes it easy front side to get chats the user is member of (userId in-array members) but this also allows anyone to remove users or change attendance which is problematic.
Option 2Model:
- /chat(collection)--- creator_id--- /attendance (sub collection)----- userId (== document Id)----- status--- /messages (sub collection)----- sender_id
Rules:
function isChatMember(chatId) { return request.auth != null && exists(/databases/$(database)/documents/chats/$(chatId)/attendance/$(request.auth.uid)) }match /chat/{chatId} { allow get if request.auth != null allow list if isChatMember(chatId) // But this makes a Read on the subcollection. Another option is to have a Cloud Function creating an array member on chat when /attendance/userId is created allow write if request.resource.data.creator_id == request.auth.uid match /attendance/{userId} { allow write if userId == request.auth.uid // Use same as chat for get/list match /messages/{messageId} { // Use same as chat for get/list allow create : request.resource.data.sender_id == request.auth.uid; allow update : resource.data.sender_id == request.auth.uid;}}
This solution seems to works but "Rules are not filters" and I think we cannot from the front app make a request to get chats for which attendance.userId exists
. Said otherwise we cannot use a where filter on a subcollection.
Solution 1? : Cloud Function to maintain the array of member directly on the chat objectSolution 2? : compound query on attendance to get the chatIds, then request with the IDs (but there might be a lot).