How can I segregate user data in firebase? - swift

I'm creating an app that has uses a firebase Cloud Firestore database. The structure seems to be collection/document/fields. I am thinking of either using the user id as the prefix to the collection name or simply a field for userId.
I'm currently using:
Firestore.firestore().collection("Events").
This could be changed to prefix event with the userId
I am currently reading everything in using:
reference(to: "Events").addSnapshotListener{ (snapshot, _) in
guard let snapshot = snapshot else {return}
for document in snapshot.documents {
// code here
}
}

It's not a really good idea to use prefixes on collection names. That doesn't work well with security rules.
The usual structure for per-user data is to have a collection with documents whose IDs are the user IDs. Then, you can further organize other data in subcollections under that document ID.
/users
/uid1
/uid2
/events
/likes
/history
Then, you can write security rules using the user's UID very easily.

Related

Firestore security rules - how to secure LIST requests by request.query checks on ACL access control list?

I want to secure a collection of accounts documents with a field access.users that contains an array of user DocumentReferences which are allowed to access an account document in the collection.
accounts.access.users = [ //array of user document references ]
In my query (JS client library) I am setting the query:
db.collection('accounts').where('access.users', 'array-contains', userRef)
To secure the data, I want to write a rule:
function userHasAccountAccess () {
let user = getUser(); // returns users document reference based on auth uid
// - here - need to check that the users document reference was requested by the query ie - that `request.query` contains the `access.users` field and that value of this filter in an array/list which includes a reference to the users' document
}
match /accounts/{docId} {
list: if userHasAccountAccess();
}
... but it seems from the docs that the only properties available on a query are limit, offset and orderBy, so then I am unable to test or secure this way.
So how are others securing their data in this type of access role ACL scenario for LIST type requests?
So after some digging, I found the answer.
Where posting a query (LIST) request as so:
db.collection('accounts').where('access.users', 'array-contains', userRef).limit(5)
... it seems that limit, orderBy and offset become properties of request.query in the security rules, but the where filters become properties of resource.data.
This is confusing because a) resource.data is usually a map of document data being posted (ie when saving records) and b) the docs describe resource.data as such.

how to use firestore wherefield method and adding document that collection to another collection

I am working a project to use shops. My structure data:
My code snippet
//current user sign in here and uid "VjZfKF1zmrV00C9VeTZxRmogybA2"
let currentUser = Auth.auth().currentUser
let db = Firestore.firestore()
db.collection("Blah").whereField("users", isEqualTo: currentUser?.uid).collection("otherPath").document().addDocument
I want to add data to use currentuser uid and if it is matching "Blah" inside documents then add that targeting document other collection.
.adddocumets or .setData it isn't allowed to firestore, so how can i figure it?
There is no way to send a query and an update statement to Firestore in one go. You will instead have to:
Execute a query to determine all documents that need to be updated.
Loop over the results of that query in your application code.
Update each document in turn inside that loop.
In addition, since users is an array field, you'll want to use the array-contains operator to match the correct documents.

Flutter Firebase delete a document of a subcollection using the document ID

Apparently, I was able to retrieve a subcollection data from firebase by using the code below
FirebaseFirestore.instance.collectionGroup('announcementlist').where('id', isEqualTo: '${docID}');
However, I am now trying to delete a subcollection by using this collectionGroup method and it is not working. I want all admin user to be able to delete the subcollections.
So what I found online is something similar to this line of code.
// define document location (Collection Name > Document Name > Collection Name >)
var docRef = Firebase.firestore().collection("Rooms").doc("bsYNIwEkjP237Ela6fUp").collection("Messages");
// delete the document
docRef.doc("lKjNIwEkjP537Ela6fhJ").delete();
which I got from codegrepper but as far as I know, the doc("bsYNIwEkjP237Ela6fUp") part requires the id of the user which is not the method I want because all admin users should be able to delete the data and it uses collection instead of collectiongroup
Is there any solution for this ? Please help.
FirebaseFirestore.instance.collection("Rooms").doc(snapshot.requireData.docs[i].id).collection("Messages").doc(snapshot.requireData.docs[i].id).delete();

Firebase Documents with Sub-Collections in Swift using Codable

