How can I create an entity specific to a user? - node.js

I'm creating an action for Google Assistant with Dialogflow and actions-on-google-nodejs that accesses the GitKraken Glo API to add cards to people's boards. I'm authenticating my users with Account Linking. I want my users to be able to say things like Add a card to [board name] or Add a card. If a board name isn't given I want the action to prompt the user for it. How can I create a session entity that get's all the board names for the logged in user?
Sorry if this doesn't make much sense, I'm pretty new to Actions on
Google and Dialogflow. Feel free to ask questions for clarity.

There are a few things you'll need to do first to use a Session Entity:
The Entity Type needs to already exist. Session Entities update existing ones. The easiest way to do this is to create the Entity you want in the Dialogflow UI. It doesn't need to have any Entities in it, but having one as a default can be useful.
You need a Service Account for your project in Google Cloud that will do the update, and a secret key for this account.
Your life will be a lot easier if you use a library, such as the dialogflow-nodejs library.
In general, your code needs to do the following, typically when the user first starts the session (ie - in your Welcome Intent Handler):
Get the list of boards
Update the Session Entity Type, creating an Entity for each board. Doing this update involves:
Issuing a patch against the projects.agent.sessions.entityTypes method with a SessionEntityType for the Entity Type you're overriding.
The SessionEntityType will contain an array of Entities with the canonical name (likely the board name, or some unique identifier) and any aliases for it (at least the board name, possibly anything else, possibly including aliases like "the first one" or "the most recent one").
The README for the library includes links to sample code about how to do this using the nodejs library. Code that I have that does this work has a function like this:
function setSessionEntity( env, entityType ){
const config = envToConfig( env );
const client = new dialogflow.SessionEntityTypesClient( config );
let parent = env.dialogflow.parent;
if( entityType.displayName && !entityType.name ){
entityType.name = `${parent}/entityTypes/${entityType.displayName}`;
}
if( !entityType.entityOverrideMode ){
entityType.entityOverrideMode = 'ENTITY_OVERRIDE_MODE_OVERRIDE';
}
const request = {
parent: parent,
sessionEntityType: entityType
};
return client.createSessionEntityType( request );
}

conv.user.email
You can use conv.user object :
const Users = {};
app.intent('Get Signin', (conv, params, signin) => {
if (signin.status === 'OK') {
const email = conv.user.email
Users[email] = { };
conv.ask(`I got your email as ${email}. 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 next?`)
}
})
app.intent('actions.intent.TEXT', (conv, input) => {
if (signin.status === 'OK') {
Users[conv.user.email] = {
lastinput: input
};
}
});
conv.id
Also with conv id is unique id for the current conversation.
// Create an app instance
const app = dialogflow()
// Register handlers for Dialogflow intents
const Users = {};
app.intent('Default Welcome Intent', conv => {
Users[conv.id] = {
conversationId: conv.id,
name: '1234'
};
})
app.intent('actions.intent.TEXT', (conv, input) => {
Users[conv.id] = {
lastinput: input
};
});
app.intent('Goodbye', conv => {
delete Users[conv.id];
})

Related

Get friend suggestions from users contacts based on whether the contact number is associated with any given account

I am trying to build an expo app with firebase auth and an express backend.
I want to build a friend suggestion system from contacts like in snapchat and instagram and other social media.
I am requesting the users for contacts permission and then processing that array just to get the phone numbers and match the format needed by firebase then I match the numbers with
However, if the contact list is too large then the request takes very long to complete (about 30s).
I understand that all contacts must be checked but how do big social medias reduce the time taken to check contacts by so much.
Server code:
//firebase admin function
const phoneNumberExists = async (phoneNumber: string) =>
await getAuth(admin).getUserByPhoneNumber(phoneNumber);
const getUsersByContacts = async (
//array of contact numbers as a string because some might include the country code
contacts: string[],
//current user's uid
currUid: string
) => {
const users = [];
for await (let contact of contacts) {
try {
//formats string according to indian phone numbers and adds country code
contact = contact.trim().replace(/ /g, "");
if (contact.length !== 13 || !contact.startsWith("+")) {
if (contact.length === 10) contact = "+91" + contact;
else if (contact.length === 12) contact = "+" + contact;
}
const { uid } = await phoneNumberExists(contact);
//checks whether mobile number belongs to current user
if (currUid !== uid) {
// gets properties of the user based on uid
const actualUser = await getUserData(uid);
if (actualUser) users.push(actualUser);
}
} catch (error) {
// this error code means no user found with the phone number so I continue
if (error.code === "auth/user-not-found") continue;
}
}
return users;
};
Every contact has to be checked and since it's the firebase function which is causing the delay I was wondering if there was a way to this with node js duplex stream. I haven't worked with streams in the past but from what I read about streams they fit the need. However all examples of streams are related to file streaming.
Is it possible to send contacts as a stream and get suggestions as a stream.
Should I convert the contacts array into a file first and then stream it?

