Firebase security rules calulate sum of a list - firebase

I have a map like this in Firestore:
ratings:{
id1:{value:5,...rest of map}
id2:{value:3,...rest of map}
}
How could i calculate the sum of values (5+3=8) ?

There are no loop operations in the security rules programming model, as those wouldn't scale. So if you want to write a rule against the sum of the ratings, you'll have to store that sum as an explicit value in the document.

Related

With all Firebase products, Is it possible to return a positive integer with out repeat

Same as SQL, you can add a new row with the primary key being auto incremental.
We need a counter where we can get unique values ​​even if we call them at the same time. (the result should be n and n+1)
Is it possible for all firebase products to create a counter that returns you a value that is never the same?
Eg.
It's possible to tell that user from firebase authentication is the first, second or third from a register (the sequence should never change) if it's so it would be great
A counter that gives you a number that never is the same even I and others request at the same time.
Main purpose
I want a 1-1 map, a positive integer (UInt32) to firebase account UID
eg. 1: aksd12391, 2: da1293nvs1dks, 4: 1is91jesoc
Using Transaction's Firestore (Concurrency)
It still returns the same not unique
Is it possible to create a counter that
returns you a value that is never the same?
Yes, you can use a numeric field in a Firestore document that you (transactionally) increment as explained here in the doc.
It's possible to tell that user from Firebase authentication is the
first, second or third from a register (the sequence should never
change)
Yes, you can assign a unique value (obtained as explained above) to a user, by, for example, saving a user profile in Firestore and assigning this value as a "functional" id.

Is there a possibility to perform non-case sensitive "where" request to Firestore

I have a request to Firestore to check if the collection of tickets contains some duplicated IDs:
firestore.collection("tickets").where("extId", "==", "Test 2").get();
The problem is - where method looks up only for case-sensitive IDs.
Is there a chance I can return a document with test 2 or tesT 2 extId?
Unfortunately Firestore queries are case sensitive and there is no way to avoid this. Here are some approaches you could take to solve this problem depending on your use case:
1. Store several versions of your data (eg store both an upper and lower case version of your data)
2. Don't allow users to type in their query, and instead provide them with predefined filter items (for example chips, or a dropdown menu)
3. Do some client side processing before executing the query (eg. convert query strings into upper or lowercase to ensure it matches the format in the document)
4. Use a search engine platform on top of Firestore such as Algolia, Typesense, Meilisearch, among others as they contain typo-tolerance and other additional features
As also #Hydra mentioned in her answer, the queries in Firestore are case-sensitive, meaning that will always return documents with the exact match. If you want to get documents with test 2 OR tesT 2, then you should consider using in operator:
Use the in operator to combine up to 10 equality (==) clauses on the same field with a logical OR. An in query returns documents where the given field matches any of the comparison values.
In your particular case, the following query will do the trick:
firestore.collection("tickets").where("extId", "in", ["test 2", "tesT 2"]).get();

Update all documents in a Firestore collection except for a pre-defined number which is based off date created

I am working on a project that when a user cancels their plan, all their documents should be updated to deactivated except for a pre-defined number of documents that are allowed to stay active. The pre-defined number amount determines the projects allowed to stay active along with the date they were created.
For example, if customer A has 1,000 documents and cancels their plan, all their documents except for the first 100 created should be updated to be deactivated.
My first attempt was to get all document ids with .listDocuments() but I noticed the created date is not part of Firestore's DocumentReference. Therefore I can't exclude the pre-defined number of documents allowed to stay active.
I could use .get() and use the created value, but I'm afraid that getting all the documents at once (which could be a million) would cause my cloud function to run out of memory, even if I have it set to the maximum allowed configuration.
Another option that I thought of, I could use .listDocuments() and write each document id to a temp collection in Firestore, which could kick off a cloud function for each document. This function would only have to work with one document, so it wouldn't have to worry about running out of resources. I am unsure how to determine if the document I'm working on should be marked as deactivated or is allowed to stay active.
I am not that worried about the reads to write as this workflow should not happen very often. Any help would be appreciated.
Thank you
One possible approach would be to mark the documents to be excluded.
I don't know what is your exact algorithm, but if you want to mark the first 100 documents that were created in a collection you can use a Cloud Function that runs for each new document and checks if there are already 100 docs in the collection.
If not, you update a field in this new document with its rank (using a Transaction to get the highest existing rank and increment it). If there are already 100 documents previously created in the collection, you just update the field to 0, for example, in such a way that later on you can query with where("rank", "==", 0).
Then, when you want to delete all the docs but the 100 first ones, just use where("rank", "==", 0) query.
So, concretely:
The first doc is created: you set the rank field to 1.
The Nth doc (N != 1) is created: you fetch all the docs with a query ordered by rank and limited to 1 doc (collecRef.orderBy("rank", "desc").limit(1)) in a Transaction. Since you are in a Cloud Function, you can use a Query in the Transaction (which you cannot with the Client SDKs). Then, still in the Transaction:
If the value of rank for the single doc returned by the Query is < 100 you set the rank value of the newly created do to [single doc value + 1]
If the value of rank for the single doc returned by the Query = 100 you set the rank value to 0
If I didn't make any mistake (I didn't test it! :-)) you end with 100 docs with a value of rank between 1 and 100 (the 100 first created docs) and the rest of the docs with a value of rank equal to 0.
Then, as said above you can use the where("rank", "==", 0) query to select all the docs to be deleted.

