How to query Firestore documents filtered by roles defined in a subcollection - firebase

In the Security Rules! video #6 the suggestion is made to create a security rule using roles defined in a private_data sub collection with a single 'private' document:
allow update: if (get(/databases/$(database)/documents/restaurants/$(restaurantID)/private_data/private).data.roles[request.auth.uid] in ['editor', 'owner'];
Using the web API, how can I query the set of documents where a user has editor or owner permissions? I don't see that a where clause can reference sub-collection documents.
Something like
const q = query(
collection(db, 'restaurants'),
where(new FieldPath('private_data', 'private', 'roles', 'user_123'), 'in', ['editor', 'owner'])
);

Firestore queries can only filter documents based on the data in each document itself. There is no way to filter based on data in another document, either from the same collection or from another (sub)collection.
This means that while it is possible to restrict write and get (single-document read) operations based on the information in the roles subcollection, it is not possible to use those same documents in a query.
If you need this use-case, the common approach is to duplicate the necessary data from the subcollection(s) into the parent document, and the filter on it there. This complicating of data write to allow or simplify a certain read is a pattern you'll see quite regularly when dealing with NoSQL databases.

Related

Read data from firebase document with sub collections >> sub documents

Is it possible to get data of sub-documents by reading only the main document in Firestore? Basically, the Firebase listCollections() method gives the id of subcollections, but how can we get their data?
I have tried for listCollections() to get the id of sub-collections, but have not found a way to get data sub-collections data.
Is it possible to get data of sub-documents by reading only the main document in Firebase?
In Firestore, the queries are shallow. This means that it can only return documents from the collection that the query is run against. So there is no way you can get documents along with the data that exists inside sub-collections in one go. A single query can only read fields of documents in a single collection.
If you need to get documents from sub-collection, you need to perform separate queries. If you have collections or sub-collections that have the exact same name then you can use a collection group query.
Firebase listCollectionIds(),Firebase listDocuments() are well documented by firebase but nobody use and write on it....
I think the best way is to list subcollection to your document throught the well-know get() and then process a foreach on result snapshot
snapshot.forEach(doc =>{
console.log(doc.id)
console.log(doc.data())
})

how to query nested maps inside an array field in firebase

I have structured my data in Firebase such that I have a collection of 'users' and each user has various fields as well as a 'skillsIndustry' Array field which contain Maps items (that have fields 'experience' and 'industry').
Now I would like to query my collection of users so that I can ask for all users that have worked in 'Airlines' with experience code of >= 1
Is this possible at all? Or do I have to create sub collections and then use Collection Group Queries?
Many thanks
No, this is not possible. You can use array-contains to query for the entire object, as explained in this SO answer, but not to query with > or <.
You could create a subcollection, but you can probably find a solution based on the duplication of the data in the same document, which is a common approach in the NoSQL world.
For example, you could have some extra fields named airlinesExperience, shippingExperience, etc. Then you use the skillsIndustry Array field when you want to display all the skills and years of experience, but you use the xxxExperience fields when you want to query.
Another possibility would be to use a map of key/value pairs like
key = "Airlines" / value = 1
key = "Shipping" / value = 3
Then you can query with:
firebase
.firestore()
.collection('users')
.where('skillsIndustry.Shipping', '>', 2)

Allow users from different collection see a different stream

I have an Orders collection. It contains a field called venueId. And I'm querying against this field using isEqualTo. The venueId is the firebase user uid. I also have a venues collection. It contains this venueId and also has a list of VenueAdmins ids(These ids are also firebase user uids )The app is a point of sales app(pos). I need to query the orders collections so that valueAdmins and venueId see the correct stream. Is quite easy to query with venueId.. venueId,isEqualto, uid. I'm wondering what's the best approach to allow the venueAdmins see the stream as well.
|-Orders // collection
order. //doc
venueId:'2344567788999999'
|-Venues // collection
venue. //doc
venueAdmin: ['3333333333333','55555555555555555']
venueId:'2344567788999999'
My query builder so far: queryBuilder: (query) => query.where('venue.id', isEqualTo: uid)
Firestore does not have the capability to "join" documents from different collections in a single query. A single query can only consider documents in single collection at a time. The way you have your data structured now, it will require at least two queries. First, to find a venue, then second, to find the orders for an admin in a venue.
The only way to make this easier from the perspective of queries is to denormalize your data by duplicating venue data into the order documents. If each order also had a list of admins, then you could reduce this down to a single query.

Firebase Firestore: How to acess a collection from another collection

I am using Vue.js and Firebase Firestore. Now I have two collectionsusers and orders. In the orders collection, I have already stored the id of each document of user collection. I now have to fetch the details of the corresponding users from this. How am I supposed to go about it?
This is what I've done so far
let orderRef = db.collection("orders")
orderRef.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type == "added") {
let doc = change.doc;
this.orders.push({
id: doc.id,
orderData: doc.data().orderData,
user_id: doc.data().user_id,
userInfo: db.collection("users").doc(user_id),
});
}
});
});
I need to store user data in userInfo.Thanks in advance
Firestore doesn't support foreign key like SQL database, so you can't retrieve nested data like SQL.
In Firestore you need to fetch referenced data separately, either you can fetch all users data separately in parallel with orders data and store it in map, or if you don't need users data initially then fetch each users data when needed like when you check details of each order.
I think that you're structuring your data as if you were working in a relational database. Firestore is a no-SQL database that doesn't have any notion of reference, the only thing Firestore understands are key-values, documents and collections, everything else has to me modeled on top of that. See Firestore data model.
In Firestore relationships are usually modeled by storing the id of the document you'd like to "reference". In that sense you might not need to store the 'users' document inside the 'order' but the field 'user_id' would suffice. The caveat is that this data layout comes at the price of having to fetch the 'user_id' from orders before you can fetch the actual user data. You could read more about data denormalization and modeling relationships in articles link1, link2 and this other thread.
Also, it's worth noting that Firestore documents are limited in size to 1MB so with your actual configuration if the amount of info of 'user' documents increases it may get to a point where it would be necessary to reshape your documents structure.
All in all, if you don't want to change your data layout you would need to follow Ked suggestions and first retrieve the 'users' data to inline it into the 'userInfo' field.

Multitenancy in Firestore

Regarding the actual limitations in querying data based on subcollections values, what is the suggested way to manage multitenancy in Firestore?
I would like to be able to retrieve and limit access to data related to entities/companies the user is part of.
Example data structure :
/companies/{companyId}/users/
/companies/{companyId}/users/{user}/roles
/companies/{companyId}/docs/
Can /companies/{companyId}/users/ be a collection?
How can I only retrieve companies where user own a role in /companies/{companyId}/users ?
Firestore paths alternate from collection to document and back again:
/collection/document/subcollection/subdocument
So yes, in this case, you would have collections of companies, users, and docs. Collections are also implicit in that they are created automatically when documents exist in them, and removed when no documents exist in them.
At present, subcollection queries (e.g. "all users in a given company") aren't supported, so you'll have to structure your query the other way around: having a users collection with company as a property, when performing a query to find all users in that company.
ref.collection('users').where('company', '==', 'ACME').get().then((document) => {/* Do stuff here */});

Resources