firestore security rules - create new user and check if name is free - flutter

to create a new user (via email authentication) I have to allow everything in firestore, is there a better way?
match /users/{userId=**} {
allow get, list;
}
Code:
I check whether the name is already taken
Future<bool> doesNameAlreadyExist(String name) async {
final QuerySnapshot result = await FirebaseFirestore.instance
.collection('users')
.where('name', isEqualTo: name)
.limit(1)
.get();
final List<DocumentSnapshot> documents = result.docs;
return documents.length == 1;
}
authentication then takes place later when the user presses the login button:
final auth = Provider.of(context)!.auth!;
String uid = await auth.createUserWithEmailAndPassword(
emailController.text,
passwordController.text,
nameController.text,
);

As mentioned in the Answer:
Enforcing uniqueness is only possible by creating an extra collection.
In your current structure, to know if a username is unique, you will
need to read each document. This is incredibly inefficient, and on top
of that it isn't possible in security rules, since they can only read
a few documents per rule.
The trick is to create an extra collection
of usernames, where you also have a document for each user, but now
the key/ID of each document is the username. With such a collection,
you can check for the existence of a certain document, which is a
primitive operation in the security rules.
For information you can check some similar scenarios - case_1 , case_2 and case_3.

Related

How can I get a collection inside a QuerySnapshot

On the explore page, I get() the entire users collection to create a user list and search results. Inside each of those user documents is a collection posts that I also need to get to create a GridView of each post. I want to reuse that users collection QuerySnapshot instead of fetching each posts collection again to save money. Is this possible?
Here is my current function:
void fetchUsers() async {
final userRef = FirebaseFirestore.instance.collection('users');
final QuerySnapshot result = await userRef.get();
final docs = result.docs.asMap();
docs.forEach((index, value) {
final profile =
ProfileObject.fromJson(value.data() as Map<String, dynamic>);
usersList.add(UserSearchResult(profile, value.id));
/// Below is the code for getting the posts, not working, need ideas
final QuerySnapshot postsResult = value.get('posts');
final posts = postsResult.docs.asMap();
posts.forEach((index, value) {
final post = Post.fromJson(value.data() as Map<String, dynamic>);
postsList.add(post);
});
});
print(usersList);
print(postsList);
}
Here is the structure of my Firestore:
users
uid (doc)
posts (collection)
info (fields)
uid (doc)
posts (collection)
info (fields)
It is not possible to call a collection to get all sub-collections. You should restructure your database to include sub-collection data in document itself. You can use a map or list for that. But remember, calling everything in one go may end up in slow performance and you might end up losing your customers. So the best way is to include the info in every posts' documents. That way, you won't loss your money and user won't feel lag in performance.
It is not possible. You fetch a document, then fetch the (sub)collection under it.
Subcollection data are not included in the initial document snapshots because Firestore queries are shallow. There shouldn't be any cost savings that you can do there?
See the similar Q&A:
Firestore: Get subcollection of document found with where

How to arrange documents in Firestore using Flutter through custom document IDs?

I want to order the documents in Firestore. The default Firestore documents list consist of alphabetic characters which get created automatically. But I don't want that. I just want to see my newly added document added at the top of my documents list. How do I do that in flutter? It would be very helpful if you provide me with a code for that. The code I use to create a collection is:
Future<void> userSetup() async {
String user = FirebaseAuth.instance.currentUser?.displayName as String;
CollectionReference users = FirebaseFirestore.instance.collection(user);
final hours = time?.hour.toString().padLeft(2, '0');
final minutes = time?.minute.toString().padLeft(2, '0');
users.add({
"customerId": FirebaseAuth.instance.currentUser?.uid.toString(),
"customerName": FirebaseAuth.instance.currentUser?.displayName,
"customerEmail": FirebaseAuth.instance.currentUser?.email,
"selectedTime": '${hours}:${minutes}',
"selectedDate": DateFormat('dd/MM/yyyy').format(date!),
});
return;
}
But I am unable to set my own document id. Please help me with the issue. Thanks in Advance
From the Flutterfire documentation, the set() method is the one you should be using to be able to specify your own document IDs instead of add(). Keep in mind that if the document ID you specify already exists in your database, the whole existing document will be replaced. This is a sample usage as found in the documentation:
CollectionReference users = FirebaseFirestore.instance.collection('users');
Future<void> addUser() {
return users
.doc('ABC123')
.set({
'full_name': "Mary Jane",
'age': 18
})
.then((value) => print("User Added"))
.catchError((error) => print("Failed to add user: $error"));
}
It seems that documents are ordered alphabetically in the Firestore console, so your custom document IDs should follow alphabetical order as you require. Not to be confused with retrieving documents from Firestore in a particular order, which is done with the orderBy() method.

Using fields in a document for another query