Firestore rule to check the value of a dynamic field within a map

Think of a poll app, which has a map in a Firestore document.
This map has the vote key as string and a number as value (counter).
For instance “what is the best food”:
{
...
...
poll : [
pizza : 10,
barbecue: 3,
pasta: 1,
...
]
...
}
The items can vary depending on the poll. Only one vote per user. Only logged in users can vote (Firestore rule).
The minimum amount is zero, people can switch from one to another (decrease moment).
Also, the increase should be only +1.
Transactions protect the app to avoid less than 0 for any item and +1 is controlled there.
Is there a way to protect also with the Firestore rules?
To be honest, I’m not sure if it’s necessary.
I can get the affected items with map diff functions. But how do I check for that specific changed item value?
The map diff result brings a set, however I cannot use the keys retrieved by the set in the request.resource.data, otherwise the problem would be solved.
I could check whether it’s 0 and also check with current data if the difference is 1.
The key will be in the map for sure, since it comes from the diff function from the map itself.
Update Feb 2021: this doesn't work for the latest firebase since the keys are a set which can't be indexed
You can get the affected keys using
let affectedKeys = request.resource.data.poll.diff(resource.data.poll).affectedKeys();
However according to the firebase docs on MapDiff it's not possible to get the changes in values, only in keys.
If you know how many poll items there are, you can do something like
let differenceOfOne = request.resource.data.poll[affectedKeys[0]] == resource.data.poll[affectedKeys[0]] + 1 || request.resource.data.poll[affectedKeys[0]] == resource.data.poll[affectedKeys[0]] - 1
and repeat that line for every index up to the maximum number of items (loops are not allowed in firebase rules).

Creating a leaderboard scenario on Cloud Firestore

I'm playing with the recently introduced Cloud Firestore and I was wondering if it's possible to get a document's index in a collection to create a leaderboard.
For example, let's say I want to retrieve a user's position in the leaderboard. I'd do something like this:
db.collection('leaderboard').doc('usedId')
There, I'd have a rank field to display that user's position in the leaderboard. However, that means I'd have to create a Cloud Function to calculate users' position everytime their score changes. Considering Firestore charges by the number of CRUD operations, that could be really expensive.
Is there a better way to do it? Let's say define a query field (i.e. score), then get that document's index in the collection's array?
You can't find the position of a document within a query result except by actually running the query and reading all of them. This makes sorting by score unworkable for just determining rank.
You could reduce the number of writes for updating ranks by grouping ranks into blocks of 10 or 100 and then only updating the rank group when a player moves between them. Absolute rank could be determined by sorting by score within the group.
If you stored these rank groups as single documents this could result in significant savings.
Note that as you increase the number of writers to a document this increases contention so you'd need to balance group size against expected score submission rates to get the result you want.
The techniques in aggregation queries could be adapted to this problem but they essentially have the same cost trade-off you described in your question.
You can filter it in the database console. Top-right of the centre column, next to the 3 vertical dots.
You can also use this GitHub Repo to search for the query you need to insert into your code: https://github.com/firebase/snippets-web/blob/f61ee63d407f4a71ef9e677284c292b0a083d723/firestore/test.firestore.js#L928-L928
If you wanted to rank users based on their 'highScores', for instance, you could use something like the following for the top 10 (and create an ordered list or similar, to represent the user's rank):
db.collection("leaderboard")
.orderBy("highScore", "desc").limit(10) // this is the line you add to filter results
.get()
.then((snapshot) => {
snapshot.docs.forEach((doc) => {
console.log(doc.data());
renderLeaderboard(doc);
});
});

Resources