How to define routes that include the current user with Iron Router? - meteor

I'm fairly new to Meteor and Iron Router and I'm trying to create a route that looks like this '/users/hippomoe/bookmarks/Kj6ecNk7DeW7PmmGA' where hippomoe would be the current logged in user and Kj6ecNk7DeW7PmmGA would be the id of a bookmark. Some of the things that I've tried were to add a handlebars helper that would provide the username
bookmark: function() {
return {
_id: this._id,
user: Meteor.user().username
};
}
and I also tried defining user: Meteor.user().username in the data context within the RouteController definition. In both instances I get the following error: "You called Route.prototype.resolve with a missing parameter. "user" not found in params"
I've tried finding an example that illustrates this type of route (which I assume would be common). The other questions that I have seen on StackOverflow related to this were about getting the user associated with a particular document and not the current user that is logged in.
It seems that I missing something simple. Can someone provide/point me to a simple example of how to achieve this?

I'm having difficulty understanding why you would need such a route. If you already know the name of the logged in user, why would you need to have it once more in the route? Or is this meant to be server-side? In that case, please show your routes.
Otherwise, does this not work:
Router.map(function () {
this.route('postShow', {
path: '/users/:ignoreme/bookmarks/_id',
data: function () {
var params = this.params;
var user = Meteor.user().username;
if (user == params.ignoreme) {
...
} else {
return "something went wrong";
}
}
});
});
This is just to allow the url pattern you want, but again, I don't see why you wouldn't just use a URL /user/bookmarks or similar and then change the logic to depend on Meteor.user().

The handlebars helper that I had written in my question
bookmark: function() {
return {
_id: this._id,
user: Meteor.user().username
};
}
would have worked if I would have specified the pathFor syntax correctly. My pathFor should have looked like this
{{pathFor 'saveBookmark' bookmark}}
I had inadvertently left off bookmark!

I think you are missing a ':' before _id, try this:
path: '/users/:ignoreme/bookmarks/:_id',

Related

Meteor Routes & Sessions -> How to coordinate

