When I query Meteor.users I do not receive the services field or any other custom fields I have created outside of profile. Why is it that I only receive _id and profile on the client and how can I receive the entire Meteor.users object?
Thanks.
From the DOcs
By default, the current user's username, emails and profile are published to the client. You can publish additional fields for the current user with:
As said above If you want other fields you need to publish them
// server
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'services': 1, 'others': 1}});
} else {
this.ready();
}
});
// client
Meteor.subscribe("userData");
The above answer does work, but it means you have to subscribe to said data, which you should do if you are getting data from users other than the currently logged in one.
But if all you care about is the logged in user's data, then you can instead use a null publication to get the data without subscribing.
On the server do,
Meteor.publish(null, function () {
if (! this.userId) {
return null;
}
return Meteor.users.find(this.userId, {
fields: {
services: 1,
profile: 1,
roles: 1,
username: 1,
},
});
});
And this is actually what the accounts package does under the hood
Related
I'm having a problem updating the current user's email address in Meteor. The problem code is here (it's all client code):
var id = Meteor.userId();
if (firstName) {
Meteor.users.update(
{ _id: id },
{ $set: { "profile.firstName": firstName }}
)};
if (lastName) {
Meteor.users.update(
{ _id: id },
{ $set: { "profile.lastName": lastName }}
)};
var oldEmail = Meteor.user().emails[0].address;
if (newEmail) {
Meteor.users.update(
{ _id: id, 'emails.0.address': oldEmail },
{ $set: { 'emails.0.address': newEmail }}
)};
The first two lines work fine (user can update their first and last names), but the last update fails with the following error showing in the console:
"errorClass {isClientSafe: true, error: 403, reason: "Not permitted. Untrusted code may only update documents by ID."
I don't understand the error, because I AM updating by ID--or at least I think I am.
Also: if I remove the reference to the old email, I get a simple "Update failed. Access denied" error in the console instead of the above-mentioned error.
Is there a way to fix this with client-side code only?
(I realize I'll also need to reset the "verified" key back to false, but that's a different issue I guess)
Do not edit the emails array directly. The Accounts package has utility functions for this use case, see Accounts.addEmail and Accounts.removeEmail.
First you will need to invoke a Method since these functions must be performed on the server.
Client js:
if (newEmail) {
Meteor.call('updateEmail', newEmail, err => {
if (err) console.log(`Error updating email address: {err}`);
});
}
server:
Meteor.methods({
updateEmail(newAddress) {
const userId = this.userId;
if (userId) {
const currentEmail = Meteor.users.findOne(userId).emails[0].address;
Accounts.addEmail(userId, newAddress);
Accounts.removeEmail(userId, currentEmail)
}
return;
}
});
You should also validate in your method that the new email address is a string that has an email address pattern.
I am creating my first major app, and I thought of a way to optimize query performance. I am not sure though whether I should go through with it.
Here is a description of my approach.
Every time a user account is created, that user is assigned a random number between 1 and 10, which is stored in their user document. The number is called a number-Id. Here is how the user schema will look like:
let User = new Schema({
/// I left out all the other fields for clairty sake
numberId: {
type: Number,
default: Math.floor(Math.random() * 11),
index: true
}
}
Every time a user creates a blogpost and post, their number-Id is referenced inside the document of that blogpost and post. This is to make querying much faster by indexing the users number-id. Here is how the document of a blogpost would look like in MongoD:
{
"title": "my Blog Post",
"_id": "ObjectId("594824b2828d7b15ecd7b6a5")",
/// Here is the numberId of the user who posted the blogpost, it is added
/// to the document of the blogpost when it is created.
"postersNumberId": 2
/// the Id of the user who posted the blogpost
"postersId": "59481f901f0c7d249cf6b050"
}
Let's say I want to get all the blogposts made by a specific user. I can optimize my query much faster by using the number-Id of the user in question as an index, given that their number-Id is referenced in all the blogposts and comment posts they make.
BlogPost.find({postersId: user_id, postersNumberId: user.numberId});
It seems like this approach warrants that I store the users number-id in req.user in order for it to be readily available whenever I need it to optimize queries. So that means I would have to store the users data in a cookie via passport:
passport.serializeUser(function(user, done){
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function (err, user){
if (err || !user) return done(err, null);
done(null, user);
});
});
Given this approach, I could now use all the information stored in the cookie, particularly the numberId, to optimize queries that retrieve the comments and blogposts a user makes:
BlogPost.find({postersId: req.user_id, postersNumberId: req.user.numberId});
However, I am using json-web-tokens to authenticate the user rather than cookies. So I will have to use a cookie to store the number-Id for indexing purposes in addition to using JWT for authentication. I've heard, however, that having cookies is bad for scalability, so I am worried that storing the users number-Id in req.user will eventually impact performance.
Should I continue with this approach, or no? What are the performance implications?
In addition to authentication JWT has a payload, which can be used to store additional information within the generated token itself:
var jwt = require('jsonwebtoken');
var token = jwt.sign({
data: {
numberId: 7
}
}, 'jwtSecret', {
expiresIn: '1h'
});
For retrieval:
jwt.verify(token, 'jwtSecret', function(err, decoded) {
if (err) {
console.log(err)
} else {
console.log(decoded);
//{ data: { numberId: 7 }, iat: 1498350787, exp: 1498354387 }
}
});
I'm trying to return all groups that the current user is a part of. Each user has a groups field that's simply an array of id's for the groups the user belongs to. Here's what my code looks like:
My server method for returning the correct groups
userGroups: function(){
var currentUser = Meteor.users.find({_id: Meteor.userId()});
return Groups.find({_id: { $in: currentUser.groups}});
}
Then the call to that method in my helper:
groups: function(){
return Meteor.call('userGroups');
}
I've tried debugging this in the console but I'm just getting more confused. I can call var user = Meteor.users.find(_id: Meteor.userId()) and it correctly assigns the current user to the variable, but then when I call user.groups (which is the array of group id's) it says it's undefined. If I check the document in the meteor mongo command line interface, the current user has a groups field with group id's in it.
find query in Meteor returns a cursor which is not an array, but an object.
You should add .fetch() or use findOne().
userGroups: function(){
var currentUser = Meteor.users.findOne({_id: Meteor.userId()});
// or use var currentUser = Meteor.users.find({_id: Meteor.userId()}).fetch();
return Groups.find({_id: { $in: currentUser.groups}});
}
This should work!
Update on publishing groups form Meteor.users collection
To add groups from Meteor.users collection to your Meteor.user() auto-publish which acccounts-password package does, you need to include that into a null publication.
Something like this:
Meteor.publish(null, function () {
if (!this.userId) return this.ready();
return Meteor.users.find({_id: this.userId}, {
fields: {
profile : 1,
emails : 1,
groups : 1
}
});
});
I'm currently creating an app that will be used by multiple companies.
Each user has the following profile:
username: johnDoe
emails: [{address: "some#email.com", verified: true}],
profile: {
name: "John Doe",
companyId: "1234"
}
I then have a collection (called Companies) of company objects that contain configuration info, templates etc specific to that company.
{
id: "1234",
configuration: {},
templates: []
}
In order to isolate each companies data I want to only publish data that matches the users profile companyId to the companies id.
if (Meteor.isServer) {
// collection to store all customer accounts
Companies = new Mongo.Collection('Companies');
// publish collection
Meteor.publish("Company", function () {
return Companies.find({id: Meteor.user().profile.companyId});
})
}
This currently works if I hardcode the clinic Id
// publish collection
Meteor.publish("Company", function () {
return Companies.find({id: "1234");
})
But returns an empty cursor with the Meteor.user().profile.companyId.
This means that the issue is either that I'm using the wrong function or more probably, the publish is happening before the user().profile.companyId can run.
Anybody know what I'm doing wrong? and do you have any advice on what to read up about so that I have an understanding of this moving forwards?
Thanks
Try doing an explicit findOne() in your publish function:
// publish collection
Meteor.publish("Company", function () {
var user = Meteor.users.findOne({_id: this.userId});
if(user && user.profile && user.profile.companyId) {
return Companies.find({id: user.profile.companyId});
} else {
console.log(user);
return this.ready();
}
});
Getting into sails.js - enjoying the cleanliness of models, routes, and the recent addition of associations. My dilemma:
I have Users, and Groups. There is a many-many relationship between the two.
var User = {
attributes: {
username: 'string',
groups: {
collection: 'group',
via: 'users'
}
}
};
module.exports = User;
...
var Group = {
attributes: {
name: 'string',
users: {
collection: 'user',
via: 'groups',
dominant: true
}
}
};
module.exports = Group;
I'm having difficulty understanding how I would save a user and it's associated groups.
Can I access the 'join table' directly?
From an ajax call, how should I be sending in the list of group ids to my controller?
If via REST URL, is this already accounted for in blueprint functions via update?
If so - what does the URL look like? /user/update/1?groups=1,2,3 ?
Is all of this just not supported yet? Any insight is helpful, thanks.
Documentation for these blueprints is forthcoming, but to link two records that have a many-to-many association, you can use the following REST url:
POST /user/[userId]/groups
where the body of the post is:
{id: [groupId]}
assuming that id is the primary key of the Group model. Starting with v0.10-rc5, you can also simultaneously create and a add a new group to a user by sending data about the new group in the POST body, without an id:
{name: 'myGroup'}
You can currently only add one linked entity at a time.
To add an entity programmatically, use the add method:
User.findOne(123).exec(function(err, user) {
if (err) {return res.serverError(err);}
// Add group with ID 1 to user with ID 123
user.groups.add(1);
// Add brand new group to user with ID 123
user.groups.add({name: 'myGroup'});
// Save the user, committing the additions
user.save(function(err, user) {
if (err) {return res.serverError(err);}
return res.json(user);
});
});
Just to answer your question about accessing the join tables directly,
Yes you can do that if you are using Model.query function. You need to check the namees of the join tables from DB itself. Not sure if it is recommended or not but I have found myself in such situations sometimes when it was unavoidable.
There have been times when the logic I was trying to implement involved a lot many queries and it was required to be executed as an atomic transaction.
In those case, I encapsulated all the DB logic in a stored function and executed that using Model.query
var myQuery = "select some_db_function(" + <param> + ")";
Model.query(myQuery, function(err, result){
if(err) return res.json(err);
else{
result = result.rows[0].some_db_function;
return res.json(result);
}
});
postgres has been a great help here due to json datatype which allowed me to pass params as JSON and also return values as JSON