Search code examples
javascriptfirebasegoogle-cloud-platformgoogle-cloud-firestoregoogle-cloud-functions

How to access Document ID in Firestore from a scheduled function


I'm using Firebase scheduled function to periodically check data in Firestore if users' detail are added to SendGrid contact list. After they're successfully added to SendGrid, I want to update the value in firestore addToContact from false to true.

exports.addContact = functions.region(region).pubsub.schedule('every 4 minutes').onRun(async(context) => {
 
  // Query data from firestore
  const querySnapshot = await admin.firestore().collection('users')
        .where('metadata.addToContact', '==', false)
        .get();
 
 // Call SendGrid API to add contact

 // If success, change metadata.addToContact to true

 if (response) {
   const docRef = admin.firestore().collection('users').doc(""+context.params.id);
   await docRef.update( {'metadata.sendToSg': true }, { merge: true } );
 }
}

I want to access context.params.id but I realise that context that passed in .onRun isn't the same with the context passed in a callable function.

NOTE: If I pass context.params.id to .doc without ""+, I got an error Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string. After google the answer, I tried using ""+context.params.id then I can console.log the context value. I only got

context {
  eventId: '<eventID>',
  timestamp: '2020-11-21T09:05:38.861Z',
  eventType: 'google.pubsub.topic.publish',
  params: {}
}

I thought I'd get document ID from params object here but it's empty.

Is there a way to get Document ID from firestore in a scheduled function?


Solution

  • The scheduled Cloud Function itself has no notion of a Firestore document ID since it is triggered by the Google Cloud Scheduler and not by an event occurring on a Firestore document. This is why the corresponding context object is different from the context object of a Firestore triggered Cloud Function.

    I understand that you want to update the unique document corresponding to your first query (admin.firestore().collection('users').where('metadata.addToContact', '==', false).get();).

    If this understanding is correct, do as follows:

    exports.addContact = functions.region(region).pubsub.schedule('every 4 minutes').onRun(async (context) => {
    
        // Query data from firestore
        const querySnapshot = await admin.firestore().collection('users')
            .where('metadata.addToContact', '==', false)
            .get();
    
        // Call SendGrid API to add contact
    
        // If success, change metadata.addToContact to true
    
        if (response) {
            const docRef = querySnapshot.docs[0].ref;
            await docRef.update({ 'metadata.sendToSg': true }, { merge: true });
            
        } else {
            console.log('No response');
        }
        return null;
    
    });
    

    Update following your comment:

    You should not use async/await with forEach(), see here for more details. Use Promise.all() instead, as follows:

    exports.addContact = functions.region(region).pubsub.schedule('every 4 minutes').onRun(async (context) => {
    
        // Query data from firestore
        const querySnapshot = await admin.firestore().collection('users')
            .where('metadata.addToContact', '==', false)
            .get();
    
        // Call SendGrid API to add contact
    
        // If success, change metadata.addToContact to true
    
        if (response) {
    
            const promises = [];
    
            querySnapshot.forEach((doc) => {
                const { metadata } = doc.data();
                if (metadata.sendToSg == false) {
                    promises.push(doc.ref.update({ 'metadata.sendToSg': true }, { merge: true }));
                }
            })
    
            await Promise.all(promises);
    
        } else {
            console.log('No response');
        }
        return null;
    
    });