How to use dialogflow Fulfillment Inline Editor to save users names and moods on a realtime database? - firebase

I built action on google using dialogflow for experience sampling purpose.
It's idea is: it asks specific users about their mood 3 times per day.
It sends these users then every week a weekly overview about their mood after it has been analysed by researchers.
So I need to save each user info with his mood entries on a database so they can be accessed later by researchers, analysed and sent back to users.
I'm using dialogflow fulfilment with index.js to connect to Firebase database to save the entries.
This agent should be integrated as action on google
On the database I get users names and moods but they are not related to each other so I cannot know which user entered which mood and, also I cannot do the userID check.
I would really appreciate if anybody could help me with the functions since I am totally unfamiliar with node.js or databases but I have to do it that way.
here is my code.
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
//initialise DB connection
const admin = require('firebase-admin');
admin.initializeApp();
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
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));
function saveName(agent) {
const nameParam = agent.parameters.name;
const context = agent.getContext('awaiting_name_confirm');
const name = nameParam || context.parameters.name;
agent.add('Hi ' + name + ' Are you ready to answer my question?' || 'Hi' + name + 'Have you got a moment for me? ' );
//agent.add('Hi' + name + 'Have you got a minute for me? ');
return admin.database().ref('/names').push({name: name}).then((snapshot)=>
{
console.log('database write sucessful: ' + snapshot.ref.toString());
});
}
function saveMood(agent) {
const moodParam = agent.parameters.mood;
const mood = moodParam;
agent.add('That is good! keep it up. Thanks for sharing with me! Bye ');
//agent.add('Hi' + name + 'Have you got a minute for me? ');
return admin.database().ref('/moods').push({mood: mood}).then((snapshot)=>
{
console.log('database write sucessful: ' + snapshot.ref.toString());
});
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Get Name', saveName);
intentMap.set('Confirm Name Yes', saveName);
// intentMap.set('Confirm Name Yes', getName);
intentMap.set('attentiveness', saveMood);
agent.handleRequest(intentMap);
});

