Firebase Cloud Function for Firestore does not work - firebase

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.

Related

Is there a way to determine once a cloud function has finished running - either through looping getDownloadURL or messaging?

I am still getting the hang of Firebase and Cloud functions, but here is what I'm trying to figure out.
The current setup
My app has a cloud function that will take a PDF that has been uploaded into a storage bucket and convert it into PNG. It doesn't destroy the original PDF, so I am left with both files.
The URL for the newly created PNG is then attached to a property on one of our documents in Firestore.
What I am trying to accomplish
I want to be able to upload a new PDF to use as a replacement image. I think I am running into a race condition where the cloud function hasn't finished executing by the time I am trying to call updateDoc() with the new PNG.
On the client side, I have the storageRef returned from the upload method:
uploadFunction(...).then((snapshot) => {
return snapshot.ref
}
I'm saving the result of this function to a variable, and I am trying to pass that into the update method that will adjust the property on my document in Firestore:
const storageRef = await functionThatUploadsPDF(file);
updateDocumentInFirestore(storageRef);
Within updateDocumentInFirestore, I'm trying to navigate to the new reference that should exist once the cloud function has finished, get a download URL, and update that property on my document:
const newImageRef = ref(storageRef.parent, "generatedImage.png");
const newDownloadURL = getDownloadURL(newImageRef).then((url) => {
updateDoc(documentRef, backgroundImage: url);
});
However, I am getting the following error - I believe due to the cloud function having not finished yet:
Firebase Storage: Object 'storage-bucket/generatedImage.png' does not exist. (storage/object-not-found)
My thoughts on potential solutions
I could try to poll the storage for the existence of generatedImage.png until the getDownloadURL call returns an actual URL, but I worry about the amount of calls this would yield.
If there is a way for the cloud function to send a message to let me know that the conversion is finished, I can send a call once for the download URL after receiving said message. However, I can't figure out how to accomplish this.
Efforts so far
I have been pursuing course 1. So far, but have not met any success yet. Scouring through Firebase documentation, I haven't been able to find any supporting resources on how to accomplish 1 or 2. Does anyone have any suggestions - either on my planned courses of action, or a new option that I haven't considered?
You can use this onFinalize trigger to send a message or update a document in Firestore to indicate that the function has finished running. This trigger is triggered whenever a file is created or updated.
onFinalize Sent when a new object (or a new generation of an existing
object) is successfully created in the bucket. This includes copying
or rewriting an existing object. A failed upload does not trigger this
event.
you can also create a promise that resolves when the downloadURL is not null, and use that promise in your updateDocumentInFirestore function. This way, the updateDoc function will only be called once the downloadURL is available.
Additionally, as was mentioned in the comments, you can consider cloud workflow.The exact implementation will depend on your specific use case
You can also check these similar cases
Firebase Storage: Object does not exist
Error: storage/object-not-found when trying to upload large image file
Firebase Storage Put could not get object

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.

Watch a collectionGroup with Firestore using Cloud Functions

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.

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.

Is it good idea to use admin.database().ref().on('child_added') in cloud functions?

I'm creating a general purpose queue on firebase cloud functions to run huge list of task. I was wondering if i can use .on('child_added') to get new task pushed to the queue.
Problem i was getting is that my queue is breaking in a middle randomly after 10 mins or sometimes 15 mins.
admin.database().ref('firebase/queues/').on('child_added', async snap => {
let data = snap.val();
console.log(data);
try {
await queueService.start(data);
} catch (e) {
console.log(e.message);
}
snap.ref.remove();
});
Or shall i go back to use triggers?
functions.database.ref('firebase/queues/{queueId}').onCreate(event => {
return firebaseQueueTrigger(event);
});
You can use child_added in cloud functions if you just want to retrieve data from the database.
onCreate() is a database trigger, that is triggered whenever new data is added to the database.
more info here:
https://firebase.google.com/docs/functions/database-events
so when new data is added to the database, at the location provided onCreate() will trigger. Both can also be used in cloud functions
The problem is not in using child_added, it is in keeping an active on(... in Cloud Functions. The on(... method attaches a listener, which stays attached until you call off(). This by nature conflicts with the ephemeral nature of Cloud Functions, which are meant to have a trigger-execute-exit flow. Typically if you need to read additional data from the database in your Cloud Function, you'll want to use once(.... so that you can detect when the read is done.
But in your specific case: if you're creating a worker queue, then I'd expect all data to come in with event.data already. Your functions.database.ref('firebase/queues/{queueId}').onCreate(event is essentially the Cloud Functions equivalent of firebase.database().ref('firebase/queues').on('child_added'.

Resources