I have 2 collections, one called Timeline and one called Posts. The first one is very simple, having 2 fields: 'PostId' and 'OwnerId', while the second one is a little bit more complex but it is not important for the purpose of my question.
Using 'OwnerId' and 'PostId' I can get a specified post in the collection Posts.
What I want to do is getting all the docs in timeline of a specified user, for each doc use it to get the post infos in Posts collection, and order the posts in descending timestamp, but I can't find a smart and effective way to do so.
To get all the docs of a specified user in Timeline I write:
QuerySnapshot snapshot = await timelineRef
.doc(currentUserID)
.collection('timelinePosts')
.get();
And to get a specified post from Posts collection I write:
QuerySnapshot snapshot = await postsRef
.doc(ownerId)
.collection('userPosts')
.doc(postId)
.get();
How can I mix these two to get the result I want? Thank you
There is no concept of a server-side join in Firestore, nor is there a way to filter the documents returned based on information in documents in another collection. All Firestore queries can do is evaluate the literal data in the candidate documents (through an index) and filter based on that.
So you will either have to duplicate the data to filter on in each userPosts document, or perform a so-called client-side join - with the latter being the most reasonable option for this use-case as far as I can see.
You'll end up with individual get() calls for the documents, or a bunch in in queries on the FieldPath.documentId() you get from timelinePosts, and then merge the results in your application code.
At the moment I found a solution that is not very elegant but at least is working:
QuerySnapshot snapshot = await timelineRef
.doc(widget.currentUser.userID)
.collection('timelinePosts')
.orderBy('timestamp', descending: true)
.get();
List<TimelineItem> timelineItems =
snapshot.docs.map((doc) => TimelineItem.fromDocument(doc)).toList();
List<PostWidget> postsTemp = [];
for (var element in timelineItems) {
DocumentSnapshot documentSnapshot = await postsRef
.doc(element.ownerId)
.collection('userPosts')
.doc(element.postId)
.get();
postsTemp.add(PostWidget(Post.fromDocument(documentSnapshot)));
}
I added timestamp field to my timelinePosts, created a class to contain the data from the first query, and then I did a second query based on the parameters I got on the first one for each doc.
Hopefully I'll find a more efficient solution but at the moment I use this

how to delete document by field inside collection inside document inside collection on firestore

I have Doctor collection when each doctor (represent by his email ) has a collection called patients_waiting.
Now, what I'm trying to do is delete one document from the paitents_waiting collection by field calls patients containing his email.
but I tried many solutions and none of them works for me now.
what I have tried to do :
Firestore.instance
.collection("Doctors")
.document(doctorEmail)
.collection("paitents_waiting")
.document(paitentEmail)
.delete();
now it's not good because the document is saved in uid and not by email but I tried to play with the where function but with no success.
how do I found this document by email and delete him?
I will mention that I'm doing it on flutter, but I think it doesn't matter.
as long as you have the patient's email address you can search with and delete it
Firestore.instance
.collection("Doctors")
. document(doctorEmail)
.collection("paitents_waiting")
.where('patient', isEqualTo:paitentEmail )
.get().then((value) => value.docs.single.reference.delete())
Nb: you are using an old version of firestore
Inside your paitents_waiting collection, the documents are NOT named according to patient email, they are randomly generated Firebase IDs. Take a closer look.
Firestore.instance
.collection("Doctors")
.document(doctorEmail)
.collection("paitents_waiting")
.document(paitentEmail) //this in your Firebase isn't an email, it's "xaErf43Asd..etc"
.delete();
If you want to follow this approach, which should be working otherwise, when you want to create a patient waiting document, use .set instead of .add, and set the document id to your pateint's email, like this:
Firestore.instance
.collection("Doctors")
.document(doctorEmail)
.collection("paitents_waiting")
.document(paitentEmail)
.set({your patient data here});
This should get things working for you.
To delete all patients for a doctor by the email you can use a combination of Firestore query and batch updates. The first one we need to get all patients with the same email and the other to delete them. A function for that would look like this:
Future<void> deletePatiensForDoctor(String docEmail, String patEmail) async {
WriteBatch batch = FirebaseFirestore.instance.batch();
QuerySnapshot querySnapshot = await Firestore.instance
.collection("Doctors")
.document(docEmail)
.collection("paitents_waiting")
.where('email', isEqualTo: patEmail)
.get();
querySnapshot.docs.forEach((doc) {
batch.delete(doc.reference);
});
return batch.commit();
}

CollectionReference to DocumentReference

I'm trying to perform a firestore transaction in Flutter.
Therefore I need to pass the documentReference.
There is no problem, when accessing the "lesson" document based on the identifier.
The issue is when fetching the active user pass. The where I'm trying to use returns the Query, and the .reference() returns the CollectionReference (which is not accepted by the transaction method).
How can I get the reference to the document basing on the value of its field.
To spice it up: DB rules don't allow me to read ALL the passes. I've got only access to those, that have my UserID (double checked - working).
DocumentReference lessonRef =
await _db.collection('lessons').document(lesson.identifier);
CollectionReference passRef = await _db
.collection('passes')
.where('userID', isEqualTo: user.identifier).reference();
You can get the DocumentReference of the Queried documents like so:
Firestore.instance
.collection('passes')
.where('userID', isEqualTo: user.identifier)
.snapshots()
.listen((reference) =>
data.documents.forEach((doc) => /*do what you want with doc.reference*/));
(I'm not an expert in Flutter and I could't test the code, but I based the answer in the API reference)