How to get field ref data in firebase in single call? - firebase

I have below firestore collections.
-Converstions(collection)
(document) {participants: {userid1: true, userid2: true}, messages: [subcollection]}
-Users(collection)
(document)(userid1){userName: 'Test1', ...}
(document)(userid2){userName: 'Test2', ...}
Now I need to query for conversations a users is in, I can do this with
firebase.firestore().collection('conversations')
.where(`participants.${uid}`, '==', true);
What this does is gets all conversation a users is participating in, I need to now get the user details from id for each document in those conversation. If we make another call to UserRef to get the user details it will make extra request for each conversation data. I wanted to know if there is easy way to get user details in single call to the firebase.

When a user is added to a document, you could also add some display information about that user (either from the app or Cloud Functions).
There is no way to return data referenced elsewhere. You either need to duplicate or make multiple fetches.

Related

Firebase - Showing users that they have unread messages

I'm trying to determine the best way to handle showing the user that they have an unread message, in the navbar for example.
Currently I have separate documents for each conversation with data like so:
users: [ 'userId-1', 'userId-2' ]
messages: [
{
message: 'Test message',
timestamp: 12345678910,
userId: 123456
},
// etc...
]
Currently I'm thinking about adding an unread property to the message objects. Then, on page load, I would have to fetch each document where users contains the currentUser id and if any of the message objects in messages contains the unread: true property.
But then I would have to mark the message as read, but only for one of the users. So my data structure already doesn't work.
Also, this doesn't seem very performant to me, especially if the user has a great amount of conversations. Any idea on how to approach this differently?
I'm trying to determine the best way to handle showing the user that
they have an unread message, in the navbar for example
I understand that you only want to show a number of unread messages (or the information that there is a least one unread message). If this is the case you can get advantage of the new count() aggregation which takes into account any filters on the query.
Your data model is not 100% clear to me but since you have an Array of users, you could have an extra Array field containing the users that haven't read the message. So on page loading, you need to build the query of all messages where this array contains the currentUser uid and then call getCountFromServer() on this query.
Instead of being charged for each message that corresponds to the query you'll be charged one document read for each batch of up to 1000 index entries matched by the query.

How do I implement a follower system with private accounts in Firebase?

I'm building an an app in Firebase with a user feature and I need to implement a system to allow:
A user to follow another user
A user to see a list of the users they're following
A user to set their profile as private so that some of their data is only visible to the people following them
A user to be able to send a follow request to a user with a private profile
A user with a private profile to be able to accept/reject follow requests
So far I've made a Firestore collection at the root called users. When a user signs up with Firebase Auth, a document is made in users with the following structure:
user (document)
username: stringaccountIsPrivate: boolean
userData (collection)
userData (document)
where all the data that would be hidden if the account were private is in the userData document.
I'm not sure how I could implement the system to fulfill my requirements from here so that I could use Firestore rules to only allow followers of a private account to view that account's userData. I would appreciate it if anyone could suggest an appropriate data structure and an outline of how to write rules for this.
For this kind of situation, you must maintain two sources of truth, one for the creator and one for the user. this is done with an array of strings in both that have the user_uid and any additional information concatenated.
The goal is to have an array of CSV-like values of which you can split and render within your app.
create a concat string: entry = [user.uid, user.name, user.url].join(';');
return string to object: entry.split(';');
Doing the following ensures that only a unique entry exists
db.doc("user/user_id/userData/followers").set({ followers: Firestore.FieldValue.ArrayUnion(entry)}, {merge: true});
This is only a rough example and some backend logic will be needed to scale large - but with this, you have a theoretical limit of 200k entries depending on how much data you want to store in the string.
Additional logic would involve cloud functions reading and writing when a request to follow has been created which handles a counter that creates new documents as needed and ensure's that the counter is updated to prevent overflow since Security Rules can't do any recursive logic or manipulate the request directly.

Check References in Array - Firestore Security Rules

I am building firestore security rules for my app and in the events collection I have a property called members that has an array of references from the users collection. How would I go about making sure that the user that has sent the request is in that collection? I know I am able to get the userId through request.auth.uid but I'm unaware of how to get the document reference in firestore rules and make sure that the reference is in the array.
The answer that I have found is this:
match /events/{eventId} {
allow read: if /databases/$(database)/documents/users/$(request.auth.uid) in resource.data.members;
}
Looks like the in keyword allows me to check if a value is inside an array and /databases/$(database)/documents/users/$(request.auth.uid) creates a DocumentReference which is the data type stored in the array.
As clarified in this other post here, similar to yours, Firestore rules are not used for filtering data, but to set which data are accessible for which users and which queries can be performed.
Considering that, you will need to write code that will query and compare the datas from your request.auth.uid, with the ids from your subcollection. This way, you will be able to confirm the data you want, that is the user requesting being authorized to access the information. This would be the correct way to handle the request and return the information or not from your database.
A simple example of code that will confirm that the requesting users is in the members subcollection is similar to the following lines:
var user = firebase.auth().currentUser;
db.collection("events").where("members", "array-contains", user.uid).get()
While this code is untested, is a starting point for what you will need to do, to guarantee that the user requesting is allowed to retrieve the information. You can get more information on what you need here.
Let me know if the information helped you!

Firebase Cloud Firestore request

I have a MESSAGERECAP collection which includes a unique id for each message, the id of the receiver of the message, the id of the sender and the message itself. In my application, when the user clicks on a friend to chat with him, I want the chat activity to start with the list of messages they have both sent.
I did this but obviously it does not give the desired result :
Query query = messageRef.orderBy("mssgId",Query.Direction.DESCENDING);
// with messageRef a reference to the MESSAGERECAP collection
Here is an overview of my database
You are getting the whole list because you are not filtering the data, just ordering it. If you check the Querying documentation for Firestore, also provided by #FrankVanPuffelen on the comments of your question, you can see that you have to use .where() to filter the data that you want to retrieve.
Also, as pointed out by #Jay in the comments, you can use Compound Queries to create a logical AND on your query to retrieve the data you want.
So if you do something like:
messageRef.where("senderId", "==", [currentUserId])
.where("receiver_id", "==", [receiverUserId])
.orderBy("mssgId",Query.Direction.DESCENDING)
When you execute this query you will get all the messages sent by the current user to the receiving user of the correponding id.

How to write to a document and read the id of it within a single transaction in Firestore?

I am doing the user authentication where I have this case:
Read from vendor_type document and if it returns null(doesn't exist) then continue the transaction,
Create new user using .auth().createUserWithEmailAndPassword(email,password),
Read the new users ID,
Write to vendor_type document some of the new user's detail such as name, surname, userId -->> userId is the problem, how can I create a user and get the ID within a single transaction, can I even do that? ,
Take the newly created ID of the user, and create a new vendor document with that ID.
So far I don't have any code to post because I don't know if this is even gonna work so I didn't start. If you have any idea how to implement this, please let me know. The main issue is getting the user ID while still in the transaction.
At the time of writing, it is not possible to combine in one transaction the creation of a user through the createUserWithEmailAndPassword() method from the Auth service AND a write to the Firestore service.
They are two different services offered by Firestore and therefore you cannot combined calls to these two different services in one transaction.

Resources