Watch a collectionGroup with Firestore using Cloud Functions - firebase

I'm working on a Firestore DB that uses collectionGroups.
The collectionGroup in question is a collection of 'Fights'.
When a new fight is created I would like to use the onCreate method in a cloud function to watch for new 'Fight' entries and then add some meta data to them. Ideally It would look something like the pseudo code below
export const placeFightersInEvent = functions.firestore
.collectionGroup('fights/{fightId}')
.onCreate(async (fightSnapshot, context) => {
// get metadata and add to the newly created 'fight'
});
I'm using the most up to date versions of the firebase functions and admin sdk but I can't seem to find an available function to do this. Is it possible to watch collection groups in this way?

Currently this is not possible for fights subcollections at any depth. Please file a feature request with Firebase support if you need to do this.
However, if you are only ever working with a fights subcollections at a known depth, then this might work just as well anyway:
export const placeFightersInEvent = functions.firestore
.document('{coll}/{doc1}/fights/{doc2}')
.onCreate(async (fightSnapshot, context) => {
// get metadata and add to the newly created 'fight'
});
It should only trigger for new fights nested below documents in any top-level collection, so it is not a true collection group event handler. But you could just create new functions for each depth required.

Related

Firebase Cloud Function for Firestore does not work

I just switched from FirebaseDatabase to FireStore, so I have to rewrite some cloud functions to match with the FireStore syntax, but the problem is it does not work as the FirebaseDatabase one.
Here is my code:
exports.addMessageTest = functions.firestore.document('/messages/{id}').onCreate(async (req, res) => {
const original = req.query.text;
const writeResult = await admin.firestore().collection('messages').add({original: original});
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
Basically, I want to trigger an event whenever there is a new document create in the declared path, but what I noticed is that the function never triggered even I tried:
create a new document directly in the database
using swift to create a new document (successfully created, but did not trigger the function) try? db.collection("messages").document().setData(data)
So what seems to be the problem here?
You are coding the onCreate() background Cloud Function for Firestore as an HTTPS Cloud Function.
The req and res do not represent the Express.js request and response objects in a onCreate() background Cloud Function.
Have a look at the documentation: The first argument of the callback function is a DocumentSnapshot representing the Firestore doc which triggered the CF and the second argument is the context of the CF.
Also, note that background CFs should be terminated in a different way compare to HTTPS CFs. See the doc for more details since correctly managing the life cycle of your CFs is very important.

Firebase cloud functions: How to wait for a document to be created and update it afterwards

Here is the situation:
I have collections 'lists', 'stats', and 'posts'.
From frontend, there is a scenario where the user uploads a content. The frontend function creates a document under 'lists', and after the document is created, it creates another document under 'posts'.
I have a CF that listens to creation of a document under 'lists' and create a new document under 'stats'.
I have a CF that listens to creation of a document under 'posts' and update the document created under 'stats'.
The intended order of things to happen is 2->3->4. However, apparently, step 4 is triggered before step 3, and so there is no relevant document under 'stats' to update, thus throwing an error.
Is there a way to make the function wait for the document creation under 'stats' and update only after it is created? I thought about using setTimeout() for the function in step 4, but guess there might be a better way.
Below is the code that I am using for steps 3 and 4. Can someone advise? Thanks!
//This listens to a creation of a document under 'lists' and creates a new document
//with the same document ID under 'stats'.
exports.statsCreate = functions.firestore
.document('lists/{listid}').onCreate((snap,context)=>{
const listidpath=snap.ref.path;
const pathfinder=listidpath.split('/');
const listid=pathfinder[pathfinder.length-1];
return db.collection('stats').doc(listid).set({
postcount:0,
})
})
//This listens to a creation of a document under 'posts' and updates the corresponding
// document under 'stats'. There is a field under 'posts' with the list ID to make this possible.
// How do I make sure the update operation happens only after the document is actually there?
exports.statsUpdate = functions.firestore
.document('posts/{postid}').onCreate((snap,context)=>{
const data=snap.data();
return db.collection('stats').doc(data.listid).update({
postcount:admin.firestore.FieldValue.increment(1)
})
})
I can see at least two "easy" solutions:
Solution #1: In your front end, set a listener to the to-be-created stat document (with onSnapshot()), and only create the post document when the stat one has been created. Note however that this solution will not work if the user does not have read access right to the posts collection.
Solution #2: Use the "retry on failure" option for background Cloud Functions. Within your statsUpdate Cloud Function you intentionally throw an exception if the stat doc is not found => The CF will be retried until the stat doc is created.
A third solution would be to use a Callable Cloud Function, called from your front-end. This Callable Cloud Function would write the three docs in the following order: list, stat and post. Then the statsUpdate Cloud Function would be triggered in the background (or you could include its business logic in the Callable Cloud Function as well).
One of the drawbacks of this solution is that the Cloud Function may encounter some cold start effect. In this case, from an end-user perspective, the process may take more time than the abonne solutions. However note that you can specify a minimum number of container instances to be kept warm and ready to serve requests.
PS: Note that in the statsCreate CF, you don't need to extract the listid with:
const listidpath=snap.ref.path;
const pathfinder=listidpath.split('/');
const listid=pathfinder[pathfinder.length-1];
Just do:
const listid = context.params.listid;
The context parameter provides information about the Cloud Function's execution.

How do I setup firebase realtime DB triggers for multiple databases using cloud functions?

I've a specific use case where I have multiple realtime DBs on a single project (and this number will grow) and I want to set up cloud functions triggers on all of them, currently I'm hoping if there's a way to get the DB name in the callback on which the cloud function is triggered?
import * as functions from 'firebase-functions';
import mongoose from 'mongoose';
export const updateData = functions.database.ref('/someendpoint/{code}').onUpdate(async (change, context) => {
$dbName = getFireBaseDBName(); //some function to get the DB name - This is the step that I would like to know how
await mongoose.connect(`mongo-db-string-connection/${dbName}`, {useNewUrlParser: true});
const Code = context.params.code;
const Schema = new mongoose.Schema({}, { collection: `someendpoint`, strict: false });
const Model = mongoose.model(`someendpoint`, Schema);
const after = change.after.val();
await Model.deleteMany({code: Code});
await Model.create({after, ...{code:Code}});
});
I need the DB name so that I can save to the database with the same name on Mongo.
For example:
Given I have a firebase project 'My-Project' and I have multiple Realtime Database instances on them say:
'db1', 'db2', 'db3'
When the trigger fires, I want to save/update/delete the data in MongoDB database so that it stays in sync with my Firebase Realtime database.
So it's crucial that not only do I get the data stored in db1 but also I get the name 'db1' so that the right data can be altered in Mongo.
Please keep in mind that more databases will be added to My-Project so
somewhere down the line it'll be 'db100.
First thing - I'll say that the way you're using database shards for multi-tenancy isn't really the way they're meant to be used. The Firebase team recommends using separate projects for multi-tenancy, one for each tenant, in order to keep users and their data isolated. The reason that database shards exist is to help developers deal with the scaling limitations of Realtime Database.
All that said, the triggers for Realtime Database don't directly provide the name of the shard in the callback. You will need to write one function for each shard, as required by the API, and described in the documentation.
To control when and where your function should trigger, call ref(path)
to specify a path, and optionally specify a database instance with
instance('INSTANCE_NAME'). If you do not specify an instance, the
function deploys to the default database instance for the Firebase
project For example:
Default database instance: functions.database.ref('/foo/bar')
Instance named "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')
Since you have to hard code the name of the shard in the function code, the you might as well just type it again inside the function itself. Or put them in global variables, and use them inside each function.
If you want to see an example of how to share code between each function declared for each instance, read this other question: How to trigger firebase function on all database instances rather than default one?

Firebase function document.create and user.create triggers firing multiple times

I'm trying to keep track of the number of documents in collections and the number of users in my Firebase project. I set up some .create triggers to update a stats document using increment, but sometimes the .create functions trigger multiple times for a single creation event. This happens with both Firestore documents and new users. Any ideas?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const firestore = require('#google-cloud/firestore')
admin.initializeApp();
const db = admin.firestore()
/* for counting documents created */
exports.countDoc = functions.firestore
.document('collection/{docId}')
.onCreate((change, context) => {
const docId = context.params.docId
db.doc('stats/doc').update({
'docsCreated': firestore.FieldValue.increment(1)
})
return true;
});
/* for counting users created */
exports.countUsers = functions.auth.user().onCreate((user) => {
db.doc('stats/doc').update({
'usersCreated': firestore.FieldValue.increment(1)
})
return true;
});
Thanks!
There is some advice on how to achieve your functions' idempotency.
There are FieldValue.arrayUnion() & FieldValue.arrayRemove() functions which safely remove and add elements to an array, without duplicates or errors if the element being deleted is nonexistent.
You can make array fields in your documents called 'users' and 'docs' and add there data with FieldValue.arrayUnion() by triggered functions. With that approach you can retrieve the actual sizes on the client side by getting users & docs fields and calling .size() on it.
You should expect that a background trigger could possibly be executed multiple times per event. This should be very rare, but not impossible. It's part of the guarantee that Cloud Functions gives you for "at-least-once execution". Since the internal infrastructure is entirely asynchronous with respect to the execution of your code on a dedicated server instance, that infrastructure might not receive the signal that your function finished successfully. In that case, it triggers the function again in order to ensure delivery.
It's recommended that you write your function to be idempotent in order to handle this situation, if it's important for your app. This is not always a very simple thing to implement correctly, and could also add a lot of weight to your code. There are also many ways to do this for different sorts of scenarios. But the choice is yours.
Read more about it in the documentation for execution guarantees.

I made a function and for some reason it is not being called. I was able to deploy it

Hi everyone I am new to firebase. I made a function and for some reason it is not being called. I was able to deploy it. what I am trying to do is that every time I change a value my slack will get notified but when I add a value/document nothing happens. I have looked in the functions and they have not been called at all.
const functions = require('firebase-functions');
var request = require("request");
const admin = require('firebase-admin');
admin.initializeApp();
exports.notifyNewRecord = functions.firestore
.document('stallion/{userId}')
.onCreate((snap, context) => {
const newValue = snap.data();
const name = newValue.name;
const points = newValue.points;
return request.post(
"https://hooks.slack.com/I have my slack id here in the code",
{json:{text: `<${user} received ${points} point for an excepted answer>.`}}
)
This is the structure of my database the database and then stallion documents and the record is right on the document name: xxxx, points: xxxx. I am using the free tier for firestore don't know if it matters. I see the function in my database functions any help would be appreciated.
You're using an onCreate trigger, which only fires when a new document in created in the "stallion" collection. It won't trigger for changes to existing documents. For that, you would need to use an onUpdate on onWrite trigger. Please be sure to read the documentation for Firestore triggers to better understand how they work.
The fire-base functions tab takes a while to upload if the function has been called. Also you cannot make calls in the free tier as mentioned by Nicholas Pesa. There is at least one mistake in the above code that user inside the call is undefined it should be name.

Resources