You have a few issues in your code and approach that you'll need to address:
You need to design your database so that you can associate the user's mood with their account. (And possibly other user information, such as their name or email address, while you're at it.)
You need a unique identity for the person.
A person's name isn't a unique identity. Two people may have the same name, or the system may hear the name differently each time, so you need to have a way to know who you're talking to.
You also need to know their email address and possibly other information, so you can send them their report at the end of the week.
You need to make sure you have their identity between calls to your Action during the same conversation.
Fortunately, you do one thing that is typically missed - you make your calls to the database using Promises. So that part works.
Unique Identity
Your example code asks the user for their name, which it sounds like you intend to use as their identity. Unfortunately, this is a bad idea for a few reasons:
The name isn't an identity. What happens if two people with the same name access your Action?
Names are easily discoverable, so other people could use it and report misleading information. This may not be too serious in your case, but it can still have trustworthiness implications.
Names can be Personally Identifiable Information (PII), so may be covered by additional privacy laws.
Users might want to terminate their account, and can't do this without "changing" their name.
Additionally, you may need other identity information later, such as their email address, and asking for that every time may become troublesome.
You have a few ways to deal with this:
If you're developing for the Google Assistant, you can also use Google Sign In for Assistant which will tell you the user's Google identifier, which you can use as a unique ID. You also get their email address and name as part of their profile.
You can ask for this information (name, email, etc) and save it against a user ID that you generate or a user name the user provides. This ID becomes the identifier. If you're developing for the Google Assistant, you can save this ID in the user's private storage - only you and the user have access to it or can delete it. If not, you may need to use the database to look up the ID. More on this later.
You may wish to use variants on this later point, depending what information you're getting and how you want the user to identify themselves every time. But the important part is that they need to identify themselves with something unique and that you can easily capture.
Use identity in the same session
If you're using Google Sign In, you don't have to worry about this. You'll get the same ID each session and for each call during a session.
If you're using the user's private storage with the Google Assistant, you'll have this as part of the userStore object.
But if you're not, you need to make sure that you get the user's ID in an early intent, and saving this as part of a Context so it is preserved in between calls to your webhook. In subsequent handlers, you can get the ID out of the context and then use it to access other information.
You don't need to store it in the database at this point. All you have is an identifier - this becomes the key that you will use for other information. You just need to remember it for later parts of the conversation.
So in your saveName() function, it might look something like
function saveName(agent) {
const nameParam = agent.parameters.name;
agent.add('Hi ' + nameParam + ' Are you ready to answer my question?');
agent.setContext({
name: 'user',
lifespan: 99,
parameters: {
id: nameParam
}
};
}
As an aside - your handler seems to try to determine if this is the user saying their name, or confirming their name. This is probably better handled as separate intents and separate handlers. Trying to combine them will confuse things.
Structuring and Accessing your Database
We have an ID. We have the user reporting the data. How do we associate the two?
There are a lot of ways to structure the data, and Firebase goes into some detail depending on how you intend to use it, access it, and make it available to the users or others.
In this case, it seems pretty straightforward that you want to store records about the user. Each record can use their ID as a key, and then contain some information about the user, including their mood.
One nice thing about the Firebase database is that you can (mostly) treat it like a Javascript object. If we think about it this way, it might look something like
{
"user": {
"id1":{...},
"id2":{...},
"id3":{
"moods": [
{"mood":"good"},
{"mood":"tired"}
]
},
"id4":{...}
}
}
And so forth. With Firebase, we would reference the moods of user "id3" with a path such as user/id3/moods. If we have the user id in a variable name, we might use the following code to get that reference
var ref = admin.database().ref('user').ref(name).ref('moods');
and then use code such as this to push an object with the mood onto the array (and return the Promise that we need to do):
var obj = {
mood: mood
};
return ref.push( obj ).then( snapshot => {
// Do stuff, including acknowledge to the user you saved it.
});
Keep in mind that you may want to also use this to store more information about each user (such as their name or email) on the user level, or more about the moods (such as a timestamp) in the mood object.

Related

How to do pattern searching in fire base real time DB [duplicate]

I am using firebase for data storage. The data structure is like this:
products:{
product1:{
name:"chocolate",
}
product2:{
name:"chochocho",
}
}
I want to perform an auto complete operation for this data, and normally i write the query like this:
"select name from PRODUCTS where productname LIKE '%" + keyword + "%'";
So, for my situation, for example, if user types "cho", i need to bring both "chocolate" and "chochocho" as result. I thought about bringing all data under "products" block, and then do the query at the client, but this may need a lot of memory for a big database. So, how can i perform sql LIKE operation?
Thanks
Update: With the release of Cloud Functions for Firebase, there's another elegant way to do this as well by linking Firebase to Algolia via Functions. The tradeoff here is that the Functions/Algolia is pretty much zero maintenance, but probably at increased cost over roll-your-own in Node.
There are no content searches in Firebase at present. Many of the more common search scenarios, such as searching by attribute will be baked into Firebase as the API continues to expand.
In the meantime, it's certainly possible to grow your own. However, searching is a vast topic (think creating a real-time data store vast), greatly underestimated, and a critical feature of your application--not one you want to ad hoc or even depend on someone like Firebase to provide on your behalf. So it's typically simpler to employ a scalable third party tool to handle indexing, searching, tag/pattern matching, fuzzy logic, weighted rankings, et al.
The Firebase blog features a blog post on indexing with ElasticSearch which outlines a straightforward approach to integrating a quick, but extremely powerful, search engine into your Firebase backend.
Essentially, it's done in two steps. Monitor the data and index it:
var Firebase = require('firebase');
var ElasticClient = require('elasticsearchclient')
// initialize our ElasticSearch API
var client = new ElasticClient({ host: 'localhost', port: 9200 });
// listen for changes to Firebase data
var fb = new Firebase('<INSTANCE>.firebaseio.com/widgets');
fb.on('child_added', createOrUpdateIndex);
fb.on('child_changed', createOrUpdateIndex);
fb.on('child_removed', removeIndex);
function createOrUpdateIndex(snap) {
client.index(this.index, this.type, snap.val(), snap.name())
.on('data', function(data) { console.log('indexed ', snap.name()); })
.on('error', function(err) { /* handle errors */ });
}
function removeIndex(snap) {
client.deleteDocument(this.index, this.type, snap.name(), function(error, data) {
if( error ) console.error('failed to delete', snap.name(), error);
else console.log('deleted', snap.name());
});
}
Query the index when you want to do a search:
<script src="elastic.min.js"></script>
<script src="elastic-jquery-client.min.js"></script>
<script>
ejs.client = ejs.jQueryClient('http://localhost:9200');
client.search({
index: 'firebase',
type: 'widget',
body: ejs.Request().query(ejs.MatchQuery('title', 'foo'))
}, function (error, response) {
// handle response
});
</script>
There's an example, and a third party lib to simplify integration, here.
I believe you can do :
admin
.database()
.ref('/vals')
.orderByChild('name')
.startAt('cho')
.endAt("cho\uf8ff")
.once('value')
.then(c => res.send(c.val()));
this will find vals whose name are starting with cho.
source
The elastic search solution basically binds to add set del and offers a get by wich you can accomplish text searches.
It then saves the contents in mongodb.
While I love and reccomand elastic search for the maturity of the project, the same can be done without another server, using only the firebase database.
That's what I mean:
(https://github.com/metaschema/oxyzen)
for the indexing part basically the function:
JSON stringifies a document.
removes all the property names and JSON to leave only the data
(regex).
removes all xml tags (therefore also html) and attributes (remember
old guidance, "data should not be in xml attributes") to leave only
the pure text if xml or html was present.
removes all special chars and substitute with space (regex)
substitutes all instances of multiple spaces with one space (regex)
splits to spaces and cycles:
for each word adds refs to the document in some index structure in
your db tha basically contains childs named with words with childs
named with an escaped version of "ref/inthedatabase/dockey"
then inserts the document as a normal firebase application would do
in the oxyzen implementation, subsequent updates of the document ACTUALLY reads the index and updates it, removing the words that don't match anymore, and adding the new ones.
subsequent searches of words can directly find documents in the words child. multiple words searches are implemented using hits
SQL"LIKE" operation on firebase is possible
let node = await db.ref('yourPath').orderByChild('yourKey').startAt('!').endAt('SUBSTRING\uf8ff').once('value');
This query work for me, it look like the below statement in MySQL
select * from StoreAds where University Like %ps%;
query = database.getReference().child("StoreAds").orderByChild("University").startAt("ps").endAt("\uf8ff");

Adding a button for google signin using f#/fable/asp.net/react

I'm working with the SAFE stack (https://safe-stack.github.io/) and through the example dojo. It's great so far.
I'd like to extend the example to include a button to login/auth via Google. So I looked at an example on the Google website (https://developers.google.com/identity/sign-in/web/build-button). And then I had a look how to do authentication using ASP.NET (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-2.1&tabs=aspnetcore2x) As a result I ended up confused as to how to integrate this into a SAFE project. Can someone tell me what they would do? SHould I be trying to use ASP.NET Identity or should I be using the JWT approach? I don't even know if they are the same since I'm very new to web frameworks.....
The other question I have is how would one inject raw Javascript into the client side of a SAFE project. The google example above shows raw JS/CSS/HTML code? Should I be injecting that as is or should I look in React for some button that does this and map that idea back through Fable?
Setting up OAuth
The easiest way to use Google OAuth is to wait until the next release of Saturn, at which point Saturn will include the use_google_oauth feature that I just added. :-) See the source code if you're interested in how it works, though I'm afraid you can't implement this yourself with use_custom_oauth because you'll run into a type error (the underlying ASP.NET code has a GoogleOptions class, and use_custom_oauth wants an OAuthOptions class, and they aren't compatible).
To use it, add the following to your application CE:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" []
The last parameter should be a sequence of string * string pairs that represent keys and values: you could use a list of tuples, or a Map passed through Map.toSeq, or whatever. The keys of that sequence are keys in the JSON structure that Google returns for the "get more details about this person" API call, and the values are the claim types that those keys should be mapped to in ASP.NET's claims system. The default mapping that use_google_oauth already does is:
id → ClaimTypes.NameIdentifier
displayName → ClaimTypes.Name
emails[] (see note) → ClaimTypes.Email
Those three are automatically mapped by ASP.NET. I added a fourth mapping:
avatar.url → `"urn:google:avatar:url"
There's no standard ClaimTypes name for this one, so I picked an arbitrary URN. Caution: this feature hasn't been released yet, and it's possible (though unlikely) that this string might change between now and when the feature is released in the next version of Saturn.
With those four claim types mapped automatically, I found that I didn't need to specify any additional claims, so I left the final parameter to use_google_oauth as an empty list in my demo app. But if you want more (say you want to get the user's preferred language to use in your localization) then just add them to that list, e.g.:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" ["language", "urn:google:language"]
And then once someone has logged in, look in the User.Claims seq for a claim of type "urn:google:language".
Note re: the emails[] list in the JSON: I haven't tested this with a Google account that has multiple emails, so I don't know how ASP.NET picks an email to put in the ClaimTypes.Email claim. It might just pick the first email in the list, or it might pick the one with a type of account; I just don't know. Some experimentation might be needed.
Also note that third-party OAuth, including GitHub and Google, has been split into a new Saturn.Extensions.Authorization package. It will be released on NuGet at the same time that Saturn's next version (probably 0.7.0) is released.
Making the button
Once you have the use_google_oauth call in your application, create something like the following:
let googleUserIdForRmunn = "106310971773596475579"
let matchUpUsers : HttpHandler = fun next ctx ->
// A real implementation would match up user identities with something stored in a database, not hardcoded in Users.fs like this example
let isRmunn =
ctx.User.Claims |> Seq.exists (fun claim ->
claim.Issuer = "Google" && claim.Type = ClaimTypes.NameIdentifier && claim.Value = googleUserIdForRmunn)
if isRmunn then
printfn "User rmunn is an admin of this demo app, adding admin role to user claims"
ctx.User.AddIdentity(new ClaimsIdentity([Claim(ClaimTypes.Role, "Admin", ClaimValueTypes.String, "MyApplication")]))
next ctx
let loggedIn = pipeline {
requires_authentication (Giraffe.Auth.challenge "Google")
plug matchUpUsers
}
let isAdmin = pipeline {
plug loggedIn
requires_role "Admin" (RequestErrors.forbidden (text "Must be admin"))
}
And now in your scope (NOTE: "scope" will probably be renamed to "router" in Saturn 0.7.0), do something like this:
let loggedInView = scope {
pipe_through loggedIn
get "/" (htmlView Index.layout)
get "/index.html" (redirectTo false "/")
get "/default.html" (redirectTo false "/")
get "/admin" (isAdmin >=> htmlView AdminPage.layout)
}
And finally, let your main router have a URL that passes things to the loggedInView router:
let browserRouter = scope {
not_found_handler (htmlView NotFound.layout) //Use the default 404 webpage
pipe_through browser //Use the default browser pipeline
forward "" defaultView //Use the default view
forward "/members-only" loggedInView
}
Then your login button can just go to the /members-only route and you'll be fine.
Note that if you want multiple OAuth buttons (Google, GitHub, Facebook, etc) you'll probably need to tweak that a bit, but this answer is long enough already. When you get to the point of wanting multiple OAuth buttons, go ahead and ask another question.

Steps to retrieve current user data in firebase using vuejs

I’m creating an application where only users I create manually in firebase can log in.
The app consists of a multistep form to gather user information. For example:
Step1 ==> gather user general information
Step2 ==> information about user’s company
I want the user to be able to log back in, see the form fields populated with his data and allow him to edit them if needed.
So far I have the multi step form, that sends data to firebase. I’m using vuex and vuefire.
I understand that I can retrieve the data in two ways: before/after navigation (https://router.vuejs.org/en/advanced/data-fetching.html)
Should I create a child in the database using the currentuser uid and then reference the database using a dynamic route such as"
db.ref('forms/' + user.uid)
I'm a beginnger and I don’t know how to go about this and if it's the correct way to think about it. How would you go about retrieving the data for a specific user and push that data into each field?
There seems to exist a vuexfire https://github.com/chrisbraddock/vuefire-auth-demo could that be the way to go about this?
I'm not familiar with vuefire but I would request user data like the following
// for getting data once
let uid = firebase.auth().currentUser.uid;
let dbRef = "users/"+uid;
firebase.database().ref(dbRef).once("value", snap => {
let email = snap.val().email;
...
})
// for realtime updates
let uid = firebase.auth().currentUser.uid;
let dbRef = "users/"+uid;
firebase.database().ref(dbRef).on("value", snap => {
this.yourVueObject = snap.val().yourData
});

How to get Iron-router query parameters in server hook

I am trying to add a referral system to my project, so currently I am basing it off of this package. The issue I am running into is my project only uses accounts-google and not accounts-password. The way this package works is it adds the iron router query parameters for the referrerCode (/register?r=ReferralCodeHere)through a preSignUpHook. I believe this only works with accounts-password wont work when creating an account with an API such as accounts-google.
My idea around this is to use a Meteor.users.before.insert hook to grab the iron router query parameters and insert them into my referrerCode field in Meteor.users since I'm already using Meteor Collection Hooks for a couple of other things.
The issue is I havent been able to find a way to get the query parameters on the server, I was hoping to do something like this:
Meteor.users.before.insert(function(userId, doc) {
doc.referrerCode = Referrer._referrerCode; // Link 1
});
(Link 1)
But this will just come up as undefined.
If I'm at my register page and it has a query like this for example: example.com/register?r=12345 Then I run Router.current().params.query.r on the client it returns 12345. Basically I just need to have that saved to the referralCode field in Meteor.users when a new user creates an account, if a referral code exists in the register URL.
I'm a bit lost with this one. I thought about setting it as a Session variable and then getting that in the before.insert hook, but that again only works on the client side. I'm thinking a meteor method might be best for this, but I'm not exactly sure how I would structure it. Any help is greatly appreciated!
Put the referral token into profile
Use that in your hook
Below I've copied some code that I've used before. It is built around an Invitations collection that tracks who invited who:
client:
var profile = {};
... any other profile settings you've captured
if ( token ) profile.referralToken = token;
Accounts.createUser({ email: email, password: password, profile: profile }, function(err){ ...})
hook:
if ( options.profile.referralToken ){ // referral case
var invitation = Invitations.findOne({ token: options.profile.referralToken });
if ( invitation )
user.invitationId = invitation._id; // the invitation used
user.invitedBy = invitation.userId; // the referring user
}
delete options.profile.referralToken;
}
return user;

Adding a Dynamic Child to my Firebase Reference URL

I've been attempting to dynamically add a child to my firebase data reference URL, so far with no luck.
Assume I have the following data structure:
MyApp
|-beta_signups
|-users
|--fred
|----email "fred#test.com"
I would like to be able to add people who sign up, as a child based on their email address under the "signups" section. here is what I tried, but it didn't work.
var myDataRef = new Firebase("https://myapp.firebaseio.com/beta_signups/");
$('#submit').click(function() {
var email = $('#email').val();
myDataRef.child(email).push({email: email, beta_key: false});
$('#email').val('We got it.');
});
Any suggestions on how I can dynamically add the child?
You can't use an email address as the key for a child path because it contains invalid characters. See Creating References in the docs.
You are also creating a child based on the email address, and then creating a child of the email by using push. Probably, you should just get rid of the .child(email) bit and use push to create the records.
var user_id = myDataRef.push({email: email, beta_key: false}).name();
The first thing to ask is whether you actually want to store the users by email. Generally, an ID is going to be much more useful (they may change their email later, in which case you have to go re-key all their user data in the system).
If that's a requirement, then you're either going to have to hash them or sanitize them. For example:
// replace all forbidden characters with something that won't appear in the email address
var key = email.replace(/[.$\[\]\/#]/, ','/);
myDataRef.child(key).set({email: email, beta_key: false});

Resources