I'm using the meteor roles package for authrizations on my webpage. I have 2 roles defined, one is the student role and another is the administrator role. For all the new users registering on my webpage, I have assigned them the students role using the following helper:
Accounts.createUser({
email:email,
password:password,
profile:{ //profile includes fullname and surname
fullname:fullname,
surname:surname
}
},
function(err){
if(err){
FlashMessages.sendError("There was an error");
} else {
FlashMessages.sendSuccess("Account Created");
Roles.addUsersToRoles( Meteor.userId(), 'Students' );
Router.go("/dashboard");
}
});
After trying to add a new user, the user does gets added to the meteor users collection and the roles get assigned as intented. But the console does returns the following error:
insert failed: WriteError({"code":11000,"index":0,"errmsg":"E11000 duplicate key error index: meteor.roles.$name_1 dup key: { : \"Students\" }","op":{"name":"Students","_id":"w3vnwKrKJTNE8TpBS"}})
Any fixes to that as I cant understand the error as the users get the roles assigned and are getting inserted into the meteor collection.
Related
I have got problem accessing the user profile details of the users other then the current user.
The goal is to display a little footer under a each of the posts in the kind of blog entries list . Footer should consist of the post and author details (like date, username etc.).
Blog entry is identified by authors' _id but the point is that I can not access
Meteor.users.find({_id : authorId});
Resulting cursor seems to be the same as Meteor.user (not 'users') and consists of one only document, and is valid for the current user ID only. For others, like authors ID, I can only get an empty collection.
The question is, if is there any way, other then next Meteor.users subscription to get authors profile (like username profile.nick etc) ???
Update: You can Publish Composite package if you want to get blog entry and user details in a single subscription. See the following sample code and edit as per your collection schemas,
Meteor.publishComposite('blogEntries', function (blogEntryIds) {
return [{
find: function() {
return BlogEntries.find({ courseId: { $in: blogEntryIds }});
// you can also do -> return BlogEntries.find();
// or -> return BlogEntries.find({ courseId: blogEntryId });
},
children: [{
find: function(blogEntry) {
return Meteor.users.find({
id: blogEntry.authorId
}, {
fields: {
"profile": 1,
"emails": 1
}
});
}
}}
}]
});
End of update
You need to publish Meteor.users from the server to be able to use it on client. accounts package will publish current user, that's why you are only seeing current user's information.
In a file in server folder or in Meteor.isServer if block do something like this
//authorIds = ["authorId1", "authorId2];
Meteor.publish('authors', function (authorIds) {
return Meteor.users.find({ _id : { $in: authorIds }});
});
or
Meteor.publish('author', function (authorId) {
return Meteor.users.find({ _id : authorId });
});
Then on client side subscribe to this publication, in template's onCreated function, with something like this
Meteor.subscribe('author', authorId); //or Meteor.subscribe('author', authorIds);
or
template.subscribe('author', authorId); //or template.subscribe('author', authorIds);
If you want to show only username (or a few other fields), you can save them in post document along with authorId. For example:
post:{
...
authorId: someValue,
authorName: someValue
}
You can use them in your templates as a field of a post.
If you have too many fields which you do not want to embed in post document, (so you want to keep only authorId), you can use publish-composite when you make your posts publication. (See example 1)
You do not need to publish all your users and their profiles.
Meteor.users.findOne() gives me back my user document.
Meteor.users.findOne({_id: 'my ID'}) gives me back my user document.
Meteor.users.findOne({_id: 'another users's ID'}) gives me back UNDEFINED.
This is obviously restricted by security. But how can I access another users's account details e.g. _id, name, profile, etc?
You'll need to add a publisher for the user. Here's an example:
// The user fields we are willing to publish.
const USER_FIELDS = {
username: 1,
emails: 1,
};
Meteor.publish('singleUser', function (userId) {
// Make sure userId is a string.
check(userId, String);
// Publish a single user - make sure only allowed fields are sent.
return Meteor.users.find(userId, { fields: USER_FIELDS });
});
Then on the client you can subscribe like this:
Metor.subscribe('singleUser', userId);
or use a template subscription like this:
this.subscribe('singleUser', userId);
Security notes:
Always check the arguments to your publishers, or clients can do bad things like pass {} for userId. If you get an error, make sure you meteor add check.
Always use a fields option with the users collection. Otherwise you'll publish all of their secrets. See the "Published Secrets" section of common mistakes.
Run it on the server like so:
Server:
Meteor.publish("otherUsers", function (userID) {
return Meteor.users.findOne({_id: userID});
});
Client:
Meteor.subscribe("otherUsers", <userIdYouWantToGetDetailsFor>);
Then you can just do a Meteor.users.findOne on the client keep in mind you can only do it for your user and the userID that you passed in the meteor subscribe
I am trying to subscribe to profdle information of a different user than the logged in user, but I am facing issues as mentioned below
I am using angular-material and my code looks like below:
//publish user info upon following user
Meteor.publish("getUserInfo", function (userId) {
return (Meteor.users.find({_id: userId}, {fields: {profile: 1}}));
});
//subscribe
$scope.$meteorSubscribe("getUserInfo", askLikeController.$root.askLike[0].userId).then(function (subscriptionHandle) {
//Second element in the userProfile array will have the profile of required user
askLikeController.$root.usersProfile = $meteor.collection(Meteor.users, false);
});
Issues:
1. In the variable askLikeController.$root.usersProfile, I am getting both the loggedIn user and the desired userinfo having userId, I was expecting userinfo of only desired userId, why is this?
2. The subscription "getUserInfo" is not reactive, and even the subscription is lost after processing few blocks of code and then in the askLikeController.$root.usersProfile I am left with only user profile of logged in user, my guess is that my subscription is being replaced by inbuilt Meteor subscription for user.
How do I solve the issues?
Regards,
Chidan
First, make sure you have removed autopublish:
> meteor remove autopublish
To get reactivity in angular-meteor you need $meteor.autorun and $scope.getReactively. Here's an example:
// we need the requested id in a scope variable
// anytime the scope var changes, $scope.getReactively will
// ... react!
$scope.reqId = askLikeController.$root.askLike[0].userId;
$meteor.autorun($scope, function() {
$scope.$meteorSubscribe('getUserInfo', $scope.getReactively('reqId')));
}).then(function(){
askLikeController.$root.usersProfile = $meteor.collection(Meteor.users, false);
})
Getting only the user you selected: NOTICE- the logged in users is always published. So you need to specify which user you want to look at on the client side, just like you did on the publish method. So, in the subscribe method:
askLikeController.$root.usersProfile = $meteor.collection(function() {
return Meteor.Users.find({_id: $scope.getReactively('reqId')})
}, false);
At this point you might be better off changing it to an object rather than a collection:
askLikeController.$root.usersProfile = $scope.$meteorObject(Meteor.Users, {_id: $scope.getReactively('reqId')});
I am using Alanning Roles to maintain a set of groups/roles for the users of my application. When a user creates an "Application", I generate a new role for them as the app_name + UUID, then add that as a group with the roles of Admin to the user that created it. I can then use the combination of the generated group name plus either the Admin or Viewer roles to determine which Applications the user has rights to see and/or edit.
The issue that I am having is that I can't figure out a good way to get the publication to only publish the things the user should see. I know that, by default at least, publications are not "reactive" in the way the client is, and they they are only reactive for the cursors they return. But, in my code I create the group/role first, add it to the user, then save the "Application", which I thought would rerun my publication, but it did not:
Meteor.publish('myApplications', function(groups) {
if (this.userId) {
console.log('Running myApplications publication...');
console.log('Found roles for user ' + this.userId + ': ', Roles.getGroupsForUser(this.userId));
return Applications.find({group: {$in: Roles.getGroupsForUser(this.userId)}});
} else {
//console.log("Skipping null user");
return null;
}
});
But, contrary to what I thought would happen (the whole publication method would re-run), I am guessing what really happens is that only the Cursor is updates. So for my next attempt, I added the mrt:reactive-publications package and simply got a cursor to the Meteor.users collection for the user, thinking that would "trigger" the publication to re-run when the user gets updated with the new group/role, but that didn't work.
I have this finally working by simply passing in the groups for the user:
Meteor.publish('myApplications', function(groups) {
if (this.userId) {
if (!groups || groups.length === 0) {
groups = Roles.getGroupsForUser(this.userId);
}
console.log('Running myApplications publication...');
console.log('Found roles for user ' + this.userId + ': ', Roles.getGroupsForUser(this.userId));
return Applications.find({group: {$in: groups}});
} else {
//console.log("Skipping null user");
return null;
}
});
And then I just call the publication like Meteor.subscribe('myApplications', Roles.getGroupsForUser(Meteor.userId())) in my route's waitOn, but this would mean that any client could call the same publication and pass in any groups they like, and potentially see documents they were not intended to see. That seems like a pretty large security flaw.
Is there a better way to implement this such that the client would not be able to coax their way to seeing stuff not theirs? I think the only real way would be to gather the groups on the publication side, but then it breaks the reactivity.
After sifting through a bunch of docs and a few very helpful stack posts, this is the alternative I came up with. Works like a charm!
My objective was to publish 'guest' users' info to the group admins for approval/denial of enhanced permissions.
Meteor.publish('groupAdmin', function(groupId) {
// only publish guest users info to group admins
if(Roles.userIsInRole(this.userId, ['group-admin'], groupId)) {
// I can't explain it but it works!
var obj = {key: {$in: ['guest']}};
var query = {};
var key = ('roles.' + groupId);
query[key] = {$in: ['guest']};
return Meteor.users.find(query, {
fields: {
createdAt: 1,
profile: 1
}
});
} else {
this.stop();
return;
}
});
Reference: How to set mongo field from variable
& How do I use a variable as a field name in a Mongo query in Meteor?
This is probably more simple than I'm making it sound.
I'm allowing my users to create their myprofile when the signin. This is a document that is stored in MyProfile = new Meteor.Collection('myprofile');. The principle is exactly the same as LinkedIn...you login, and you fill out a profile form and any edits you make simply updates that one document.
Within the document there will be fields such as 'summary' 'newsInterest' and others, also 'owner' which is the users Id.
1) How can I insert a document into the MyProfile collection with the 'owner' field being the userId of the newly created user on StartUp?
This is so that the data of this document, the values of these fields will be passed onto the myprofile page. Initially the values returned will be blank but as the user types, upon keyup the myprofile document will be updated.
Users are created as follows on the client. This is fine for now.
2) In addition, please provide any links if people have created users on the server. I called a method to insert the following as an object into Meteor.users.insert(object);but this does not work.
Template.join.events({
'submit #join-form': function(e,t){
e.preventDefault();
Accounts.createUser({
username: t.find('#join-username').value,
password: t.find('#join-password').value,
email: t.find('#join-email').value,
profile:{
fullname: t.find('#join-fullname').value,
summary: [],
newsInterest: [],
otherstuff: []
}
});
Router.go('myprofile');
}
});
1) In order to solve issue one you have two options.
Instead of having a separate collection for profiles like you would in a normalized MySQL database for example. Add the users profile data within the profile object already attached to objects in the user collection. You can then pass in the values you want in the options parameter of the Accounts.createUser function
Template.join.events({
"submit #join-form": function (event) {
event.preventDefault();
var firstName = $('input#firstName').val(),
lastName = $('input#lastName').val(),
username = firstName + '.' + lastName,
email = $('input#email').val(),
password = $('input#password').val(),
profile = {
name: firstName + ' ' + lastName
};
Accounts.createUser({
email: email,
username: username,
password: password,
profile: profile
}, function(error) {
if (error) {
alert(error);
} else {
Router.go('myprofile');
}
});
}
});
This is an example using jQuery to get the values but your t.find should work equally fine.
If you really do want to use a separate collection then I recommend using the following code inside the onCreateUser function (server side) instead:
Accounts.onCreateUser(function(options, user) {
user._id = Meteor.users._makeNewID();
profile = options.profile;
profile.userId = user._id;
MyProfile.insert(profile);
return user;
});
When you want to update or add additional data into the profile field for a user you can use the following:
var newProfile = {
summary: 'This summary',
newsInterest: 'This newsInterest',
otherstuff: 'Stuff'
};
Meteor.users.update(Meteor.userId, {$set: {profile: newProfile}});
Or if you went for the separate collection option the following:
var newProfile = MyProfile.findOne(Meteor.userId);
newProfile.summary = 'This summary';
newProfile.newsInterest = 'This newsInterest';
newProfile.otherstuff = 'Stuff';
MyProfile.update(Meteor.userId, newProfile);
Haven't tested this so let my know if I have any syntax / typo errors and I'll update.
Q2: you have to send email, password and profile object to server and use the same Accounts.createUser. Everything works fine.
Question 1 is easily resolved using the Accounts.onCreateUser callback on the server - it provides the user object to the callback, so you can just take the userId from this and insert a new "myProfile" with that owner.
As Will Parker points out below, the user document won't actually have an _id at this point though, so you need to create one and add it to the user object that the callback returns. You can do this with:
user._id = Meteor.users._makeNewID();