Meteor - Validating new user document serverside - meteor

I'm having trouble with this seemingly trivial stuff, harrrr!
I have this user document:
userData = {
account: {
type: 'free'
},
profile: {
name: 'Artem',
},
username: 'aaa#gmail.com',
password: '123'
};
Which I'm sending client-side: Accounts.createUser(userData);
Then server side I want to check if account type equals 'free'. If it doesn't - I want to abort new user creation (and hopefully throw error client side)
There are 2 functions which I've found in the docs that presumably can help me do it:
Accounts.validateNewUser
Problem: it receives 'trimmed-down' user object which doesn't contain properties other than profile, username, password, email. Thus I cannot validate account.type as it doesn't exist on user object being validated.
Accounts.onCreateUser
Problem: it is called after a generic user object is created and there is no way I can cancel inserting new document in Users collection. It absolutely requires to return a user document. If I return undefined it throws errors on server:
Exception while invoking method 'createUser' Error: insert requires an argument
It also doesn't allow to throw method errors (as it's not a method) -> thus I cannot log error client side.

You can use Accounts.validateNewUser with little change to your data structure:
userData = {
profile: {
name: 'Artem',
account : {
type : 'free'
}
},
username: 'aaa#gmail.com',
password: '123'
};
Then you should be able to access data you need.
As far as I remember there were some discussion on meteor forum about removing profile field, that's why I'm solving this kind of problems in different way. For me Meteor.users is collection which should not be changed for sake of peace in mind - it could be changed by future version of meteor. My approach require to write more code in the beginning, but later it pays off, because you have place to store data about user and Meteor.users collection has docs with minimal amount of data.
I would use jagi:astronomy#0.12.1 to create schema and custom methods. In general I would create new collection UserAccounts with schema:
UserAccount = new Astro.Class( {
name: 'UserAccount',
collection: 'UserAccounts',
fields: {
'userId' : {type: 'string'},
'type' : {type: 'string', default:'free'}
},
} )
and add schema to Meteor.users :
User = new Astro.Class( {
name: 'User',
collection: Meteor.users,
fields: {
'services' : {type: 'object'},
'emails' : {type: 'array'}
},
methods:{
account : function(){
return UserAccounts.findOne({userId:this._id})
}
}
} )
The usage looks like this:
var user = Meteor.users.findOne();
user.account().type
In summary:
Accounts.onCreateUser : always allow to create user account and always create UserAccount which corresponds to it ( with field userId)

Related

Retrieving data from custom useraccounts fields

I'm using the Atmosphere package meteor-useraccounts, but I cannot figure out how to retrieve data from custom made fields.
I made the field:
AccountsTemplates.addField({
_id: 'callsign',
type: 'text',
placeholder: {
signUp: "Callsign"
},
required: true,
});
and I would like to get the logged in user's callsign from the user's collection.
I tried to see if the registration pushes the data to the user's collection, but it seems not so. Is there a way to do this?
try hooking into the account creation, it should show you your values in options.profile. then you can save them to the user object or wherever you would like. put this code somewhere on the server:
Accounts.onCreateUser((options, user) => {
console.log('--------------------------------');
console.log('options:', options);
console.log('--------------------------------');
console.log('user:', user);
console.log('--------------------------------');
return user;
});
be sure to return the user object at the end.

SimpleSchema update error

I'm trying to create a favorite button by saving the usersId to the logged in users account. The concept is, if there is a userId (user is a favorite), else user is not a favorite. The problem is I'm getting an error update failed: Error: Favorites must be an array and I'm not sure what this means.
Path: schema.js
Schema.UserProfile = new SimpleSchema({
"favorites.$.favorite": {
type: Object
}
});
Path: studentlist.js
Template.student.events({
'click .favourite':function(event,template) {
console.log('click');
var candidateId = this._id;
Meteor.users.update({_id: Meteor.userId() }, { $set: { "profile.favorites": candidateId }});
}
});
You have basically two errors.
as of the error, you must have defined Favorites property to be an array. and in the code you're trying to update with $set command.
when you're inserting an item into an array in MongoDB, you've to use $push operator.
and the second problem you'll face after fixing this one would be the improper data type insertion. because you've defined favorite to be an object, but trying to insert a mere id.

MDG ValidatedMethod with Aldeed Autoform: "_id is not allowed by the schema" error

