Cloud Functions and Firestore does not go up the root in Firebase - firebase

// The Cloud Functions for Firebase SDK to create Cloud Functions and
setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.giveCard = functions.firestore
.document('Profiles/{profileId}/cards/{cardsId}/_given/{_givenID}')
.onWrite((event) => {
// Get the field values of what I am working with
const oldGiven = event.data.previous.data()['given'];
const newGiven = event.data.data()['given'];
// Get the cardID to make sure that is there
const cardID = event.params.cardsId;
// An array to go through
const give_profiles = event.data.data()['given_profiles'];
// error cardDatatwo is returned as undefined
const cardDatatwo = newGiven.parent;
// error cardDatathree is returned as undefined
const cardDatathree = event.data.ref.root
// // error cardDatafour cannot read propoerty of undefined
// const cardDatafour = cardDatathree.child('Profiles/{profileId}/cards/{cardsId}')
// error cardDatafive 'The value of cardfive is DocumentReference...
const cardDatafive = event.data.ref.firestore.doc('Profiles/{profileId}/cards/{cardsId}');
// Check that the values have changed
if (newGiven == oldGiven) return;
if (newGiven !== undefined) {
console.log('The old value of given is', oldGiven);
console.log('The new value of given is', newGiven);
console.log('The value of the card is', cardID);
console.log('The value of the cardtwo is', cardDatatwo);
console.log('The value of the cardthree is', cardDatathree);
// console.log('The value of the cardfour is', cardDatafour);
console.log('The value of the cardfive is', cardDatafive);
for (var profile of give_profiles) {
console.log(profile);
};
return;
}
return console.log("No given value");
});
I am having great difficulty in getting the root for Firestore working with Cloud Functions. It works differently of course.
I am try to get a value up the path towards the root after an onUpdate has been fired further down.
.parent does not work
functions.database.ref of course does not work as that's the realtime database
and cannot use
firebase.firestore() is also not working in node
and event.data.ref.firestore.doc comes back as undefined.
I am sure have gone through every option.
Hope you can help.
Wo

According to the documentation, you can access collections via firestore, like this:
exports.giveCard = functions.firestore
.document('Profiles/{profileId}/cards/{cardsId}/_given/{_givenID}')
.onWrite((event) => {
// other code
const ref = event.data.ref.firestore.doc('your/path/here');
return ref.set({foo: 'bar'}).then(res => {
console.log('Document written');
});
});
You can use firestore to build a path to whatever part of the database you're seeking to access. You can also use event.data.ref.parent, like so:
exports.giveCard = functions.firestore
.document('Profiles/{profileId}/cards/{cardsId}/_given/{_givenID}')
.onWrite((event) => {
// other code
const parentref = event.data.ref.parent;
const grandparentref = parentref.parent; // gets to cardsId
return grandparentref.set({foo: 'bar'}).then(res => {
console.log('Document written');
});
});

Related

Firebase Function Unable to Find userId and tweetId

I am using Firebase functions for Firestore database. I am trying to update a field based on the new tweet being added.
Here is my Firebase Function on production:
const admin = require('firebase-admin')
admin.initializeApp()
const db = admin.firestore()
const functions = require("firebase-functions");
functions.logger.log("START OF FUNCTION");
exports.myFunction = functions.firestore
.document('timelines/{userId}/tweets/{tweetId}')
.onCreate((change, context) => {
const userId = context.params.userId
const tweetId = context.params.tweetId
functions.logger.log(context.params.userId);
functions.logger.log(context.params.tweetId);
db.doc(`/timelines/${userId}/tweets/${tweetId}`).update({likeCount: 200})
})
I am triggering it through an iPhone app. I am logged in to my account and I add a new Tweet. The Firebase function does get called but userId and tweetId are undefined. I am not sure why they are undefined. Any ideas?
Without knowing your client-side logic it's difficult to know if there are other issues. I would suggest adding some error handling to narrow down the cause. You could also try pulling it from the data response instead of context (assuming the schema matches).
Also note using 'snap' instead of 'change' as change is generally reserved for 'onWrite' and 'onUpdate' hooks.
exports.myFunction = functions.firestore
.document('timelines/{userId}/tweets/{tweetId}')
.onCreate(async (snap, context) => {
try {
const { userId, tweetId } = snap.data();
functions.logger.log(userId);
functions.logger.log(tweetId);
return await db.doc(`/timelines/${userId}/tweets/${tweetId}`).update({ likeCount: 200 });
}
catch (error) {
functions.logger.log(error);
}
});

How to delete document collection and all nested data from auth.user.onDelete trigger