NestJS: Authorization based on instances property best practice

I need authorization in NestJS based on instances property.
Ex. user can update only his own articles.
Is there another way despite defining the logic in each services? ( I know it is possible using CASL )
Not having a global guard will facility errors, and everything is authorized by default unless add logic on the service.
What about creating a function that takes the request, the model and the name of the proprety and use it wherever you want ?
const verifAuthorization = (
req: Request,
propName: string,
model: any
): void => {
const sender: User = req.user;
if (!sender) {
throw new BadRequestException("there is no user in the token");
}
if (!sender._id.equals(model[propName])) {
throw new UnauthorizedException();
}
};
Yes ! you will call it in every service you want to check the authorization in, but it will save you a lot of time and code

How can I edit the backend of firebase cloud function extension?

I am coding a project similar to patreon.com and my goal is to let multiple members create their own subscription plans and sell it.
I came across a firebase extension for stripe payments - https://firebase.google.com/products/extensions/stripe-firestore-stripe-payments
The problem with this extension is that I can only create a 1 premium membership that adds custom claims to the auth object and I can validate it like this:
export default async function isUserPremium(): Promise<boolean> {
await auth.currentUser?.getIdToken(true);
const decodedToken = await auth.currentUser?.getIdTokenResult();
return decodedToken?.claims?.stripeRole ? true : false;
}
That means that even if I have 100 different subscriptions, I can only attach single boolean value, which is useless.
I went to the source code and I found this snippet:
// Update their custom claims
if (role) {
try {
// Get existing claims for the user
const { customClaims } = await admin.auth().getUser(uid);
// Set new role in custom claims as long as the subs status allows
if (['trialing', 'active'].includes(subscription.status)) {
logs.userCustomClaimSet(uid, 'stripeRole', role);
await admin
.auth()
.setCustomUserClaims(uid, { ...customClaims, stripeRole: role });
} else {
logs.userCustomClaimSet(uid, 'stripeRole', 'null');
await admin
.auth()
.setCustomUserClaims(uid, { ...customClaims, stripeRole: null });
}
} catch (error) {
// User has been deleted, simply return.
return;
}
}
I don't fully understand this code, but I think this is where the boolean value is assigned.
Would it be possible to somehow edit this source code, so that instead of boolean value, I could store subscription plan ids in Array , so that in the front end I could validate and allow customer to access users content only if he has active plan in that array
?

actions on google user.storage clears out with every post call from the user-at the same coversation. how do i save the data of the conversation?

