How do I share information between functions on Firebase realtime database - javascript

I am developing a project now in react-native and I integrated firebase to handle my data.
I am now working on the index.js file from the function.
So basically what I am trying to do is through the use of .onCreate functions retrieve the information of the user once it is created and use the same information in .onCreate function for orders.
I tried to store my information on global variables but it doesn't work it appears as undefined.
Probably in the image it would be explained better.
This is where i am having problems
var username;
// ------------ USER DATA FUNCTION ------
exports.requestUserData = functions.database.ref('/users/{userId}/{autoGen}')
.onCreate((snap, context) => {
username = snap._data.name;
console.log(` Username: ${username}`);
});
// ---- ORDERS FUNCTION -----
exports.sendEmail = functions.database.ref('/orders/{userId}/{orderId}/')
.onCreate((snap, context) => {
const userId = context.params.userId;
// USERNAME IS WHAT I NEED TO GET TO USE IN THIS FUNCTION
console.log(`THIS IS WHAT I AM EXPECTING TO GET! --> ${username}`);
EDIT: If it is not possible then could i make a request to a database within another function?
Thanks a lot for any help!!

Cloud Functions cannot share information through shared global variables. Each function runs completely independently from each other and have no shared state. You're going to have to find a way to share data by some other means, such as another location in the database.
I suggest watching this for more information: https://www.youtube.com/watch?v=rCpKxpIMg6o

As Doug said, you can't share information between different Cloud Functions through global variables. The two functions run in different containers. But even if they ran on the same container, there's no guarantee that requestUserData would be invoked before sendEmail.
Instead, you'll need to read the username inside the sendEmail function with something like this:
var username;
exports.sendEmail = functions.database.ref('/orders/{userId}/{orderId}/')
.onCreate((snap, context) => {
const userId = context.params.userId;
return snapshot.ref.root.child(`/users/${userId}/name`).once("value).then((nameSnapshot) => {
const username = nameSnapshot.val();
console.log(username);
... any code that needs the username
});
});

Related

Making a follow system with firestore and cloud functions

Hi I am attempting to make a social media app on Firestore.
Now to model a follow system here is my plan.
users (Collection)
{uid} document which contains Followers and Following as a number.
following (Collection)
{uid}
myFollowing (subCollection)
{uid of other user}
followers (Collection)
{uid}
myFollowers (subCollection)
{uid of other user}
So here is my plan, and please feel free to critique it and help me make it better, because I dont know if this is the best way to do it.
When user A follows user B, I will write a document in:
following
A uid
myFollowing
B uid
This write will happen straight from the app.
After which I plan to trigger a cloud function that does two things, 1. It will increment a counter in the users collection, that holds total following. 2. It will write another document which would be
Followers
B uid
myFollowers
A uid
And after this I can have another cloud function that triggers whenever a document is made in the Followers/uid/myFollowers collection which increments followers count in the users collection.
So here are the questions
Is this the best way to go about this?
How do i write the cloud functions?
Thanks for any help you can give me!
I solved this by doing everything I did above, and using the following code for cloud functions
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.onFollowCreate = functions.firestore
.document("following/{userID}/myFollowing/{id}")
.onCreate((snap, context) => {
const newValue = snap.data()
const db = admin.firestore();
db.collection("users").doc(context.params.userID).update({following: admin.firestore.FieldValue.increment(1)}).catch((er)=>{console.log(er)})
db.collection('followers').doc(newValue.uid).collection("myFollowers").doc(context.params.userID).set({uid: context.params.userID, timeStamp: new Date()}).catch(er=>console.log(er))
});
exports.onFollowDelete = functions.firestore
.document("following/{userID}/myFollowing/{id}")
.onDelete((snap, context)=>{
const deletedValue = snap.data()
const db = admin.firestore();
db.collection("users").doc(context.params.userID).update({following: admin.firestore.FieldValue.increment(-1)}).catch(er=>console.log(er))
db.collection('followers').doc(deletedValue.uid).collection("myFollowers").doc(context.params.userID).delete().catch(er=>console.log(er))
})
exports.onFollowersCreate = functions.firestore
.document("followers/{userID}/myFollowers/{id}")
.onCreate((snap, context)=>{
const db = admin.firestore();
db.collection("users").doc(context.params.userID).update({followers: admin.firestore.FieldValue.increment(1)}).catch(er=>console.log(er))
})
exports.onFollowersDelete = functions.firestore
.document("followers/{userID}/myFollowers/{id}")
.onDelete((snap, context)=>{
const db = admin.firestore();
db.collection("users").doc(context.params.userID).update({followers: admin.firestore.FieldValue.increment(-1)}).catch(er=>console.log(er))
})
I've thought of this before and it was very similar. I think this might be the best way to go about structuring your database. Here's an article on Medium about some database designs.
Now for the functions, you want one which will trigger once you write that document about A following B. See the docs for a onCreate function. Your cloud functions will live in a node.js 10 serverless environment and will have no connection to your front-end application. Here's a real world example of some of my functions on a deployed site. I would recommend not adding data to firestore on your front-end. Instead make a onCall HTTP function, see more about those here.
Sorry for not giving you actual code to go off of, but I find doing it yourself will help you learn. Good luck :)

