0

I want to create a function with node.js but I've got stuck at a point.

Explanation of what I want to do:

First, the function will trigger when a new document added to the path profile/{profileID}/posts/{newDocument}

the function will send a notification to all the following users. the problem comes here.

I've another collection in the profile collection which is followers and contains documents of the field followerID.

I want to take this followerID and use it as a document id to access the tokenID field with I've added to the profile document.

like this:

..(profile/followerID).get(); and then access the field value of tokenID field.

My current Code:- Index.js

const functions = require('firebase-functions');

const admin = require('firebase-admin');


admin.initializeApp(functions.config().firebase);


exports.fcmTester = functions.firestore.document('profile/{profileID}/posts/{postID}').onCreate((snapshot, context) => {
    const notificationMessageData = snapshot.data();

    var x = firestore.doc('profile/{profileID}/followers/');

    var follower;

    x.get().then(snapshot => {
      follower = snapshot.followerID;
    });



    return admin.firestore().collection('profile').get()
        .then(snapshot => {
            var tokens = [];

            if (snapshot.empty) {
                console.log('No Devices');
                throw new Error('No Devices');
            } else {
                for (var token of snapshot.docs) {
                    tokens.push(token.data().tokenID);
                }

                var payload = {
                    "notification": {
                        "title": notificationMessageData.title,
                        "body": notificationMessageData.title,
                        "sound": "default"
                    },
                    "data": {
                        "sendername": notificationMessageData.title,
                        "message": notificationMessageData.title
                    }
                }

                return admin.messaging().sendToDevice(tokens, payload)
            }

        })
        .catch((err) => {
            console.log(err);
            return null;
        })

});

my firestore database explanation.

profile | profileDocuments | posts & followers | followers collection documents & posts collection documents

I have a parent collection called profile and it contains documents as any collection these documents contain a field called tokenID and that I want to access, but I will not do this for all users only for followers (the users who follwed that profile) so I've created a new collection called followers and it contains all the followers IDs, I want to take every followerID and for each id push tokenID to tokens list.

1 Answer 1

4

If I understand correctly your question, you should do as follows. See the explanations below.

exports.fcmTester = functions.firestore.document('profile/{profileID}/posts/{postID}').onCreate((snapshot, context) => {
    
    const notificationMessageData = snapshot.data();
    const profileID = context.params.profileID;

    // var x = firestore.doc('profile/{profileID}/followers/');  //This does not point to a document since your path is composed of 3 elements

    var followersCollecRef = admin.firestore().collection('profile/' + profileID + '/followers/');  
    //You could also use Template literals `profile/${profileID}/followers/`

    return followersCollecRef.get()
    .then(querySnapshot => {
        var tokens = [];
        querySnapshot.forEach(doc => {
            // doc.data() is never undefined for query doc snapshots
            tokens.push(doc.data().tokenID);
        });
        
        var payload = {
            "notification": {
                "title": notificationMessageData.title,
                "body": notificationMessageData.title,
                "sound": "default"
            },
            "data": {
                "sendername": notificationMessageData.title,
                "message": notificationMessageData.title
            }
        }

        return admin.messaging().sendToDevice(tokens, payload)
        
    }); 

First by doing var x = firestore.doc('profile/{profileID}/followers/'); you don't declare a DocumentReference because your path is composed of 3 elements (i.e. Collection/Doc/Collection). Note also that,in a Cloud Function, you need to use the Admin SDK in order to read other Firestore documents/collections: So you need to do admin.firestore() (var x = firestore.doc(...) will not work).

Secondly, you cannot get the value of profileID just by doing {profileID}: you need to use the context object, as follows const profileID = context.params.profileID;.

So, applying the above, we declare a CollectionReference followersCollecRef and we call the get() method. Then we loop over all the docs of this Collection with querySnapshot.forEach() to populate the tokens array.

The remaining part is easy and in line with your code.


Finally, note that since v1.0 you should initialize your Cloud Functions simple with admin.initializeApp();, see https://firebase.google.com/docs/functions/beta-v1-diff#new_initialization_syntax_for_firebase-admin


Update following your comments

The following Cloud Function code will lookup the Profile document of each follower and use the value of the tokenID field from this document.

(Note that you could also store the tokenID directly in the Follower document. You would duplicate data but this is quite common in the NoSQL world.)

exports.fcmTester = functions.firestore.document('profile/{profileID}/posts/{postID}').onCreate((snapshot, context) => {

    const notificationMessageData = snapshot.data();
    const profileID = context.params.profileID;

    // var x = firestore.doc('profile/{profileID}/followers/');  //This does not point to a document but to a collectrion since your path is composed of 3 elements

    const followersCollecRef = admin.firestore().collection('profile/' + profileID + '/followers/');
    //You could also use Template literals `profile/${profileID}/followers/`

    return followersCollecRef.get()
        .then(querySnapshot => {
            //For each Follower document we need to query it's corresponding Profile document. We will use Promise.all()

            const promises = [];
            querySnapshot.forEach(doc => {
                const followerDocID = doc.id;
                promises.push(admin.firestore().doc(`profile/${followerDocID}`).get());  //We use the id property of the DocumentSnapshot to build a DocumentReference and we call get() on it.
            });

            return Promise.all(promises);
        })
        .then(results => {
            //results is an array of DocumentSnapshots
            //We will iterate over this array to get the values of tokenID

            const tokens = [];
            results.forEach(doc => {
                    if (doc.exists) {
                        tokens.push(doc.data().tokenID);
                    } else {
                        //It's up to you to decide what you want to to do in case a Follower doc doesn't have a corresponding Profile doc
                        //Ignore it or throw an error
                    }
            });

            const payload = {
                "notification": {
                    "title": notificationMessageData.title,
                    "body": notificationMessageData.title,
                    "sound": "default"
                },
                "data": {
                    "sendername": notificationMessageData.title,
                    "message": notificationMessageData.title
                }
            }

            return admin.messaging().sendToDevice(tokens, payload)

        })
        .catch((err) => {
            console.log(err);
            return null;
        });

});
Sign up to request clarification or add additional context in comments.

7 Comments

thank you very much really helped me but in the followers collection I have the follwerID field and therefore I can use it as a document id in profile collection to get the tokenID field . if you don't mind please help me in this part
I would help you with pleasure but you need to explain very clearly what are the exact references of the different documents. In your comment you speak about the field of a "collection". It is difficult to understand exactly what is what. Please update your question with a clear explanation showing the exact paths of the different documents.
Yes, there was a typo in my answer: I've adapted it around 15 minutes ago. Please take the new code in the answer.
In my answer, at the bottom. I have modified it as I said, so just copy it again. You can see the modifications here stackoverflow.com/posts/59228683/revisions
I am sorry for wasting your time, thank you! it worked!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.