Meteor: insert document upon newly created user - 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();

Related

How to add custom fields to Meteor.users collection?

Sorry for my english. I use the package useraccounts:bootstrap for login, registration and so on. How can I add arbitrary data to Meteor.users collection after registration. For example, I want, that users after registration had a field 'status' with a value of 'false' or the field 'time' with time of registration. Thank you.
If the user needs to supply the data, you will need to customize the UI and add the desired fields.
On the server, you can attach an onCreateUser() callback to set the data when a new user is created.
import _ from 'lodash';
Accounts.onCreateUser((options, user) => {
// add your extra fields here; don't forget to validate the options, if needed
_.extend(user, {
status: false,
createdAt: new Date()
});
return user;
});
the options argument contains the data from the client side.
useraccounts:bootstrap provides you a way to customize your registration panel templates by adding visible, explicit and editable fields into the registration form, as explained in useraccounts/core's GitHub documentation(look for AccountTemplates.addFields method).
However, useraccounts:bootstrap is dependent on accounts-password, so you can use its Accounts.createUser method, simply by passing additional fields in the object passed into Accounts.createUser method. Your createUser method would be like:
Accounts.createUser({
username:'newuser',
password:'pass1234',
profile:{ //no sensitive data here, this can be modified by the user
},
registrationTime: new Date, //date & time of registration
status: false
});
This problem was discussed on Meteor forums:forums.meteor.com.
A more elegant way of solving your problem is calling a server-side function Accounts.onCreateUser every time a user account is created. This function would assign the registrationTime and status to the newly created account. Check this in Meteor's docs: Accounts.onCreateUser docs.meteor.com
Here is how I'm doing it; matches the meteor docs style and doesn't require lodash:
import { Accounts } from 'meteor/accounts-base';
Accounts.onCreateUser((options, user) => {
const userToCreate = Object.assign({
status: false,
createdAt: new Date(),
}, user);
if (options.profile) userToCreate.profile = options.profile;
return userToCreate;
});

Meteor: how to access another user's details?

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

Reactive subscription on user collection

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')});

Accounts.onCreateUser adding extra attributes while creating new users, good practices?

I'm creating new user with Accounts.createUser() and it works normally if you are not doing anything fancy. But I want to add some other fields to new user that are not listed on documentation. Here is my code:
var options = {
username: "funnyUserNameHere",
email: "username#liamg.com",
password: "drowssap",
profile: {
name: "Real Name"
},
secretAttribute: "secretString"
};
var userId = Accounts.createUser(options);
In this example I have added secretAttribute to my options object. Because this is not documented it's just fair it's not adding my attribute under user object.
So I googled and figured out that something like this might work:
Accounts.onCreateUser(function(options, user) {
if (options.secretAttribute)
user.secretAttribute = options.secretAttribute;
return user;
});
And yes! This works, but there is always BUTT.. *BUT.. After this one it's not saving profile anymore under the user object. However this makes it work:
Accounts.onCreateUser(function(options, user) {
if (options.secretAttribute)
user.secretAttribute = options.secretAttribute;
if (options.profile)
user.profile = options.profile;
return user;
});
So what I want from you guys?
I want to know why onCreateUser loses profile (before the fix above) in my case?
Is my approach good practice?
Is there better solution adding extra attributes for user object while creating them?
ps: I thinks it's obvious why I don't want to save all extra fields under profile ;)
Well it wasn't so hard.. Here it stands in documentation: "The default create user function simply copies options.profile into the new user document. Calling onCreateUser overrides the default hook." - Accounts.onCreateUser
Try this:
Accounts.onCreateUser((options, user) => (Object.assign({}, user, options)));
The best thing I found to this issue is:
Accounts.onCreateUser(function(options, user) {
// Use provided profile in options, or create an empty object
user.profile = options.profile || {};
// Assigns first and last names to the newly created user object
user.profile.firstName = options.firstName;
user.profile.lastName = options.lastName;
// Returns the user object
return user;`enter code here`
});
https://medium.com/all-about-meteorjs/extending-meteor-users-300a6cb8e17f

Publication of items where User is in group (Alanning Roles and Publications)

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?

Resources