Currently, the logic for deleting user data is the following:
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
const firestore_tools = require('firebase-tools');
admin.initializeApp();
const Auth = admin.auth();
const UsersCollection = admin.firestore().collection(`users`);
exports.deleteUserDocuments = functions.auth.user().onDelete((user) => {
const userID = user.uid;
UsersCollection.doc(userID)
.delete({})
.catch(error => {
return error
});
});
But since the user document record contains nested collections that contain other documents and collections they are still preserved due to the fact:
When you delete a document, Cloud Firestore does not automatically delete the documents within its sub-collections
I've researched a bit and found a documentation on how to create a callable function:
https://firebase.google.com/docs/firestore/solutions/delete-collections
But I wonder is it possible to have this logic instead executed from the auth.user.onDelete trigger?
Update with the Solution
const firestore_tools = require('firebase-tools');
exports.deleteUserDocuments = functions.auth.user().onDelete((user) => {
const userID = user.uid;
const project = process.env.GCLOUD_PROJECT;
const token = functions.config().ci_token;
const path = `/users/${userID}`;
console.log(`User ${userID} has requested to delete path ${path}`);
return firestore_tools.firestore
.delete(path, {
project,
token,
recursive: true,
yes: true,
})
.then(() => {
console.log(`User data with ${userID} was deleted`);
})
});
You can run whatever code you want in whatever trigger you want. The type of the trigger doesn't have any bearing on the type of code you can run.

dialogflow chatbot how to go to firestore database and return with support relevant questions?

I am using "firestore" database for my "dialogflow" chat bot which I already created for an online grocery store. The problem is: I want my chatbot to initially ask questions from users to find the proper item title in my database and then return to user by asking 3-4 support relevant questions about that item. the questions must be the item attributes (Brand, color, size...) and will vary from one item to another. so the chatbot will stream down the user to find the best item.
can you please help me to find the answer? I already created the codes but they don't work and I do not know what is wrong with that. If you already created this and have the index.js file, I appreciate to propose me here.
index.js:
'use strict';
const functions = require('firebase-functions');
// Import admin SDK
const admin = require('firebase-admin');
const {
WebhookClient
} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:*'; // enables lib debugging statements
admin.initializeApp(functions.config().firebase);
// here we get the database in a variable
const db = admin.firestore();
const data = {...};
// Add a new document in collection "dialogflow" with document ID 'agent'
const dialogflowAgentRef = db.collection('dialogflow').doc('agent').set(data);
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({
request,
response
});
function writeToDb(agent) {
// Get parameter from Dialogflow with the string to add to the database doc
const databaseEntry = agent.parameters.databaseEntry;
// Get the database collection 'dialogflow' and document 'agent' and store
// the document {entry: "<value of database entry>"} in the 'agent' document
const dialogflowAgentRef = db.collection('dialogflow').doc('agent').where('title', '==', title);
return db.runTransaction(t => {
t.set(dialogflowAgentRef, {
entry: databaseEntry
});
return Promise.resolve('Write complete');
}).then(doc => {
agent.add(`Wrote "${databaseEntry}" to the Firestore database.`);
}).catch(err => {
console.log(`Error writing to Firestore: ${err}`);
agent.add(`Failed to write "${databaseEntry}" to the Firestore database.`);
});
}
function readFromDb(agent) {
// Get the database collection 'dialogflow' and document 'agent'
const dialogflowAgentDoc = db.collection('dialogflow/agent/rss/channel/item'); // .doc('agent')
// Get the value of 'entry' in the document and send it to the user
return dialogflowAgentDoc.get()
.then(doc => {
if (!doc.exists) {
agent.add('No data found in the database!');
} else {
agent.add(doc.data().entry);
}
return Promise.resolve('Read complete');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
agent.add('Please add a entry to the database first by saying, "Write <your phrase> to the database"');
});
}
// Map from Dialogflow intent names to functions to be run when the intent is matched
let intentMap = new Map();
intentMap.set('ReadFromFirestore', readFromDb);
intentMap.set('WriteToFirestore', writeToDb);
agent.handleRequest(intentMap);
});
There are a number of issues with your code as you've shown it that could cause problems reading and writing with the Firestore database.
It looks like you're trying to find an existing collection to write to with the line
const dialogflowAgentRef = db.collection('dialogflow').doc('agent').where('title', '==', title);
but title isn't defined anywhere, which I suspect causes an error. Furthermore, doc() returns a DocumentReference, but there is no where() method in a DocumentReference.
Remember that you need to structure Firestore using alternating collections and documents. So your "firebase" collection can contain a document named "agent", and that document may have subcollections.
When you're trying to read with
const dialogflowAgentDoc = db.collection('dialogflow/agent/rss/channel/item');
You're getting a collection, but then trying to treat it as a document. The comment suggests that you're trying to read a specific doc from this collection (which makes sense), but you're loading that document by a hard-coded string "agent", rather than trying to get the agent from the parameters passed to you from Dialogflow.
Finally - the paths in the read and write sections don't match. Using hard-coded paths are fine when testing, but make sure you're using matching paths and that they reflect the collection/doc/collection/doc/... path requirement.
So in both cases, you might have a reference that looks something like
const docTitle = agent.parameters.title;
const docRef = db.collection('dialogflow').doc(title);
Which, if you have defined a "title" parameter in your Intents in Dialogflow, will use this to reference the doc, which you can then read or write.
thanks for the answer I already changed my database to real time firebase instead of firestore. still having problem with support relevant questions. I want to go to my real time database to find the item by search using "oederByChild" and "equalTo" methods as I found these in people questions and answer in this website. still cannot find and item title through my database child. here is the codes are written:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {
WebhookClient
} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:*'; // enables lib debugging statements
admin.initializeApp(functions.config().firebase);
const db = admin.database();
// const ref = db.ref('server/saving-data/fireblog');
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({
request,
response
});
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
// Get the database collection 'dialogflow' and document 'agent' and store
// the document {entry: "<value of database entry>"} in the 'agent' document
function writeToDb(agent) {
const databaseEntry = agent.parameters.databaseEntry;
const acc = db.ref('rss/channel/item/4/title'); //**This worked! */
acc.set({
entry: databaseEntry
});
return Promise.resolve('write complete')
.then(_acc => {
agent.add(`Wrote ${databaseEntry} to the realtime database.`);
return false;
}).catch(err => {
console.log(`Error writing to Firestore: ${err}`);
agent.add(`Failed to write "${databaseEntry}" to the Firestore database.`);
});
}
// and this is when we want to write to in the same child, keeping the old values:
//const acc = db.ref('/rss/channel/item/5/color'); //**This worked! */
//const result = acc.child(databaseEntry).set({entry: databaseEntry});
//agent.add(`Wrote ${databaseEntry} to the realtime database.`);
//console.log(result.key);
//});
// to read data
function readFromDb(agent) {
const any = agent.parameters.any;
agent.add(`Thank you...`);
var rootRef = db.ref();
var childref = rootRef.child("rss/channel/item");
return childref.orderByChild("title").equalTo("Icebreaker").once("value").then(function(snapshot){ //has been taken from the bus example: https://stackoverflow.com/questions/51917390/dialogflow-how-do-i-pass-a-parameter-through-in-a-firebase-query
var colored = snapshot.child("color/__text").val();
var sized = snapshot.child("size/__text").val();
agent.add(`Your search result for ` + any + ` Throughout the database is ` + colored +
` Color and ` + sized + ` Size`);
return Promise.resolve('Read complete');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
agent.add('Please add a entry to the database first by saying, "Write <your phrase> to the database"');
});
}
// Map from Dialogflow intent names to functions to be run when the intent is matched
let intentMap = new Map();
intentMap.set('IWannaBuy', readFromDb);
intentMap.set('WriteToFirebase', writeToDb);
agent.handleRequest(intentMap);
});
enter code here
[this is how my database is][1]
[1]: https://i.stack.imgur.com/QdFy5.png

