How to get Iron-router query parameters in server hook - meteor

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;

Related

How to prevent Meteor's accounts-base from auto-publishing the email of the current user?

According to Meteor's documentation on the Accounts package:
By default, the current user’s username, emails and profile are published to the client.
Is it possible to prevent Meteor from auto-publishing these fields? I know it's just for the user that is logged in, but that user could take a walk or be online somewhere public.
This structure of the code seems to be defined in accounts_server.js (search for autopublish and email - lines 37 and 696).
The most straightforward way to do this is going to be to modify the value of Accounts._defaultPublishFields.projection and remove the emails key. An easy way to do this while keeping the other values is to use a combination of rest and spread like so:
import { Accounts } from 'meteor/accounts-base';
const { emails, ...fields } = Accounts._defaultPublishFields.projection;
Accounts._defaultPublishFields.projection = { ...fields };
Just make sure this runs on the server and you should be good to go.

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

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.

Cannot Create User Profile in Firebase

First off, thanks for taking the time; this is my first question posted!
I'm in the middle of coding my first mobile application in React Native: a stats managing App for my fraternity. Right now, I would like an admin user to be able to add other users to the app and set their initial profile information (first name, last name, position, dues paid etc).
Once they submit the details, the code below tells firebase to create a new user using the provided email and password, then adds their other profile information under the user parent in the Realtime Database.
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(() => {
console.log(key);
firebase.database().ref('/ChapterName/users/' + key)
.set({ firstName, lastName, rank, position, goodStanding, dues, communityService, chapters, mixers, brotherhoods });
})
The JSON tree created can be seen here: Firebase Tree
My problem is that when the new user logs in, I cannot find a way to access their profile information...ideally, I would set the key of each child under 'user' to the user's email: that way they could login and I could match their email to the key of their profile info and fetch it that way. Unfortunately, keys must be UTF-8 encoded and don't allow for me to include '# or .'
Does anyone know a way around this? I feel as if the firebase Admin API could be a solution but I can't seem to wrap my head around it.
Inside react-native with firebase, if they user already login and you already integrate firebase with redux, you can access the current user information inside redux using this line
state.firebase.profile
as per my suggestion, I think it's best for to integrate
react-native app + react-native-firebase + react-redux-firebase
thus, you dont have to use web firebase function as it's already exist in props.
if you need to update the profile after user already created you can use something like this
this.props.firebase.auth().createUserAndRetrieveDataWithEmailAndPassword(email, password).then((user) => {
this.props.firebase.updateProfile(
{
name: 'Hazim',
age: 24,
mode: 'Rampage'
}
)
}).catch((error) => {
//Error value
})
and inside any component, you can get current user info with connect function like this.
class Home extends Component {
}
export default compose(
firebaseConnect(),
connect( state => {
profile: state.firebase.profile
})
)(Home)

Meteor Publish/Subscribe passing object with string parameter issue

I am trying to pass a object { key:value} and send it to meteor publish so i can query to database.
My Mongo db database has (relevant datas only) for products:
products : {
categs:['Ladies Top','Gents'],
name : Apple
}
In meteor Publish i have the following:
Meteor.publish('product', (query) =>{
return Clothings.find(query);
})
In client i use the following to subscribe:
let query = {categs:'/ladies top/i'}; // please notice the case is lower
let subscribe = Meteor.subscribe('product',query);
if (subscribe.ready()){
clothings = Products.find(query).fetch().reverse();
let count = Products.find(query).fetch().reverse().length; // just for test
}
The issue is, when i send the query from client to server, it is automatically encoded eg:
{categs:'/ladies%top/i'}
This query doesnot seem to work at all. There are like total of more than 20,000 products and fetching all is not an option. So i am trying to fetch based on the category (roughly around 100 products each).
I am new to ,meteor and mongo db and was trying to follow existing code, however this doesnot seem to be correct. Is there a better way to improve the code and achieve the same ?
Any suggestion or idea is highly appreciated.
I did go through meteor docs but they dont seem to have examples for my scenario so i hope someone out there can help me :) Cheers !
Firstly, you are trying to send a regex as a parameter. That's why it's being encoded. Meteor doesn't know how to pass functions or regexes as parameters afaict.
For this specific publication, I recommend sending over the string you want to search for and building the regex on the server:
client:
let categorySearch = 'ladies top';
let obj = { categorySearch }; // and any other things you want to query on.
Meteor.subscribe('productCategory',obj);
server:
Meteor.publish('productCategory',function(obj){
check(obj,Object);
let query = {};
if (obj.categorySearch) query.category = { $regex: `/${obj.categorySearch}/i` };
// add any other search parameters to the query object here
return Products.find(query);
});
Secondly, sending an entire query objet to a publication (or Method) is not at all secure since an attacker can then send any query. Perhaps it doesn't matter with your Products collection.

Iron router : route by session value

I having some thoughts about what is the most right way to do the develop platform interface for project which grab it's all data from external API.
This platform has few account types, which i currently store in session after the user logging in.
for the example i will use 3 account types :
Admins
Sellers
Buyers
What I need :
each account type will use same routing path's but - will rendered different templates. for ex.
route '/' for admin user will render 'admin-dash' template, and so for 'buyer-dash' and 'seller_dash'
I am storing the account type on session,
Session('userRole', 'admin'/'seller'/'publisher');
and if the user is a seller or publisher I am adding an 'account-id' value to the session also, so I will be make use of when i will need to pull data for the current account.
The question is, if can i declare different routes for same path, by the current session data.
So what do you suggest ?
Thanks!
Router.route('/', function () {
var userType = Session.get('userRole');
if(userType == 'admin')
{
this.render('admin-dash');
}
else if(userType == 'buyer'){
this.render('buyer-dash');
}
});
this is how I think you can achieve your goal.

Resources