I'm getting the error "_id is not allowed by the schema" when trying to use an autoform to update a collection via a ValidatedMethod.
As far as I can see from this example and the official docs there is no expectation for my schema to include the _id field, and I wouldn't expect to be updating the id from an update statement, so I have no idea why this error is happening.
If I switch from using the validated method to writing directly to the collection (with a schema attached to the collection that doesn't have the id in) everything works as expected, so I'm assuming the issue is with my the validate in my ValidatedMethod.
Any idea what I'm doing wrong?
Template: customer-edit.html
<template name="updateCustomerEdit">
{{> quickForm
collection="CustomerCompaniesGlobal"
doc=someDoc
id="updateCustomerEdit"
type="method-update"
meteormethod="CustomerCompanies.methods.update"
singleMethodArgument=true
}}
</template>
Template 'code behind': customer-edit.js
Template.updateCustomerEdit.helpers({
someDoc() {
const customerId = () => FlowRouter.getParam('_id');
const instance = Template.instance();
instance.subscribe('CustomerCompany.get', customerId());
const company = CustomerCompanies.findOne({_id: customerId()});
return company;
}
});
Update Validated Method:
// The update method
update = new ValidatedMethod({
// register the name
name: 'CustomerCompanies.methods.update',
// register a method for validation, what's going on here?
validate: new SimpleSchema({}).validator(),
// the actual database updating part validate has already been run at this point
run( newCustomer) {
console.log("method: update");
return CustomerCompanies.update(newCustomer);
}
});
Schema:
Schemas = {};
Schemas.CustomerCompaniesSchema = new SimpleSchema({
name: {
type: String,
max: 100,
optional: false
},
email: {
type: String,
max: 100,
regEx: SimpleSchema.RegEx.Email,
optional: true
},
postcode: {
type: String,
max: 10,
optional: true
},
createdAt: {
type: Date,
optional: false
}
});
Collection:
class customerCompanyCollection extends Mongo.Collection {};
// Make it available to the rest of the app
CustomerCompanies = new customerCompanyCollection("Companies");
CustomerCompaniesGlobal = CustomerCompanies;
// Deny all client-side updates since we will be using methods to manage this collection
CustomerCompanies.deny({
insert() { return true; },
update() { return true; },
remove() { return true; }
});
// Define the expected Schema for data going into and coming out of the database
//CustomerCompanies.schema = Schemas.CustomerCompaniesSchema
// Bolt that schema onto the collection
CustomerCompanies.attachSchema(Schemas.CustomerCompaniesSchema);
I finally got to the bottom of this. The issue is that autoform passes in a composite object that represents the id of the record to be changed and also a modifier ($set) of the data, rather than just the data itself. So the structure of that object is along the lines of:
_id: '5TTbSkfzawwuHGLhy',
modifier:
{
'$set':
{ name: 'Smiths Fabrication Ltd',
email: 'info#smithsfab.com',
postcode: 'OX10 4RT',
createdAt: Wed Jan 27 2016 00:00:00 GMT+0000 (GMT Standard Time)
}
}
Once I figured that out, I changed my update method to this and everything then worked as expected:
// Autoform specific update method that knows how to unpack the single
// object we get from autoform.
update = new ValidatedMethod({
// register the name
name: 'CustomerCompanies.methods.updateAutoForm',
// register a method for validation.
validate(autoformArgs) {
console.log(autoformArgs);
// Need to tell the schema that we are passing in a mongo modifier rather than just the data.
Schemas.CustomerCompaniesSchema.validate(autoformArgs.modifier , {modifier: true});
},
// the actual database updating part
// validate has already been run at this point
run(autoformArgs)
{
return CustomerCompanies.update(autoformArgs._id, autoformArgs.modifier);
}
});
Excellent. Your post helped me out when I was struggling to find any other information on the topic.
To build on your answer, if for some reason you want to get the form data as a single block you can use the following in AutoForm.
type="method" meteormethod="myValidatedMethodName"
Your validated method then might look something like this:
export const myValidatedMethodName = new ValidatedMethod({
name: 'Users.methods.create',
validate(insertDoc) {
Schemas.NewUser.validate(insertDoc);
},
run(insertDoc) {
return Collections.Users.createUser(insertDoc);
}
});
NB: The Schema.validate() method then requires an Object, not the modifier as before.
I'm unclear if there are any clear advantages to either method in general.
The type="method-update" is obviously the way you want to go for updating documents because you get the modifier. The type="method" seems to be the best way to go for creating a new document. It would likely also be the best option in most cases where you're not intending to create a document from the form data.

Meteor alanning:roles - Error invoking Method 'updateRoles': Internal server error [500]

I am trying to set the role for the logged in user (using alanning:roles package) via a method on the server. Here's what I have...
Client
var userId = Meteor.userId();
Meteor.call('updateRoles',userId,'admin');
And this is the simplified version of the method from the docs...
server/userMethods.js
Meteor.methods({
updateRoles: function (targetUserId, roles) {
Roles.setUserRoles(targetUserId, roles)
}
})
No matter what I try I keep getting the following error...
Error invoking Method 'updateRoles': Internal server error [500]
Problem solved.
The reason was because I am using autoform (simple schema) for the 'users' collection and I needed to include the following (uncommented part) in the schema...
// Add `roles` to your schema if you use the meteor-roles package.
// Option 1: Object type
// If you specify that type as Object, you must also specify the
// `Roles.GLOBAL_GROUP` group whenever you add a user to a role.
// Example:
// Roles.addUsersToRoles(userId, ["admin"], Roles.GLOBAL_GROUP);
// You can't mix and match adding with and without a group since
// you will fail validation in some cases.
//
//roles: {
// type: Object,
// optional: true,
// blackbox: true
//},
// Option 2: [String] type
// If you are sure you will never need to use role groups, then
// you can specify [String] as the type
roles: {
type: [String],
optional: true
},

Adding a field to the user document on new user creation (Meteor)

I'm trying to add a field, score, to users on creation.
I put this code in the server hook:
Accounts.onCreateUser(function(options, user) {
user.score = 1;
return user;
});
But when I try Meteor.user() in the console, I don't see the score object.
You're following the right approach for inserting the extra key, using onCreateUser is correct.
However, by default Meteor doesn't publish all the keys in the user object. On the client you will only see _id, emails, and profile. If you want to see score on the client you can either stuff it into the profile instead of at the root of the user document (easiest):
user.profile.score = 1; // in onCreateUser
or include the score key in a user publication on the server:
Meteor.publish('me',function(){
return Meteor.find({ _id: this._id },{ fields: { profile: 1, emails: 1, score: 1 }});
});
And subscribe to it on the client:
Meteor.subscribe('me');

Resources