How to Count Users with a Firebase Cloud Function (getting Function Returned Undefined error)

I have a Firebase Cloud Function that assigns a number to a user on onWrite. The following code works but something is wrong because the console logs state Function returned undefined, expected Promise or value.
I'm also not sure how to refer to the root from inside the onWrite so I've created several "parent" entries that refer to each other. I'm sure there is a better way.
onWrite triggers on this:
/users/{uid}/username
The trigger counts the children in /usernumbers and then writes an entry here with the uid and the child count + 1:
/usernumbers/uoNEKjUDikJlkpLm6n0IPm7x8Zf1 : 5
Cloud Function:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.setCount = functions.database.ref('/users/{uid}/username').onWrite((change, context) => {
const uid = context.params.uid;
const parent1 = change.after.ref.parent; //uid
const parent2 = parent1.ref.parent; //users
const parent3usernumbers = parent2.ref.parent.child('/usernumbers/');
const parent3usernumbersuid = parent2.ref.parent.child('/usernumbers/'+uid);
parent3usernumbers.once("value")
.then(function(snapshot) {
var a = snapshot.numChildren();
return parent3usernumbersuid.transaction((current) => {
return (a + 1);
}).then(() => {
return console.log('User Number Written', uid, a);
});
});
});
Is there a better way to do this? How can I get the Function Returned Undefined error to go away?
I should also mention it takes a few seconds for the 'usernumber' entry to be written. I'm guessing it's waiting for the function to return something.
Your function have to return a Promise :
exports.setCount = functions.database.ref('/users/{uid}/username').onWrite((change, context) => {
const uid = context.params.uid;
const parent1 = change.after.ref.parent; //uid
const parent2 = parent1.ref.parent; //users
const parent3usernumbers = parent2.ref.parent.child('/usernumbers/');
const parent3usernumbersuid = parent2.ref.parent.child('/usernumbers/'+uid);
return new Promise((resolve, reject) => {
parent3usernumbers.once("value").then(function(snapshot) {
var a = snapshot.numChildren();
return parent3usernumbersuid.transaction((current) => {
return (a + 1);
}).then(() => {
console.log('User Number Written', uid, a);
resolve({uid : uid, a : a})
}).catch(function(e) {
reject(e)
})
});
});
});