Meteor Question: what is the best way to implement real sessions?
I have a normal login page with statistics. No issues there.
Now, I want people to be able to check-in with specific urls; assume they later
hit a url like http://localhost:3000/checkin/area1
How can I coordinate that with their login?
I have a route for the checkin:
Router.route('/checkin/:_id', function () {
var req = this.request;
var res = this.response;
this.userId = 'steve'; //TODO: Need way to pull userId
if (!this.userId) {
res.end('Please login first');
} else {
//Verify correct area
//Verify that haven't check before
var lastCheckin = checkins.find({ user: this.userId, visited: this.params._id });
if (lastCheckin.count() == 0) {
//we haven't checkedin yet
checkins.insert({ user: this.userId, visited: this.params._id, createdAt: new Date() })
res.end('Checkin '+this.userId+' for '+this.params._id);
} else {
console.log('already checked in '+lastCheckin.createdAt);
res.end(this.userId+' already visited '+this.params._id);
}
}
}, {where: 'server'});
Things I've tried:
Persistent session: But that doesn't work because request is not coming from the main page, so no session variable to pull.
Pull cookie in Request (since Persistent session seems to have 1). The original login doesn't appear to have the request object, so I don't know where to get that.
Others (diff situations) have shown a Meteor.user() inside a route, but the software complains that it can only be used inside a method. What can be used inside a route?
What else can I try?
Thanks for you help.
Ok, I am not sure if this will help exactly but.
If you are on /myPage, then you have an event that runs:
Router.go('/yourPage');
Session.set('yourVariable', "yourValue");
You will be able to access the session variable (yourVariable) in /yourPage (and yourPage's code).

Is there a way to update the URL in Flow Router without a refresh/redirect?

Is there a way to update a part of the URL reactively without using FlowRouter.go() while using React and react-layout?
I want to change the value in the document that is used to get the document from the DB. For example, if I have a route like ~/users/:username and update the username field in the document, I then have to user FlowRouter.go('profile', {data}) to direct the user to that new URL. The "old" route is gone.
Below is the working version I have, but there are two issues:
I have to use FlowRouter.go(), which is actually a full page refresh (and going back would be a 404).
I still get errors in the console because for a brief moment the reactive data for the component is actually wrong.
Relevant parts of the component are like this:
...
mixins: [ReactMeteorData],
getMeteorData() {
let data = {};
let users = Meteor.subscribe('user', {this.props.username});
if (user.ready())
data.user = user;
return data;
}
...
updateName(username) {
Users.update({_id:this.data.user._id}, {$set:{username}}, null, (e,r) => {
if (!e)
FlowRouter.go('profile', {username});
});
},
...
The route is like this:
FlowRouter.route('/users/:username', {
name: 'profile',
action(params) {
ReactLayout.render(Main, {content: <UserProfile {...params} />});
}
});
The errors I get in the console are:
Exception from Tracker recompute function:
and
TypeError: Cannot read property '_id' of undefined

Meteor: How to assign different roles to users during sign up process

I am using the meteor package ian:accounts-ui-bootstrap-3 for accounts and alanning:roles for assigning roles.
On the sign up form I have two options one for Doctor and one for Advisor. I want to assign the selected option as a role to that user. Can someone let me know how to do this?
I have just started learning meteor and don't know much about its flow. I can assign roles to a user if I create the user manually like this:
var adminUser = Meteor.users.findOne({roles:{$in:["admin"]}});
if(!adminUser){
adminUser = Accounts.createUser({
email: "mohsin.rafi#mail.com",
password: "admin",
profile: { name: "admin" }
});
Roles.addUsersToRoles(adminUser, [ROLES.Admin]);
}
But I want to assign a roll automatically as a user signs up and select one of the options and that option should be assigned as his role.
You shouldn't need a hack for this. Instead of using Accounts.onCreateUser you can do it with the following hook on the Meteor.users collection. Something along the lines of the following should work:
Meteor.users.after.insert(function (userId, doc) {
if (doc.profile.type === "doctor") {
Roles.addUsersToRoles(doc._id, [ROLES.Doctor])
} else if (doc.profile.type === "advisor") {
Roles.addUsersToRoles(doc._id, [ROLES.Advisor])
}
});
To get around having to check on login every time it's possible to directly set the roles on the user object instead of using the Roles API.
A hack? Yep, you probably need to make sure the roles have already been added to roles... not sure if there's anything else yet.
if(Meteor.isServer){
Accounts.onCreateUser(function(options, user){
if(options.roles){
_.set(user, 'roles.__global_roles__', ['coach', options.roles]);
}
return user;
});
}
Note: _.set is a lodash method not in underscorejs.
There's no pretty solution because:
There's no server side meteor callback post complete account creation.
In onCreateUser the user hasn't been added to the collection.
Accounts.createUser's callback is currently only for use on the client. A method could then be used from that callback but it would be insecure to rely on it.
The roles package seems to grab the user from the collection and in onCreateUser it's not there yet.
you can use the Accounts.onCreateUser hook to manage that.
Please keep in mind the code below is fairly insecure and you would probably want to do more checking beforehand, otherwise anyone can assign themselves admin. (from docs):
options may come from an untrusted client so make sure to validate any values you read from it.
Accounts.onCreateUser(function (options, user) {
user.profile = options.profile || {};
if (_.has(options, 'role')) {
Roles.addUserToRoles(user._id, options.role);
}
return user;
});
Thanks for your response. I tried but it doesn't work for me.
I used Accounts.onLogin hook to to manage this. Below code works for me:
Accounts.onLogin(function (info) {
var user = info.user;
if(user.profile.type === "doctor"){
Roles.addUsersToRoles(user, [ROLES.Doctor])
}
else
if(user.profile.type === "advisor"){
Roles.addUsersToRoles(user, [ROLES.Advisor])
}
return user;
});

Individual subscriptions at template-level, how bad am I abusing template-level subscriptions?

I've gotten lazy and added the following helper:
// Given a userId, show the username
Handlebars.registerHelper('username', function(userId) {
// This seems extremely wasteful
Template.instance().subscribe('user', userId);
var user = Meteor.users.findOne({ _id: userId });
if (user) {
return user.username;
}
return "";
});
Yay for template-level subscriptions, works great for my prototype! I couldn't find anybody denouncing the idea, but maybe that's because it's so stupid that nobody would consider doing it. Do you have experience with this pattern, and can you recommend it?
I'm worried about the amount of subscriptions specifically, and about the extensive re-rendering they might cause.
From my point of view, this isnt the smartest thing to do. Why reinvent the wheel when you
already have this kind of helper defined.
{{currentUser}}
http://docs.meteor.com/#/full/template_currentuser
Need username ... {{currentUser.username}}
The idea of the general helper probably isn't that bad, but I would separate the subscription and the name retrieval to have the subscription run only once:
Handlebars.registerHelper('userSubscribe', function(userIds) { // make userIds an array
Template.instance().subscribe('users', userIds); // change your publishing function to take the array
});
Handlebars.registerHelper('userName', function(userId) {
var user = Meteor.users.findOne({ _id: userId });
if (user) {
return user.username;
}
return "";
});
Each template is then responsible for sending the list of users it actually wants to subscribe to. Let's assume you have a posts collection that has, for simplicity, the following format:
{author: user_id, commenter: user_id, text: String}
Then you should be able to use it like so:
// JS
Template.myTemplate.helpers({
getMyUsers: function() { return [this.author, this.commenter]; }
});
// HTML
<template name='myTemplate'>
{{userSubscribe getMyUsers}}
The author is {{userName author}} and the commenter is {{userName commenter}}
</template>
It's probably still not ideal, but it should re-render only once rather than for each user whose name you want to look up when the data reaches the client.

How do I use ObjectID from a mongoimport?

I use a mongoimport to import a bunch of large csv files into a meteor collection, however when they do the insertions the _id values are ObjectID, whereas meteor uses string ids. There's a small blurb on ObjectIDs in the meteor docs here but I don't really understand what I am supposed to do. For example, using Iron Router I have a single route like so
this.route('profileView', {
path: '/profiles/:_id',
notFoundTemplate: 'notFound',
fastRender: true,
waitOn: function() {
return [Meteor.subscribe('singleProfile', this.params._id, Meteor.userId())];
},
data: function() {
Session.set('currentProfileId', this.params._id);
return Profiles.findOne({
_id: this.params._id
}, {
fields: {
submitted: 0
}
});
}
but the url of the route is of type object and looks like http://localhost:3000/profiles/ObjectID(%22530845da3621faf06fcb0802%22). It also doesn't return anything and the page renders blank. Here's the publication.
Meteor.publish('singleProfile', function(id, userId) {
return Profiles.find({
_id: id,
userId: userId,
forDel: {
$ne: true
}
});
});
I guess my question is, how am I supposed to use ObjectIDs so that the routes use just the string portion of the ObjectID, and how do I return the data properly?
Update: I've managed to get the ObjectID out of the url by changing the link to the view from Details to Details so the url is now http://localhost:3000/profiles/530845da3621faf06fcb0802. Unfortunately, the page still renders blank, and I am not sure if that's because of the way I am subscribing, publishing, or finding the collection item.
Summing up the comment thread as an answer:
The string part of the ObjectID can be obtained by simply calling ._str on the id as
id._str
You can also craft an ObjectID from a hex string by using
new Meteor.Colletion.ObjectID(hexstring)
So when you access your route using Details you can craft your find like:
Profiles.findOne({
_id: new Meteor.Collection.ObjectID(this.params._id)
});
Generally speaking, when working with ObjectID's, you will find yourself needing some antipatterns to convert from string to objectId or vice versa, so a utility like the following will come in handy:
IDAsString = this._id._str ? this._id._str : this._id
and
IDAsObjectId = this._id._str ? this._id : new Meteor.Collection.ObjectID(this._id)
Please also take a look at github.com/meteor/meteor/issues/1834 and groups.google.com/forum/#!topic/meteor-talk/f-ljBdZOwPk for pointers and issues around using ObjectID's.

Resources