I wrote a very simple Cloud Function to add some fields in Firestore with some info from the newly created FirebaseUser in FirebaseAuth.
In Firestore, I have a collection named "highscore". Everytime, a new user is created, I want to add a document with the firebaseusers uid as document, and 2 fields, like:
highscore/uid/score & highscore/uid/usernick (e.g highscore/fgt38gudg9/430 & highscore/fgt38gudg9/cooldude45)
This is my function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.database()
//On user creation, trigger: Add information to /highscore/uid
exports.onUserCreation = functions.auth.user().onCreate((user) => {
const collection = db.collection("highscore")
const userid = user.uid
const usernick = user.displayName
collection.doc(userid).set({
score: 0
user: usernick
})
})
However, when the function is triggered, I run into this error:
TypeError: db.collection is not a function
at exports.onUserCreation.functions.auth.user.onCreate (/srv/index.js:11:24)
at cloudFunctionNewSignature (/srv/node_modules/firebase-functions/lib/cloud-functions.js:120:23)
at /worker/worker.js:825:24
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
I can't figure this out. Any suggestions?
I think you are using Firstore:
const db = admin.firestore()
admin.database() gets you a reference to the Realtime Database instance for your project. What you want to use instead is admin.firestore().
Also, you will want to return the promise that you get from set(), otherwise, the operation might not complete before the function terminates.
return collection.doc(userid).set({
score: 0
user: usernick
})
Be sure to read the documentation about terminating functions to understand your obligations in dealing with async work represented by promises.
Related
I am trying to update a user's firestore doc from a Firebase Function using a query, and having issues getting it to work. My Function code is the following:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
/**
* A webhook handler function for the relevant Stripe events.
*/
// Here would be the function that calls updatePlan and passes it the customer email,
// I've omitted it to simplify the snippet
const updatePlan = async (customerEmail) => {
await admin.firestore()
.collection('users').where('email', '==', customerEmail).get()
.then((doc) => {
const ref = doc.ref;
ref.update({ 'purchasedTemplateOne': true });
});
};
I'm getting the following error in the firebase logs when the query is run:
Exception from a finished function: TypeError: Cannot read properties of undefined (reading 'update')
Any help regarding what I may be doing wrong or suggestions on how I could achieve this would be greatly appreciated, Thank you in advance!
Update:
I was able to solve my problem with more understanding of Firestore Queries:
const updatePlan = (customerEmail) => {
const customerQuery = admin.firestore().collection("users").where("email", "==", customerEmail)
customerQuery.get().then(querySnapshot => {
if (!querySnapshot.empty) {
// Get just the one customer/user document
const snapshot = querySnapshot.docs[0]
// Reference of customer/user doc
const documentRef = snapshot.ref
documentRef.update({ 'purchasedTemplateOne': true })
functions.logger.log("User Document Updated:", documentRef);
}
else {
functions.logger.log("User Document Does Not Exist");
}
})
};
The error message is telling you that doc.ref is undefined. There is no property ref on the object doc.
This is probably because you misunderstand the object that results from a Firestore query. Even if you are expecting a single document, a filtered query can return zero or more documents. Those documents are always represented in an object of type QuerySnapshot. That's what doc actually is - a QuerySnapshot - so you need to treat it as such.
Perhaps you should check the size of the result set before you access the docs array to see what's returned by the query. This is covered in the documentation.
here's the basic premise of what im trying to accomplish here. if a user ask a question about a product i want to send a notification to other users who currently own that product. basically saying "hey, so and so has a question about this product. maybe you can help since you own it already"
each userProfile collection has a subcollection called 'notify' where notifications are stored for various things. what i need to do is sort through the userProducts and find every user who owns the product and then create a notification post in only the notify sub-collections for those specific users who own that product.
here is the basic code. the first bit works in that it does return an array of userIDs who own that product. where im struggling now is getting it to create a new doc in the Notify sub-collection for just those specific users. is this possible to do?
exports.Questions = functions.firestore
.document("/userPost/{id}")
.onCreate(async (snap, context) => {
const data = snap.data();
if (data.question == true) {
const userProducts = await db
.collection("userProducts")
.where("product", "==", data.tag)
.get();
const userData = userProducts.docs.map((doc) => doc.data().userId);
await db
.collection("userProfile")
.where("userId", "in", userData)
.get()
.then((querySnapshot) => {
return querySnapshot.docs.ref.collection("notify").add({
message: "a user has asked about a product you own",
});
});
});
Your current solution is on the right track, but there are improvements that can be made.
Use a guard pattern for the data.question == true check.
You don't need to get userProfile/<uid> as you aren't using its contents.
When changing multiple documents at once, you should consider batching them together for simpler error handling.
ref.add(data) is shorthand for ref.doc().set(data) which you can use in the batched write to create new documents.
exports.Questions = functions.firestore
.document("/userPost/{id}")
.onCreate(async (snap, context) => {
const data = snap.data();
if (!data.question) {
console.log("New post not a question. Ignored.")
return;
}
const userProducts = await db
.collection("userProducts")
.where("product", "==", data.tag)
.get();
const userIds = userProducts.docs.map(doc => doc.get("userId")); // more efficient than doc.data().userId
// WARNING: Limited to 500 writes at once.
// If handling more than 500 entries, split into groups.
const batch = db.batch();
const notificationContent = {
message: "a user has asked about a product you own",
};
userIds.forEach(uid => {
// creates a ref to a new document under "userProfile/<uid>/notify"
const notifyDocRef = db.collection(`userProfile/${uid}/notify`).doc();
batch.set(notifyDocRef, notificationContent);
});
await batch.commit(); // write changes to Firestore
});
Note: There is no special handling here for when no one has bought a product before. Consider pinging the product's owner too.
I am trying to implement single index searching using Algoliasearch for my iOS mobile app. I have about 110 users on my application. However, when I upload their data to Algolia search's index the function times out before uploading all users. Instead it throws an Error message in the http browser and declares a timeout in the Firestore console.
Firestore console:
sendCollectionToAlgolia
Function execution took 60044 ms, finished with status: 'timeout'
I created the function using this tutorial:
https://medium.com/#soares.rfarias/how-to-set-up-firestore-and-algolia-319fcf2c0d37
Although i have ran into some complications, I highly recommend that tutorial if you have your app using swiftUI iOS platform and implement cloud functions using Typescript.
Heres my function:
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import algoliasearch from 'algoliasearch';
admin.initializeApp();
const db = admin.firestore();
const algoliaClient = algoliasearch(functions.config().algolia.appid, functions.config().algolia.apikey)
const collectionIndexName = functions.config().projectId === 'PROJECT-XXXX' ? 'prod_SEARCH' : 'dev_SEARCH';
const collectionIndex = algoliaClient.initIndex(collectionIndexName);
//rename to uploadUsersToAlgolia
export const sendCollectionToAlgolia = functions.https.onRequest(async (req, res) => {
const algoliaRecords: any[] = [];
const querySnapshot = await db.collection('users').get();
querySnapshot.docs.forEach(doc => {
const document = doc.data();
const record = {
objectID: doc.id,
fullname: document.fullname,
bio: document.bio,
username: document.username,
uid: document.uid,
profileImageURL: document.profileImageURL,
backgroundImageURL: document.backgroundImageURL,
fcmToken: document.fcmToken,
accountCreated: document.accountCreated,
inspirationCount: document.inspriationCount,
BucketListCount: document.BucketListCount,
CompletedBucketListCount: document.CompletedBucketListCount,
FriendsCount: document.FriendsCount
};
algoliaRecords.push(record);
});
// After all records are created, we save them to
collectionIndex.saveObjects(algoliaRecords, (_error: any, content: any) => {
res.status(200).send("users collection was indexed to Algolia successfully.");
});
});
If you just want to change the default 1 minute timeout, you can do that when you configure the function.
functions.runWith({timeoutSeconds: X}).https.onRequest(async (req, res)
Increasing the timeout won't help if your function doesn't end up sending a response, so you should also add some logging/debugging to figure out if the final call to res.send() is actually happening. If the function never sends a response, it will definitely time out no matter what happens.
I want to get data from my database on fire base and want to save that data to amount amount: snapshot, I did apply this const snapshot = firestore.collection('payment').doc(context.params.amount).get(); does that works in the same way? but I am getting an error that context is undefined.
I actually want to get data from database.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const firestore= admin.firestore();
const stripe = require('stripe')('');
const snapshot = firestore.collection('payment').doc(context.params.amount).get();
const customer = stripe.customers.create({
email: 'customer#example1.com',
});
stripe.customers
.create({
email: 'foo-customer#example.com',
})
.then((customer) => {
return stripe.customers.createSource(customer.id, {
source: 'tok_visa',
});
})
.then((source) => {
return stripe.charges.create({
amount: snapshot,
currency: 'usd',
customer: source.customer,
});
})
.then((charge) => {
// New charge created on a new customer
})
.catch((err) => {
// Deal with an error
});
you are trying to get amount through accessing params through context,
depends on your error, this means context is undefined which means you are trying to get params of undefined. you need to explain what is context means here, is it a global variable? is this code inside a cloud function? if yes you need to move this declaration const snapshot = firestore.collection('payment').doc(context.params.amount).get();
inside your cloud function ,
this is an example of firebase cloud function
I am following a tutorial where I am adding some Firebase Cloud Functions to my project (step 5). I have successfully deployed my cloud function to firebase but nothing happens when I add a new product manually in the Firebase Database console. I discovered that the Firebase cloud function is triggered but it is getting an error: "TypeError: Cannot read property 'productId' of undefined"
What am I doing wrong?
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.sendMessage = functions.firestore
.document('products/{productId}')
.onCreate(event => {
const docId = event.params.productId; // <-- error here
const name = event.data.data().name;
const productRef = admin.firestore().collection('products').doc(docId)
return productRef.update({ message: `Nice ${name}! - Love Cloud Functions`})
});
That tutorial must be out of date. Some things have changed in the Functions SDK when it released version 1.0. You can read about those changes here.
Database triggers are now passed two parameters instead of one. The new context parameter contains the value of wildcards in the reference path:
exports.sendMessage = functions.firestore
.document('products/{productId}')
.onCreate((snapshot, context) => {
const docId = context.params.productId;
If you want to continue with that tutorial, you'll have to manually convert all of its old stuff to new stuff.
OK. So thanks to Dough Stevensson's answer notifying me that the syntax was old I have now a solution:
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
var db = admin.firestore();
exports.sendMessage = functions.firestore
.document('products/{productId}')
.onCreate((snapshot, context) => {
const docId = context.params.productId;
const productRef = db.collection('products').doc(docId)
return productRef.update({ message: `Nice ${name}!`})
});