I am trying to save the data through the conversation with user.storage, I am accessing the user.storage like this:
app.post('/', express.json(), (req, res) => {
const agent = new WebhookClient({ request: req, response: res })
let personalD=new personalDetails(agent)
function personal_details(){
personalD.foo()
}
let intentMap = new Map()
intentMap.set('inform.PersonalDetails',personal_details)
agent.handleRequest(intentMap)
}
//that's the personalDetails class:
class PersonalDetails{
constructor(agent){
this.agent=agent;
this.conv=this.agent.conv();
}
foo() {
this.conv.user.storage.name=this.agent.parameters.name;
this.conv.user.storage.age=this.agent.parameters.age;
this.conv.user.storage.gender=this.agent.parameters.gender;
const gotname = this.conv.user.storage.name==''?0:1
const gotage = this.conv.user.storage.age==''?0:1
const gotgender =this.conv.user.storage.gender==''?0:1
const name=this.conv.user.storage.name;
const gender=this.conv.user.storage.gender;
if (gotname && !gotage&&!gotgender)
this.agent.add(`Ok, ${name}, How old are you? and what is you'r gender?`)
else if (gotname && gotage&&!gotgender)
this.agent.add(`Ok, ${name}, What gender you belong to`)
else if(gotname && !gotage&&gotgender)
this.agent.add(`Ok, ${name}, How old are you?`)
else if (!gotname && gotage&&gotgender)
this.agent.add(`What's your name please?`)
else if (!gotname && !gotage&&gotgender)
this.agent.add(`Well dear ${gender}, What is your name and how old are you`)
else if(!gotname && gotage&&!gotgender)
this.agent.add('Let me know what is your name and what is your gender')
else if (!gotname && !gotage&&!gotgender)
this.agent.add(`I want to get to know you before we begin. what is you'r name?`)
}
}
module.exports=PersonalDetails;
Dialogflow wants from the user three entites: name, age and gender. When the user does not provide all of them the code does some logic to see what is missing.
The problem is that at first I enter lets say name and age, and then it asks the user about the gender, when the user enteres the gender it's already forgetting the name and the age...
please help
In your dialogflow fulfillment code, you are initializing parameters in user.storage on every request from the intent, instead of only when you have a value from the user. This code is your problem:
this.conv.user.storage.name=this.agent.parameters.name;
this.conv.user.storage.age=this.agent.parameters.age;
this.conv.user.storage.gender=this.agent.parameters.gender;
You have to set the user.storage only once and then, you can use it anywhere directly.
app.intent('GetUserName', (conv, {name}) => {
conv.user.storage.name= name;
conv.ask(`Hi, ${conv.user.storage.name}!.
Please tell me how can I help you? `);
});
app.intent('AboutSC', (conv) => {
conv.ask(`well ${conv.user.storage.name}. What more would you like to know? `);
});
You can use the user.storage parameters directly. But using variable/constants that are initialized on every request will change the value everytime & will not help.

Access Facebook Messenger User Profile API in DialogFlow

I'm building a cross-platform chatbot in Google's DialogFlow. I'd like to access the Facebook User Profile API to learn the user's first name.
I'm struggling to find advice on how (or if) I can make this happen.
https://developers.facebook.com/docs/messenger-platform/identity/user-profile/
Has anybody here achieved this?
I did that for one of my bots yesterday, you need 2 things, first the Page Token and second is the psid which is Page scope user ID.
On dialogflow, you will receive the request block with psid as sender id. You can find it at:
agent.originalRequest.payload.data.sender.id
This psid needs to be passed to api get request at
/$psid?fields=first_name with your page Token as accessToken to get the first name in response.
You need to make a call to Facebook Graph API in order to get user's profile.
Facebook offers some SDKs for this, but their official JavaScript SDK is more intended to be on a web client, not on a server. They mention some 3rd party Node.js libraries on that link. I'm particularly using fbgraph (at the time of writing, it's the only one that seems to be "kind of" maintained).
So, you need a Page Token to make the calls. While developing, you can get one from here:
https://developers.facebook.com/apps/<your app id>/messenger/settings/
Here's some example code:
const { promisify } = require('util');
let graph = require('fbgraph'); // facebook graph library
const fbGraph = {
get: promisify(graph.get)
}
graph.setAccessToken(FACEBOOK_PAGE_TOKEN); // <--- your facebook page token
graph.setVersion("3.2");
// gets profile from facebook
// user must have initiated contact for sender id to be available
// returns: facebook profile object, if any
async function getFacebookProfile(agent) {
let ctx = agent.context.get('generic');
let fbSenderID = ctx ? ctx.parameters.facebook_sender_id : undefined;
let payload;
console.log('FACEBOOK SENDER ID: ' + fbSenderID);
if ( fbSenderID ) {
try { payload = await fbGraph.get(fbSenderID) }
catch (err) { console.warn( err ) }
}
return payload;
}
Notice you don't always have access to the sender id, and in case you do, you don't always have access to the profile. For some fields like email, you need to request special permissions. Regular fields like name and profile picture are usually available if the user is the one who initiates the conversation. More info here.
Hope it helps.
Edit
Promise instead of async:
function getFacebookProfile(agent) {
return new Promise( (resolve, reject) => {
let ctx = agent.context.get('generic');
let fbSenderID = ctx ? ctx.parameters.facebook_sender_id : undefined;
console.log('FACEBOOK SENDER ID: ' + fbSenderID);
fbGraph.get( fbSenderID )
.then( payload => {
console.log('all fine: ' + payload);
resolve( payload );
})
.catch( err => {
console.warn( err );
reject( err );
});
});
}

Resources