Cloud Functions for Firebase Time Out w/ wrong response

Newbie question: Cloud Function times out every single time I run it.
In addition, it only returns ONE value, which is the first userId, in the Functions Log and none of its children. Im assuming this is because it's calling the .once however, it's in a forEach loop, so I'm not sure what it wants.
Firebase database
-items
---- userId0123456789
---- randomKey987654321
-- itemName
-- itemDate
-- itemType
---- userId987654321
---- randomKey012345678
-- itemName
-- itemDate
-- itemType
And here is the function code...
const key = req.query.key;
**let userID = 'xxxxx';
let ikey = 'xxx';**
var dbRef = admin.database().ref('/items/{userID}/{ikey}');
dbRef.once("value", function(snapshot) {
snapshot.forEach(function(child) {
console.log(child.key+": "+child.val());
});
});
UPDATE: here is the entire function and now it's just timing out with no response.
'use strict';
// Firebase Functions
const functions = require('firebase-functions');
// Firebase Admin
const admin = require('firebase-admin');
// Default admin firebase configuration
admin.initializeApp(functions.config().firebase);
const rp = require('request-promise');
const promisePool = require('es6-promise-pool');
const PromisePool = promisePool.PromisePool;
const secureCompare = require('secure-compare');
const MAX_CONCURRENT = 3;
//Initial function call:
exports.CheckItemTypeinFB = functions.https.onRequest((req, res) => {
const key = req.query.key;
// Exit if the keys don't match
if (!secureCompare(key, functions.config().cron.key)) {
console.log('The key provided in the request does not match the key set in the environment. Check that', key,
'matches the cron.key attribute in `firebase env:get`');
res.status(403).send('Security key does not match. Make sure your "key" URL query parameter matches the ' +
'cron.key environment variable.');
return;
}
// Try the database here...
let userID = 'xxx';
let ikey = 'xxxxx
//create database ref
let ref = admin.database().ref(`/items/${userID}/${ikey}`);
//do a bunch of stuff
ref.once("value", function(snapshot) {
snapshot.forEach(function(child) {
console.log(`${child.key}: ${child.val()}`);
});
res.send(200, {/* response data */});
});
//send back response
// res.redirect(200);
}) // END THE MAJJOR CONTAINER THINGS
// Returns an access token using the Google Cloud metadata server. */
function getAccessToken(accessToken) {
// If we have an accessToken in cache to re-use we pass it directly.
if (accessToken) {
return Promise.resolve(accessToken);
}
const options = {
uri: 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token',
headers: {'Metadata-Flavor': 'Google'},
json: true
};
return rp(options).then(resp => resp.access_token);
}
Help is much appreciated.
Update:. Timeout is fixed and it returns the userId's that are in the database under "/items". HOWEVER, if I use ${userId}/${key} I get nothing. I'm still not able to tell how to get the children under random userId's in the database and none of the other posts I read explain it. Firebase's docs state to use {userId} to get all under that wildcard but its not working. What am I missing?
You're not returning the result of the once function or returning at all, so the function doesn't know when to finish hence the timeout.
let userID = 'xxxxxxxxx';
let key = 'xxxxxxxx';
let ref = admin.database().ref(`/items/${userID}/${key}`);
return ref.once("value", function(snapshot) {
snapshot.forEach(function(child) {
console.log(`${child.key}: ${child.val()}`);
});
});
Also please be aware that the reference you are observing will give you the children of a particular user item (itemName, itemDate, itemType). If you want the items belonging to a particular user, adjust your reference path to be /items/${userID}.
When inside a HTTP trigger, you can send a response after observing the value.
exports.CheckItemTypeinFB = functions.https.onRequest((req, res) => {
...
ref.once("value", function(snapshot) {
snapshot.forEach(function(child) {
console.log(`${child.key}: ${child.val()}`);
});
res.send(200, {/* response data */});
});
});

Resources