Firestore Security Rule limit field entry - firebase

In my Firestore document, I am trying to limit the fields that the users could update to only the ones I allow. I thought about a function that could check if request.resource.keys() contains any keys that are not the ones I allow, if it does, I block update, if not, I allow update. so far, my function looks like this:
function field_limit() {
let allow_keys = ["field1", "field2", "field3"];
return request.resource.keys() in allow_keys;
}
inside the path match:
allow update: if field_limit();
When I run a unit test in Emulator that modifies "field1", it does not pass. I think I am doing something wrong here because I am comparing the whole keys list against the allow_keys list.
What should I do in order to achieve the desired functionality then?
I thought about a solution to iterate through request.resource.keys(), and see if each key is in allow_keys. If one isn't, block update. But how to traverse through each value of a list?
edit. Should be request.resource.data.keys() instead of request.resource.keys(). Otherwise, it won't work.

The in operator doesn't work the way you're expecting. See the documentation for that - it only checks if a single value is in a list.
If you want to check if a list of values contains only a subset of values in another list, you should use hasOnly instead.
request.resource.keys().hasOnly(allow_keys)
This will evaluate true if the list request.resource.keys() contains only values from the allow_keys list.

Related

firebase query - find doc where map value is in an array

I'm trying to find the id of a doc where a map value in an array of maps equals "x".
in the following case, I'm trying to find which rep owns the cause with code "hog"
I'll likely be going down the denormalizing route, but is this possible?
Firestore has an array-contains operator that you can use to query whether a certain item exists in an array field, but that operator only works if you specify the exact, complete value of the field. It can't test for a partial match.
The common approach to your use-case is to add an additional array field with just the values you want to query on, i.e.
cause-codes: ["hog"]
Once you modified your documents with this additional field, you can then use a query like:
repsRef.where('cause-codes:', 'array-contains', 'hof')

How can I limit and sort on document ID in firestore?

I have a collection where the documents are uniquely identified by a date, and I want to get the n most recent documents. My first thought was to use the date as a document ID, and then my query would sort by ID in descending order. Something like .orderBy(FieldPath.documentId, descending: true).limit(n). This does not work, because it requires an index, which can't be created because __name__ only indexes are not supported.
My next attempt was to use .limitToLast(n) with the default sort, which is documented here.
By default, Cloud Firestore retrieves all documents that satisfy the query in ascending order by document ID
According to that snippet from the docs, .limitToLast(n) should work. However, because I didn't specify a sort, it says I can't limit the results. To fix this, I tried .orderBy(FieldPath.documentId).limitToLast(n), which should be equivalent. This, for some reason, gives me an error saying I need an index. I can't create it for the same reason I couldn't create the previous one, but I don't think I should need to because they must already have an index like that in order to implement the default ordering.
Should I just give up and copy the document ID into the document as a field, so I can sort that way? I know it should be easy from an algorithms perspective to do what I'm trying to do, but I haven't been able to figure out how to do it using the API. Am I missing something?
Edit: I didn't realize this was important, but I'm using the flutterfire firestore library.
A few points. It is ALWAYS a good practice to use random, well distributed documentId's in firestore for scale and efficiency. Related to that, there is effectively NO WAY to query by documentId - and in the few circumstances you can use it (especially for a range, which is possible but VERY tricky, as it requires inequalities, and you can only do inequalities on one field). IF there's a reason to search on an ID, yes it is PERFECTLY appropriate to store in the document as well - in fact, my wrapper library always does this.
the correct notation, btw, would be FieldPath.documentId() (method, not constant) - alternatively, __name__ - but I believe this only works in Queries. The reason it requested a new index is without the () it assumed you had a field named FieldPath with a subfield named documentid.
Further: FieldPath.documentId() does NOT generate the documentId at the server - it generates the FULL PATH to the document - see Firestore collection group query on documentId for a more complete explanation.
So net:
=> documentId's should be as random as possible within a collection; it's generally best to let Firestore generate them for you.
=> a valid exception is when you have ONE AND ONLY ONE sub-document under another - for example, every "user" document might have one and only one "forms of Id" document as a subcollection. It is valid to use the SAME ID as the parent document in this exceptional case.
=> anything you want to query should be a FIELD in a document,and generally simple fields.
=> WORD TO THE WISE: Firestore "arrays" are ABSOLUTELY NOT ARRAYS. They are ORDERED LISTS, generally in the order they were added to the array. The SDK presents them to the CLIENT as arrays, but Firestore it self does not STORE them as ACTUAL ARRAYS - THE NUMBER YOU SEE IN THE CONSOLE is the order, not an index. matching elements in an array (arrayContains, e.g.) requires matching the WHOLE element - if you store an ordered list of objects, you CANNOT query the "array" on sub-elements.
From what I've found:
FieldPath.documentId does not match on the documentId, but on the refPath (which it gets automatically if passed a document reference).
As such, since the documents are to be sorted by timestamp, it would be more ideal to create a timestamp fieldvalue for createdAt rather than a human-readable string which is prone to string length sorting over the value of the string.
From there, you can simply sort by date and limit to last. You can keep the document ID's as you intend.