I want to create a simple SwiftUI app that uses FireStore and has a collection "recipes" and a collection "users" for user specific data. In the users collection, I want to add a document for every user that holds a sub-collection with the favourite recipes of the user.
On the client side, I am using something like this for the user data:
import FirebaseFirestoreSwift
import Foundation
struct Account: Codable {
#DocumentID var id: String?
var favoriteRecipes = Set<Recipe>()
}
Now if I write the document to firebase using the Codable support, it creates a Map for the set of recipes (which is fine I guess, I just want to have it another way).
So I obviously can handle the sub-collection "manually" and use it like any other stand-alone collection.
Nevertheless, I am wondering if there is some sort of "Best-Practice" for handling sub-collections in Firebase with Codable in Swift?
Thanks a lot!
There are two parts to your question. Let me try to answer them individually.
How to handle nested data structures in Firestore / Codable?
Any attribute on a Codable struct will be mapped against the respective attribute on a Firestore document (you have some influence over thus by using the CodingKeys enum - see this article.
Nested types will be mapped to dictionaries on the document, whereas arrays and other sequences will be mapped to arrays on the document.
In order to retrieve a sub-collection of a document, you will need to perform a separate fetch request. Firestore doesn't support fetching a nested tree of documents/sub-collections on the client. It's a different story on the server, though. Check out Renaud's article to learn more about this.
How to store user-specific data?
For any user=specific data, I would recommend one of the following two ways to structure your data:
Storing as a sub-collection
In this scenario, we have one top-level collection users, which contains documents for all your users (let Firestore auto-generate the document IDs for you, and store Firebase Auth's user ID as an attribute on the respective user document.
/(root)
+ users <-- (collection)
+ 0FABQ...RiGg <-- (user document)
- uid: "6ZPt...BLiK3fnl2" <-- (Firebase Auth user ID)
- name: "Johnny Appleseed" <-- (attribute)
+ recipes (collection) <-- (sub-collection)
+ A69EF...4EFA <-- (recipe document)
- name: "Scones" <-- (attribute)
+ FCED...12D5 <-- (another user document)
You can then use the user's ID (from Firebase Auth) to query all the user's recipes.
Storing as a top-level collection
In this scenario, we have two top-level collections: one for all your users, and another one for all the recipes. In order to distinguish a user's recipes, each recipe doc has a uid attribute which contains the respective user's user ID:
/(root)
+ users <-- (collection)
+ 0FABQ...RiGg <-- (user document)
- uid: "6ZPt...BLiK3fnl2" <-- (Firebase Auth user ID)
- name: "Johnny Appleseed" <-- (attribute)
+ FCED...12D5 <-- (another user document)
+ recipes (collection) <-- (collection)
+ A69EF...4EFA <-- (recipe document)
- uid: "6ZPt...BLiK3fnl2" <-- (Firebase Auth user ID)
- name: "Scones" <-- (attribute)
To retrieve a user's recipes, you query all recipes and filter for the ones that match the user ID of the currently signed in user.
To learn more about Firestore data modelling, check out the documentation, which also contains links to a number of really useful videos. Fireship also has a really good article about this.

Efficiently get user info for reviews collection query

I have a product document and a sub-collection of its reviews. Each review has the userId of the user that created the document. I want to efficiently get all users from those reviews without having to read each document beforehand.
I can query efficiently for those reviews but I can't think of a way to get the user info of the reviews i'm querying without doing a single query for the user after reading the document. Any Ideas?
productDocument.collection("reviews")
.getDocuments() { (querySnapshot, err) in
for reviewDocument in querySnapshot!.documents {
let review = reviewDocument.data()
let userId = review.senderId
usersCollection.document(userId).getDocument { (userDocument, error) in
if let userDocument = userDocument{
let user = userDocument.data()
print("\(user.name) said \(review.text)")
}
}
}
Cloud Firestore doesn't have any "join" type queries that can combine results from multiple collections. In your case, you are going to have to query and iterate each document in reviews, collect all the user IDs from those documents, then fetch each user document individually. There's no shortcut for this provided by the SDKs, though you might want to write a utility function of your own.
If this is unacceptable, consider adding another collection that you can query once that will give you everything you need. It is not uncommon to duplicate data in NoSQL type databases in order to optimize situations like this.