I am adding a chat to my app (without groups, only individuals chats (between 2 users)) where if one user deletes his full chat with other user, both users will not be able to read their previous conversation, likes happens on Telegram.
With this use case, I don't need database denormalization at all.
This is my NoSql model:
usernames/ (collection)
jack/ (document)
-userId: "41b932028490"
josh/ (document)
-userId: "212930294348"
users/ (collection)
41b932028490/ (document)
-avatar: {uri}
-username: jack
-...other data
212930294348/ (document)
-avatar: {uri}
-username: "josh"
-...other data
chats/ (collection)
chatId/ (document) <------------ (Some kind of chat room)
-members: ["41b932028490", "212930294348"]
messages/ (sub-collection)
message1Id/ (document)
-userId: "41b932028490" // sender
-type: "text"
-text: "hello world!"
message2Id/ (document)
-userId: "41b932028490" // sender
-type: "GIF"
-uri: "https://media.giphy.com/media/HP7mtfNa1E4CEqNbNL/giphy.gif"
The problem I am having is with the logic to create the chat rooms. As you can see, I have a field "members", with the ids of both users. What I have thought to do is just making a query and trying to find the room where both users (the message's sender and the message's receiver) are members, in the chats collection. If the room exists, I will only have to add the message. If not, I will have to create the room with the new first message.
For example, if a user "Jack" is going to send a first message to the user "Josh" (the chat room doesn't exist yet), the server will firstly create a chat room (with both users as members), and the new first message as data.
But... what would happen if both users send the message at the same time? (Inconsistencie: 2 chat rooms might be created). To avoid those kinds of inconsistencies, I am running this logic atomically, in a database transaction, like this:
await firestore.runTransaction((transaction) => {
// Does the chat room exists in the chats' collection?
const chatsRef = firestore.collection("chats");
return chatsRef
.where("members", "array-contains", fromId) <--------- PROBLEM!
.where("members", "array-contains", toId) <----------- PROBLEM!
.limit(1)
.get()
.then((snapshot) => {
if (!snapshot.empty) {
// If yes, add the message to the chat room
const chatRoom = snapshot.docs[0];
const chatRoomPath = chatRoom.ref.path;
const newMessageRef = firestore
.doc(chatRoomPath)
.collection("messages")
.doc(); // Auto-generated uuid
const newMessageData = {
userId: fromId,
message,
date: admin.firestore.FieldValue.serverTimestamp(),
};
transaction.set(newMessageRef, newMessageData);
} else {
// TODO - Check that the receiver exists in the database (document read)
// TODO - If exists -> create chat room with the new message
// TODO - If not exists -> throw error
}
// TODO - Increment the chat counter for the receiver when adding the message
});
});
But, the docs says
you can include at most one array-contains or array-contains-any clause in a compound query.
This is where i'm stuck, as I need to find a room with both users as members... and the only other way I can think of is to perform two queries (an OR)
where("member1", "==", fromId).where("memeber2", "==", toId)
// and
where("member1", "==", toId).where("memeber2", "==", fromId)
How can I make this work with a single query? Any ideas?
Thank you.
Instead of using an array, I have used a map where the key is the user's id, and the value a true boolean.
members = {
"832384798238912789": true,
"90392p2010219021ud": true,
}
Then I query like:
.where(`members.${fromId}`, "==", true)
.where(`members${toId}`, "==", true)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With