firestore map key-value pairs randomly change order when retrieved from the database

I have a button which runs the following code when it's clicked:
let dataReference = await db.collection("dog").doc("1").get()
let HashMap = dataReference.data().Annotations
console.log(HashMap)
My firestore database looks like this:
Whenever this function is run, it returns the proper dictionary, however, the ordering of the keys seems to change randomly. Here's a screenshot of my console logs when I pressed the button a bunch of times:
Why does the ordering of the key-value pairs change and is there a way to fix it?
The Firestore SDK does not guarantee an order of iteration of document fields. What you see in the console is always lexically sorted by the code of the console itself. If you require a stable ordering, you should sort them yourself before iteration.
One workaround you can do is to have the order of the keys you want in an array. Because arrays are ordered it will maintain the order you desired. Then you take that key and use it in the dictionary. While the dictionary is out of order, you will be accessing each value in order by key.

How to update value of element in an array stored as a FieldValue in Firestore

I stored an Array as a FieldValue in one of my document in Firestore. Now I want to update one of its element value. As per the documentation I can use arrayRemove or arrayUnion function to remove the element from array but I didn't see any method to update the value of element. Is there any way that I can help me to update the elements' value.
Here I stored array in "patients" field of document. This array represents list of patients. Look at 0th position of element. Here status's value is "current". I want to update this to "processed". Is there anyway that I can do so.
Also is there any way that I can query the elements of array on the basis of the value placed on status key.
As per the documentation I can use arrayRemove or arrayUnion function to remove the element from an array.
There is no way you can use one of those functions to add or remove elements from an array that contains objects. To be able to use those functions, your array should contain for example literal strings. In that way, you can add or remove one of the elements using arrayRemove() or arrayUnion() functions.
I didn't see any method to update the value of an element.
There is no update method. arrayUnion() method will add a new element in the array only if it does not exist. If you want to update an element, you have to remove it first from the array and then add the new one. This is also available in the case of strings and not in the case of objects.
Is there any way that I can help me to update the elements' value.
Yes, two ways in which you can achieve this. The first one would be to get the entire document, get the patients property as a list of hashmaps, iterate through its elements, make the desired changes and write the document back. The second one would be to transform that array into a subcollection where each patient will become a document. In this way, you can simply update add or remove a document using the corresponding functions.
Also is there any way that I can query the elements of the array on the basis of the value placed on the status key.
Using your current document structure, no. If you want to query the patients of a user based on the value of a specific property, you should definitely use the second approach. There is no way you can achieve this using your actual schema.

Firebase OrderByChild return extra empty child

I'm trying to make query with orderBy and I get another empty child.
component .ts
loadCourses(){
this.profileProvider.getSpecificCourse().on('value',f=>{
console.log(f.val());
})
}
provider
getSpecificCourse(){
return firebase.database().ref(`CoursesRelated`).orderByChild('cs').equalTo(2);
}
firebase structure
getting the another empty node
expected result:
the same result without the empty node.
in addition I want to make another orderby and get the ls equal to 6, how can I do that?
You're using numeric indexes to store the nodes, which means that Firebase SDK interprets them as an array. For the results: { "0": {...}, "2": {...} it then creates a three element array [{...}, null, {...}].
For a good, longer explanation of how Firebase deals with arrays, read this blog post Best Practices: Arrays in Firebase.
The way to get rid of this behavior is to not use sequential numeric keys. The simplest way to do this is to either add the items by calling Firebase's push() method (which generates keys in a format -K... and is explained here), or by prefixing the numbers with a fixed string (e.g. Course0, Course1, Course2). Since these format are strings, they bypass the Firebase SDK's array-coercion logic.

Resources