Error updating different Collection document using Cloud Function - firebase

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.

Related

Firebase Cloud Functions: TypeError snapshot.forEach is not a function

I've been struggling to understand why my Firebase cloud function isn't working.
I'm deleting a reserved number in a collection called 'anglerNumbers' when a new user has registered and when that users' document has been created. I use this on the client to make sure a reserved number can't be used twice. I'm following the documentation here: https://firebase.google.com/docs/firestore/query-data/queries?authuser=0 (Using Node.js)
But I keep getting the Error: TypeError: snapshot.forEach is not a function
Here's the function:
exports.newUser = functions.firestore.document('users/{userId}')
.onCreate((snap, context) => {
const newUserNumber = snap.data().anglerNumber;
const anglersRef = admin.firestore().collection('anglerNumbers');
const snapshot = anglersRef.where('anglerNumber', '==', newUserNumber).get();
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
doc.delete();
});
})
It does not console log 'No matching documents'. So there are documents but I can't perform the forEach as indicated by the documentation. What am I missing? Thanks!
in this line in your code:
const snapshot = anglersRef.where('anglerNumber', '==', newUserNumber).get();
You assume that get resolves immediately to a snapshot but in fact get() returns a promise that will resolve into a snap shot. You need to wait for this async function.
Either use await if that is possible in your context or use:
anglersRef.where('anglerNumber', '==', newUserNumber).get().then((snapshot)=>{
//do you processing
});

Stripe: Unknown arguments ([object Object])

I am trying to write a firebase cloud function that attaches a payment method to a stripe customer, subscribes them to a plan, and writes the subscription object to firestore.
I actually just wrote a function that worked but am not sure what I changed.
exports.attachAndSubscribe = functions.firestore
.document('stripe_customers/{userId}')
.onUpdate(async (change, context) => {
const source = change.after.data();
const paymentMethod = source.paymentMethod;
await stripe.paymentMethods.attach(paymentMethod.id,
{customer: source.customer_id},
{invoice_settings: {default_payment_method: paymentMethod.id}
});
const subscription = await stripe.subscriptions.create(
{customer: source.customer_id,items: [{plan: 'plan_FnA3IsFL5Xc6Ct'}]
});
return admin.firestore()
.collection('stripe_customers')
.doc(userId)
.set(
{subscription: subscription});
});
When the function gets triggered I get the following error:
Stripe: Unknown arguments ([object Object]). Did you mean to pass an
options object?
duck was right. The default_payment_method property only exists in the customer object, not in the paymentMethod object. I solved the problem by separately updating the stripe customer after attaching the payment method.

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

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) ...

"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