Add field to document using cloud functions

I wanted to make the id of each document as a field of that document so that I can store it inside the doc. here is the cloud function I created:
exports.assignPID = functions.database
.ref('/players/{playerId}')
.onCreate((snapshot,context)=>{
const playerId = context.params.playerId;
console.log("new player "+playerId);
// const data = snapshot.val();
return snapshot.ref.update({'pid': playerId})
})
this deploys without any errors but whenever I add a new document to the 'players' collection there is no change in the document whatsoever
In your question, you use the word "document" to describe your data, which is a Firestore concept, and you've tagged this question google-cloud-firestore. However, the code you've written is for Realtime Database, which is a completely different product. So it will never run in response to changes in Firestore.
When you declare a function with functions.database, that's means Realtime Database. Instead, you should declare a Firestore trigger with functions.firestore. Please read the linked documentation for information about how to do that.

How to query database with firebase cloud functions

I am trying to query my firestore database using cloud functions.
I want to trigger an email notification every time a new reading in my database is under the value of 10.
Here is the relevant database structure for reference: database structure.
The "readings" field is an array and each "reading" is a map which holds the fields "date" and "value".
Currently I am at the point where I can send an email notification every time a new user is created however I want this to work for the database. I am unsure how to query for the "readings" array and then for each individual reading.
Here is my code so far which sends an email when a new user is created
exports.sendNotification = functions.auth.user().onCreate((user) => {
const mailOptions = {
from: '"Spammy Corp." <noreply#firebase.com>',
to:"fakeEmail#btopenworld.com",
text: "TEST"
};
return mailTransport.sendMail(mailOptions)
.then(() => console.log("It worked"))
.catch((error) =>
console.error('There was an error while sending the email:', error));
});
See: https://firebase.google.com/docs/firestore/extend-with-functions
For example, to fire on all new readings added to that first child:
exports.sendEmail = functions.firestore
.document('sensor/UGt.../readings')
.onCreate((snap, context) => {
const newValue = snap.data();
const value = newValue.value;
if (value < 10) {
// send email
}
});
In further comments you mentioned listening for new readings in all sensor elements, not just your first one. This is unfortunately not possible in an efficient / simple way (source). Instead you will have to listen to all onUpdate events on /sensor/, check if the update is adding a reading, then check the value & send your email.
It may be easier to call the cloud function directly from wherever adds the reading, depending on how many times the /sensor/ path is going to be updated for other reasons (since every time this happens, it's a waste of resources).

How can I make Dialogflow agent greet user if they have used the action before?

I'm using Actions On Google / Dialogflow, and I'm trying to make a function that will greet a user by their name if they've used the action before, and if not, will ask for their name. I have tried to map this to the "Welcome intent" through fulfillment, but whenever I try to run the action on the simulator, I get this error:
Error 206: Webhook Error
Which Initially would make sense if this was mapped to another intent, but I'm wondering if I'm getting this error because you can't have a fulfillment with the welcome intent?
Here's the code I'm using in the inline editor which may be the problem:
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request,response) => {
const agent = new WebhookClient({ request, response });
function welcome(conv) {
if (conv.user.last.seen) {
conv.ask(`Welcome back ${name}!`);
} else {
conv.ask('Welcome to The app! My name is Atlas, could I get your name?');
}}
let intentMap = new Map();
intentMap.set('Welcome Intent', welcome);
agent.handleRequest(intentMap);
How come this isn't working? Do I need to implement user login? Do I need to use a function that would write to a firestore databbase?
Thanks for the help or suggestions!
Let's clear a few things up to start:
You can have fulfillment with your welcome intent.
You do not need user login. Although using Google Sign In for Assistant can certainly be used, it doesn't fundamentally change your problem here.
You do not need to use a function that writes to the firestore database. Again, you could use it, but this doesn't change your problems.
The specific reason this isn't working is because the conv parameter in this case contains a Dialogflow WebhookClient rather than an actions-on-google Conversation object.
To get the Conversation object with the parameter you have, you can call conv.getConv(), which will give you an object that has a user parameter. So this may look something like
function welcome(conv) {
let aog = conv.getConv();
if (aog.user.last.seen) {
conv.ask(`Welcome back ${name}!`);
} else {
conv.ask('Welcome to The app! My name is Atlas, could I get your name?');
}}
There are, however, still some issues with this. Most notably, it isn't clear where name will come from. I assume you will get it out of the user store object, but you don't seem to have done this.
For anyone who comes across this question in the future and just wants a straight forward answer without having to search through ambiguous answers / documentation, here is what to do step by step:
note: I ended up using the Google Sign in method, but even if this isn't your goal, i'll post the link to the alternative method.
1) Import the actions on google module. What people / tutorials don't to show is you have to import the library like this (for user login):
const {
dialogflow,
Permission,
SignIn
} = require('actions-on-google')
instead of
const dialogflow = require('actions-on-google')
2) Use this code:
const app = dialogflow({
clientId: '<YOUR CLIENT ID from Actions on Google>',
});
app.intent('Start Signin', conv => {
conv.ask(new SignIn('To get your account details'));
});
app.intent('Get Signin', (conv, params, signin) => {
if (signin.status === 'OK') {
const payload = conv.user.profile.payload;
conv.ask(`Welcome back ${payload.name}. What do you want to do next?`);
} else {
conv.ask(`I won't be able to save your data, but what do you want to do next?`);
}
});
This function will ask the user for a login, and next time you invoke the intent, it will say "Welcome back name", because google automatically saves it.
Here's the link to the alternative method:

