admin.firestore(...).document is not a function at exports - firebase

I am running a cloud function that is triggered by a firebase realtime database change and updates FireStore. However, although the function triggers, the function cannot access the Firestore.
exports.reveal = functions.database.ref('/reveals/{postIDthatWasRevealed}/revealed').onUpdate((change, context) => {
const revealedValue = change.after.val()
if (revealedValue === true) {
var updates = {}
const postID = context.params.postIDthatWasRevealed
return admin.firestore().document('/posters/' + postID).get().then(querySnapshot => {
At this point, the console log states TypeError:admin.firestore(...).document is not a function at.. I have already tried this answer :Tried Answer But the problem continues. Does this have to do with the fact that I am accessing firestore inside of firebase cloud function? Are my cloud functions not properly updated?
Edit (Includes Initialization code)
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
In addition, I have tried to debug the function by changing it to :
if (revealedValue === true) {
var updates = {}
const postID = context.params.postIDthatWasRevealed
return admin.firestore()
Now, when I trigger the function, I get Function execution took 811 ms, finished with status: 'ok' Thus, it seems as if the firestore function is valid, just that my syntax may be off or I may be forgetting something

use doc() function instead of document()

document() is a property of functions.firestore
The equivalent for admin.firestore() is collection().doc()
See Firestore: Get Data for more info.
admin.firestore().collection('posters').doc(postID) ...

Related

Firebase Cloud Function does not terminate in functions:shell

I'm trying to import a JSON into a collection. For testing purpose, I'm using emulators and document creation trigger.
The workflow is :
start emulators with firebase emulators:start
start functions shell in another terminal with firebase functions:shell (tells me that no emulator is running btw)
call my function with tempoCF()
it runs and add documents to collection but it seems that the function does not terminate. I cannot call another function and need to press CTRL+C to be able to write again in the shell.
Here is the function I use :
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const fetch = require('node-fetch');
admin.initializeApp();
const db = admin.firestore();
exports.tempoCF = functions
.firestore.document('/tempo/{docId}')
.onCreate(async (snap, context) => {
console.log("onCreate");
let settings = { method: "Get" };
let url = "https://opendata.paris.fr/api/records/1.0/search/?dataset=sanisettesparis&q=&rows=-1";
try {
let response = await fetch(url, settings);
let json = await response.json();
// TODO for each json object, add new document
return Promise.all(json["records"].map(toiletJsonObject => {
console.log(toiletJsonObject);
return db.collection('toilets').doc(toiletJsonObject["recordid"]).set({});
}));
}
catch(error) {
console.log(error);
return null;
}
}
);
I know I can use the emulator UI to create a new document that trigger the tempoCF function and it works as well but I fear that my function isn't correct and could generate bugs in production.
Here is the screenshot of the terminal. It prints logs and at the end, there is no way to write anything on the last empty line in the screenshot. I run it in Android Studio but I don't think that it matters.
I'm not on Windows but on a Mac, and I can reproduce your problem in the Terminal by calling the function with tempoCF(). Somehow, by doing that, you are simulating the creation a Firestore document without data.
But if I pass some data when calling the Cloud Function, e.g. tempoCF({foo: "bar"}) (i.e. providing new test data for the onCreate operation) I'm able to write to the Terminal after the CF has completed. See the doc for more details.

Error updating different Collection document using Cloud Function

By using Cloud Functions, when a document from "users" collection is edited, the edited files should be updated in uploads collection wherever the user id is stored.
For the above requirement I am using the below function.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const settings = {
timestampsInSnapshots: true
};
admin.initializeApp();
admin.firestore().settings(settings);
var db = admin.firestore();
exports.updateUser = functions.firestore.document('users/{userId}')
.onUpdate((change, context) => {
var userId = context.params.userId;
const newValue = change.after.data();
const name = newValue.display_name;
var uploadsRef = db.collection('uploads');
uploadsRef.where('user.id', '==', userId).get().then((snapshot) => {
snapshot.docs.forEach(doc => {
doc.set({"display_name" : name}); //Set the new data
});
}).then((err)=> {
console.log(err)
});
});
When this executes, I get the below error in the logs.
TypeError: doc.set is not a function
at snapshot.docs.forEach.doc (/user_code/index.js:31:21)
at Array.forEach (native)
at uploadsRef.where.get.then (/user_code/index.js:29:27)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
And also the below.
Unhandled rejection
How do I approach the problem? What is the best approach to deal with the snapshots document updates?
When you do a get() on a Query object, it will yield a
QuerySnapshot object. When you use its docs property, you're iterating an array of QuerySnapshotDocument objects that contain all the data from the matched documents. It looks like you're assuming that a QuerySnapshotDocument object has a set() method, but you can see from the linked API docs that it does not.
If you want to write back to a document identified in a QuerySnapshotDocument, use its ref property to get a DocumentReference object that does have a set() method.
doc.ref.set({"display_name" : name}); //Set the new data
Bear in mind that if you make this change, it will run, but may not update all the documents, because you're also ignoring the promise returned by the set() method. You'll need to collect all those promises into an array and use Promise.all() to generate a new promise to return from the function. This is necessary to help Cloud Functions know when all the asynchronous work is complete.

How to trigger function when date stored in firestore database is todays date?

I am creating an app where I need to send push notification when today's date matches with the date stored in database in order to send push notification.
How to achieve this?
Update:
You can use a scheduled Cloud Function, instead of writing an HTTPS Cloud Function that is called via n online CRON Job service. The Cloud Function code stays exactly the same, just the trigger changes.
Scheduled Cloud Functions were not available at the time of writing the initial anwser.
Without knowing your data model it is difficult to give a precise answer, but let's imagine, to simplify, that you store in each document a field named notifDate with format DDMMYYY and that those documents are store in a Collection named notificationTriggers.
You could write an HTTPS Cloud Function as follows:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({ origin: true });
const moment = require('moment');
admin.initializeApp();
exports.sendDailyNotifications = functions.https.onRequest((request, response) => {
cors(request, response, () => {
const now = moment();
const dateFormatted = now.format('DDMMYYYY');
admin.firestore()
.collection("notificationTriggers").where("notifDate", "==", dateFormatted)
.get()
.then(function(querySnapshot) {
const promises = [];
querySnapshot.forEach(doc => {
const tokenId = doc.data().tokenId; //Assumption: the tokenId is in the doc
const notificationContent = {
notification: {
title: "...",
body: "...", //maybe use some data from the doc, e.g doc.data().notificationContent
icon: "default",
sound : "default"
}
};
promises
.push(admin.messaging().sendToDevice(tokenId, notificationContent));
});
return Promise.all(promises);
})
.then(results => {
response.send(data)
})
.catch(error => {
console.log(error)
response.status(500).send(error)
});
});
});
You would then call this Cloud Function every day with an online CRON job service like https://cron-job.org/en/.
For more examples on how to send notifications in Cloud Functions, have a look at those SO answers Sending push notification using cloud function when a new node is added in firebase realtime database?, node.js firebase deploy error or Firebase: Cloud Firestore trigger not working for FCM.
If you are not familiar with the use of Promises in Cloud Functions I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/
You will note the use of Promise.all() in the above code, since you are executing several asynchronous tasks (sendToDevice() method) in parallel. This is detailed in the third video mentioned above.
Use Google Cloud Functions Scheduled Triggers
https://cloud.google.com/scheduler/docs/tut-pub-sub
Using a scheduled trigger you can specify how many times to invoke your function by specifying the frequency using the unix-cron format. Then within the function you can do date check and other needed logic

"TypeError: functions.firestore.collection is not a function"

Looking through the Firestore documentation, I see many examples of functions.firestore.document but I don't see any examples of functions.firestore.collection. Firestore syntax is
firebase.firestore().collection('...').doc('...')
I get an error message with
firebase.firestore().document('...')
Yet in Cloud Functions with this code:
exports.myFunction = functions.firestore.collection('...').doc('...').onUpdate(event => {
on deploy I get an error message:
TypeError: functions.firestore.collection is not a function
When I change the code to
exports.getWatsonTokenFirestore = functions.firestore.document('...').onUpdate(event => {
I don't get an error message on deploy.
Why does Cloud Functions appear to have a different data structure than Cloud Firestore?
Here's my full Cloud Function. My collection is User_Login_Event and my document is Toggle_Value:
exports.getWatsonTokenFS = functions.firestore.document('User_Login_Event/{Toggle_Value}').onUpdate(event => {
var username = 'TDK',
password = 'swordfish',
url = 'https://' + username + ':' + password + '#stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api';
request({url: url}, function (error, response, body) {
admin.firestore().collection('IBM_Watson_Token').document('Token_Value').update('token');
});
return 0; // prevents an error message "Function returned undefined, expected Promise or value"
});
The function deploys without error but when it executes I get this error message:
TypeError: firebase.firestore is not a function
I'm confused as firebase.firestore isn't in my Cloud Function. It's in my Angular front-end code in various places, without a problem. What is this error message referring to? I tried changing the line
admin.firestore().collection('IBM_Watson_Token').document('Token_Value').update('token');
to
firebase.firestore().collection('IBM_Watson_Token').document('Token_Value').update('token');
and to
console.log("getWatsonTokenFS response");
but I got the same error message.
Yes. You should format it as...
exports.getWatsonTokenFirestore = functions.firestore.document('myCollection/{documentId}').onUpdate(event => {
// code
});
collection and doc are methods within firebase.firestore. To access them via functions.firestore, you must use document.
You can see a full list of Classes for Cloud Firestore and the latest SDK for Cloud Functions for Firebase
Update
I've been working on your code. I've added in all of the dependencies and initialization, which I assume that you have in your code. I can't see where you're using any data from Firestore in your IBM Watson request and I can't see how you're writing any of the returned data back to Firestore. As I'm not familiar with your request method, I've commented it out, to give you what should be a working example of an update to Firestore and writes something back. I also edited some of your code to make it more readable and changed the Cloud Functions code to reflect v1.0.0, released today (I've been testing it for a while) :)
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp();
const firestore = admin.firestore();
exports.getWatsonTokenFS = functions.firestore
.document('User_Login_Event/{Toggle_Value}')
.onUpdate((snap, context) => {
let username = 'TDK';
let password = 'swordfish';
let url = `https://${username}:${password}#stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api`;
// request({url}, function (error, response, body) {
// firestore.doc(`${IBM_Watson_Token}/${Token_Value}`).update('token');
// });
return firestore.doc(`IBM_Watson_Token/Token_Value`).update('token')
.then(response => {
return Promise.resolve();
})
.catch(err => {
return Promise.reject(err);
});
});
Now that Firebase has updated firebase-admin to 5.12.0 and firebase-functions to 1.0.1 my test function is working. The function that Jason Berryman wrote is correct except for two lines. Jason wrote:
.onUpdate((snap, context) => {
That should be
.onUpdate((change, context) => {
Secondly, Jason wrote:
return firestore.doc(`IBM_Watson_Token/Token_Value`).update('token')
The corrected line is:
return firestore.collection('IBM_Watson_Token').doc('Token_Value').update({
token: 'newToken'
})
I made two changes in Jason's code. First, I changed the location syntax; more on this below. Second, update() requires an object as the argument.
To show the syntax for locations, I wrote a simple Cloud Function that triggers when a value at a location in Cloud Firestore changes, and then writes a new value to a different location in Cloud Firestore. I removed the line const firestore = admin.firestore(); to make the code more clear:
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp();
exports.testFunction = functions.firestore.document('triggerCollection/{documentID}').onUpdate((change, context) => {
return admin.firestore().collection('writeCollection').doc('documentID').update({
token: 'newValue'
})
.then(response => {
return Promise.resolve();
})
.catch(err => {
return Promise.reject(err);
});
});
Let's compare three syntaxes for locations in Cloud Firestore. First, in the browser I use this syntax:
firebase.firestore().collection('myCollection').doc('documentID')
Next, in a Cloud Function trigger I use this syntax:
functions.firestore.document('myCollection/{documentID}')
Third, in the Cloud Function return I use this syntax:
admin.firestore().collection('myCollection').doc('documentID')
The first and last lines are the same except that from the browser you call Firebase with firebase, when from the server you call Firebase using the firebase-admin Node package, here aliased to admin.
The middle line is different. It's calling Firebase using the firebase-functions Node package, here aliased to functions.
In other words, Firebase is called using different libraries, depending on whether you're calling from the browser or the server (e.g., a Cloud Function), and whether in a Cloud Function you're calling a trigger or a return.
Cloud functions is triggered based on events happening in Firebase example in realtime database, authentication.
Cloud firestore is triggered based on events happening in Firestore which uses the concept of documents and collections.
As explained here:
https://firebase.google.com/docs/functions/firestore-events
The cloud firestore triggers are used when there is a change in a document.
I had the same problem. I used following
const getReceiverDataPromise = admin.firestore().doc('users/' + receiverUID).get();
const getSenderDataPromise = admin.firestore().doc('users/' + senderUID).get();
return Promise.all([getReceiverDataPromise, getSenderDataPromise]).then(results => {
const receiver = results[0].data();
console.log("receiver: ", receiver);
const sender = results[1].data();
console.log("sender: ", sender);
});

Reading firestore parents from firebase's function

Here is my code:
exports.onCreateLog = functions
.firestore
.document('/accounts/{userId}/account/{accountId}/logs/{logId}')
.onCreate((event) => {
let documentRef = functions
.firestore
.doc(`/accounts/${event.params.userId}/account/${event.params.accountId}`);
return documentRef
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
console.log(`Document retrieved successfully. ${JSON.stringify(documentSnapshot.data())}`);
}
});
});
What I would like to do is read the value from the parent, but I tried with this error:
TypeError: Cannot read property 'firestore' of undefined
I tried to get the parent, and get the value with no luck. Is there any advise? Thanks.
You can't use your functions object reference to query your database. You can only use it to build your Cloud Function trigger.
If you want to query your database within a Cloud Function, you need to use the Admin SDK.
const admin = require('firebase-admin')
admin.firestore().doc(...).get()

Resources