delete incoming write event after calculations in firebase functions

I have an app that uses firebase, the whole stack pretty much, functions, database, storage, auth, messaging, the whole 9. I want to keep the client end very lightweight. So if a user comments on a post and "tags" another user, let's say using the typical "#username" style tagging, I moved all of the heavy lifting to the firebase functions. That way the client doesn't have to figure out the user ID based on the username, and do everything else. It is setup using triggers, so when the above scenario happens I write to a "table" called "create_notifications" with some data like
{
type: "comment",
post_id: postID,
from: user.getUid(),
comment_id: newCommentKey,
to: taggedUser
}
Where the taggedUser is the username, the postID is the active post, the newCommentKey is retrieved from .push() on the comments db reference, and the user.getUid() is from the firebase auth class.
Now in my firebase functions I have a "onWrite" trigger for that specific table that gets all of the relevant information and sends out a notification to the poster of the post with all the relevant details. All of that is complete, what I am trying to figure out is... how do I delete the incoming event, that way I don't need any sort of cron jobs to clear out this table. I can just grab the event, do my needed calculations and data gathering, send the message, then delete the incoming event so it never even really exists in the database except for the small amount of time it took to gather the data.
A simplified sample of the firebase functions trigger is...
exports.createNotification = functions.database.ref("/create_notifications/{notification_id}").onWrite(event => {
const from = event.data.val().from;
const toName = event.data.val().to;
const notificationType = event.data.val().type;
const post_id = event.data.val().post_id;
var comment_id, commentReference;
if(notificationType == "comment") {
comment_id = event.data.val().comment_id;
}
const toUser = admin.database().ref(`users`).orderByChild("username").equalTo(toName).once('value');
const fromUser = admin.database().ref(`/users/${from}`).once('value');
const referencePost = admin.database().ref(`posts/${post_id}`).once('value');
return Promise.all([toUser, fromUser, referencePost]).then(results => {
const toUserRef = results[0];
const fromUserRef = results[1];
const postRef = results[2];
var newNotification = {
type: notificationType,
post_id: post_id,
from: from,
sent: false,
create_on: Date.now()
}
if(notificationType == "comment") {
newNotification.comment_id = comment_id;
}
return admin.database().ref(`/user_notifications/${toUserRef.key}`).push().set(newNotification).then(() => {
//NEED TO DELETE THE INCOMING "event" HERE TO KEEP DB CLEAN
});
})
}
So in that function in the final "return" of it, after it writes the finalized data to the "/user_notifications" table, I need to delete the event that started the whole thing. Does anyone know how to do that? Thank you.
First off, use .onCreate instead of .onWrite. You only need to read each child when they are first written, so this will avoid undesirable side effects. See the documentation here for more information on the available triggers.
event.data.ref() holds the reference where the event occurred. You can call remove() on the reference to delete it:
return event.data.ref().remove()
The simplest way to achieve this is through calling the remove() function offered by the admin sdk,
you could get the reference to the notification_id through the event, i.e event.params.notification_id then remove it when need be with admin.database().ref('pass in the path').remove(); and you are good to go.
For newer versions of Firebase, use:
return change.after.ref.remove()

Categories

Resources