Cannot access other users' email addresses in Meteor app - meteor

Before I begin, I have followed the steps here: Meteor Querying other users by email
And I have read the Meteor documentation about publishing users and how to add more fields than the id, username, and profile. My situation exists in spite of all of these things.
I'm trying to access other user's email addresses, beyond just the currently logged in user. I have 2 templates that need this access. The first template works and is able to access it. The second template is unable to.
Here is the setup code I have for publishing the emails field, and subscribing (I've also tried not specifying 'address' [e.g. fields: {emails: 1}] but that has the same result)
if (Meteor.isServer) {
Meteor.publish("allUsers", function () {
return Meteor.users.find({});
});
Meteor.publish("allUserData", function () {
return Meteor.users.find({}, {fields: {"emails.address": 1}});
});
};
if (Meteor.isClient) {
Meteor.subscribe("allUsers");
Meteor.subscribe("allUserData");
};
Here is the code from the template that works:
Template.createPartner.events({
'click .setup-partner' : function(event, template) {
var partner = Meteor.users.findOne({"emails.address": 'example#mail.com' }); <-- works
}
});
Here is the code from the template that doesn't work:
Template.infoSelect.partnerEmail = function() {
var partnerId = Meteor.user().profile.partnerId; <-- works
var partner = Meteor.users.findOne({_id: partnerId}); <-- works but only _id and profile are returned
return partner.emails[0].address; <-- throws exception because the 'emails' field doesn't exist
};
I've also tried this, but no difference:
var partner = Meteor.users.find({_id: partnerId}, {fields: {"emails.address": 1}});
Why can I not see the user's email address in the second template, but I can in the first?

I think its because you're subscribing to two sets of the same collection. Meteor uses the first subscription and ignores the second. I'm not sure why it works on one occasion though.
If you remove the first subscription and go with the second It should work, basically remove the line:
Meteor.subscribe("allUsers");
One more tip. You could alter your email function to:
Template.infoSelect.partner = function() {
var partnerId = Meteor.user().profile.partnerId; <-- works
var partner = Meteor.users.findOne({_id: partnerId}); <-- works but only _id and profile are returned
return partner;
};
And your handlebar would be : (it just opens up more options for your partner variable so you could reference him/her by name too)
<template name="infoSelect">
{{partner.email.0.address}}
{{partner.profile.name}} <!--If you have configured profiles -->
</template>

Related

Meteor.user with Additional Fields on Client

In Meteor, one can add additional fields to the root-level of the new user document like so:
// See: https://guide.meteor.com/accounts.html#adding-fields-on-registration
Accounts.onCreateUser((options, user) =>
// Add custom field to user document...
user.customField = "custom data";
return user;
});
On the client, one can retrieve some data about the current user like so:
// { _id: "...", emails: [...] }
Meteor.user()
By default, the customField does not exist on the returned user. How can one retrieve that additional field via the Meteor.user() call such that we get { _id: "...", emails: [...], customField: "..." }? At present, the documentation on publishing custom data appears to suggest publishing an additional collection. This is undesired for reasons of overhead in code and traffic. Can one override the default fields for Meteor.user() calls to provide additional fields?
You have a couple of solutions that you can use to solve this.
Null Publication
Meteor.publish(null, function () {
if (this.userId !== null) {
return Meteor.users.find({ _id: this.userId }, { fields: { customField: 1 } });
} else {
return this.ready();
}
}, { is_auto: true });
This will give you the desired result but will also result in an additional database lookup.. While this is don't by _id and is extremely efficient, I still find this to be an unnecessary overhead.
2.Updating the fields the Meteor publishes for the user by default.
Accounts._defaultPublishFields.projection = { customField: 1, ...Accounts._defaultPublishFields.projection };
This has to be ran outside of any Meteor.startup blocks. If ran within one, this will not work. This method will not result in extra calls to your database and is my preferred method of accomplishing this.
You are actually misunderstanding the documentation. It is not suggesting to populate and publish a separate collection, just a separate publication. That's different. You can have multiple publications/subscriptions that all feed the same collection. So all you need to do is:
Server:
Meteor.publish('my-custom-user-data', function() {
return Meteor.users.find(this.userId, {fields: {customField: 1}});
});
Client:
Meteor.subscribe('my-custom-user-data');

Tracker autorun using findone

I have this piece of code in client side:
Tracker.autorun(function () {
if (params && params._id) {
const dept = Department.findOne({ _id: params._id }) || Department.findOne({ name: params._id });
if (dept) {
}
}
});
params will be passed into the url. So, initially we won't have the department data and the findOne method will return null, and then later on, when data arrives, we can find the department object.
But if user enters an invalid id, we need to return them 404. Using tracker autorun, how can I distinguish between 2 cases:
a. Data is not there yet, so findOne returns null
b. There is no such data, even in server's mongodb, so findOne will also returns null.
For case a, tracker autorun will work fine, but for case b, I need to know to return 404
I would suggest you to subscribe to data inside template, like below so you know when subscriptions are ready, then you can check data exists or not
Template.myTemplate.onCreated(function onCreated() {
const self = this;
const id = FlowRouter.getParam('_id');
self.subscribe('department', id);
});
Template.myTemplate.onRendered(function onRendered() {
const self = this;
// this will run after subscribe completes sending records to client
if (self.subscriptionsReady()) {
const id = FlowRouter.getParam('_id');
const dept = Department.findOne({ _id: params._id }) || Department.findOne({ name: params._id });
if (dept) {
// found data in db
} else {
// 404 - no department found in db
}
}
});
If you are using Iron-Router, you may try this hack.
Router.route('/stores', function() {
this.render('stores', {});
}, {
waitOn: function() {
return [
Meteor.subscribe('stores_db')
];
}
});
The sample code above will wait for the subscription "stores_db" to complete, before rendering anyhing. Then you can use your findOne logic no problems, ensuring that all documents are availble. This suits your situation.
This is what I used to do before I completely understand MeteorJS publications and subscriptions. I do not recommend my solution, it is very bad to user experience. Users will see the page loading forever while the documents are being download. #Sasikanth gave the correct implementation.

Meteor.js accounts package [duplicate]

I want to have client-side access for a certain set of fields for ALL users while I would like to have access to even more fields for the current user only. How do I go about writing publish code to accomplish this?
Right from Meteor documentation:
Meteor.publish("userData", function () {
return Meteor.users.find({_id: this.userId},
{fields: {'other': 1, 'things': 1}});
});
And also:
Meteor.publish("allUserData", function () {
return Meteor.users.find({}, {fields: {'nested.things': 1}});
});
Hope this helps.
As mentioned above, the
Meteor.publish("userData", function () {
return Meteor.users.find({_id: this.userId},
{fields: {'other': 1, 'things': 1}});
});
and
Meteor.publish("allUserData", function () {
return Meteor.users.find({}, {fields: {'nested.things': 1}});
});
publish functions will push the data from the Users collection.
Subscribe with
Tracker.autorun(function () {
Meteor.subscribe("userData");
Meteor.subscribe("allUserData");
});
And the additional data will automatically go into the Users collection and be available in the Meteor.user() object.
My story with that:
I proceeded as documentation says, but encountered with weird behavior.
I had publish function, where I published whole profile and email object for current user (lets say userData) and just some subset for the other users (allUserData).
When I had -
Meteor.subscribe("allUserData");
Meteor.subscribe("userData");
On client side right after user logged in, I've received just allUserData data. Thats mean even for my logged in user (That user couldn't see his own email address). When I refresh browser, bug was fixed and I got properly allUserData for all users except one logged in, which has his proper userData (with mentioned email address).
What is interesting, if I changed the sequence of that subscriptions, bug was fixed.:
Meteor.subscribe("userData");
Meteor.subscribe("allUserData");
Putting into Meteor.autosubscribe(function () { }) doesn't changed anything.
Finally I tried put that subscription into Deps.autorun(function() { }) and explicitly add reactivity and the problem with sequence was resolved..:
Deps.autorun(function() {
Meteor.subscribe("allUserData", Meteor.userId());
Meteor.subscribe("userData", Meteor.userId());
// or
// Meteor.subscribe("userData", Meteor.userId());
// Meteor.subscribe("allUserData", Meteor.userId());
});
In publish function I just replace this.userId with userId from parameter.
With next bug which I encountered was, that I've got secret systemData object in profile user's object and that can see just admins, not regular logged in users. But although correct set publish function with 'profile.systemData': 0 that secret object could see all logged in users which looked into his profile object.
Probably it was because my publish function(s) somehow interfered with publish function in Meteor Account package:
// Publish the current user's record to the client.
Meteor.publish(null, function() {
if (this.userId) {
return Meteor.users.find(
{_id: this.userId},
{fields: {profile: 1, username: 1, emails: 1}});
} else {
return null;
}
}, /*suppress autopublish warning*/{is_auto: true});
Anyway I resolved it with help of method Account.onCreateUser() and adding systemData next to profile object, not into profile.
There starts my other problems :) see Meteor.loginWithPassword callback doesn't provide custom object in User accounts doc
PS: If I knew it at begin, I've put systemData object into special collection.

meteorjs meteor-user-status mizzao package: cannot get other users

Trying to implement mizzao/meteor-user-status user status package, but only getting the status for the currently logged in user (me). Others show undefined.
Code in /server folder:
Meteor.publish('userStatus', function() {
return Meteor.users.find({
"status.online": true
});
});
Code in /client folder:
Meteor.subscribe('userStatus');
Template.member.helpers({
isOnline: function(username) {
console.log(username); //gets each username, not just logged me logged in
var currentUser = Meteor.users.findOne({
username: username
});
console.log(currentUser.status.online); //gets undefined for other users other than me
return currentUser.status.online; //returns undefined for other users
}
});
Maybe this needs to reference "userStatus" somehow instead of Meteor.users.findOne(...), but since global object was not created, e.g. like UsersOnline = new Mongdb.Collection - I don't know how to implement this.
Getting error in Chrome debugging tool: TypeError: Cannot read property 'status' of undefined, obviously, it only gets currently logged in user.
Edit: To clarify: I get other users from a custom "profiles" collection in Mongo that I created specifically to overcome the issue of not being able to read "users" collection, due to security, I guess. So I get the list of profiles and supply the user name to this function that supposed to use mizzao package to see if they are online. Since the package reads from "users" collection, it only returns current user (me).
I would be totally OK if I could modify the package for it to write to my "profiles" collection, if someone suggests how to do that.
Another update:
I ran the count on the server, and it returns only one (me, I guess).
var cursor = Meteor.users.find({
"status.online": true
}, {
fields:{
_id: 1,
status: 1,
username: 1
// Any other fields you may need
}
});
console.log(cursor.count());
If you have a publication like this:
Meteor.publish('userStatus', function() {
return Meteor.users.find({
"status.online": true
});
});
it will return users that are online only. So you get undefined for users other than you, because they are not logged-in.
Try logging in as a different user at the same time (i.e. using different browser), and see whether you can now see the status of that user.
You can always change your publication to:
Meteor.publish('userStatus', function() {
return Meteor.users.find({}, {fields: {username : 1, status : 1}});
});
and you will get statuses for all users. But if you only need to check the "online" flag (i.e. you don't need other features of the package), it is better to only send data about the online users and consider not published user as offline.
I guess you trying to read users before subscription is ready.
Server:
Meteor.publish('userStatus', function() {
return Meteor.users.find({
"status.online": true
}, {
fields:{
_id: 1,
status: 1,
profile: 1,
username: 1
// Any other fields you may need
}
});
});
Client (you should return cursor, which will re-run once subscription is ready):
Meteor.subscribe('userStatus');
Template.member.helpers({
isOnline: function(username) {
console.log(username); //gets each username, not just logged me logged in
var cursor = Meteor.users.find({username: username});
if(cursor.count()){
var currentUser = cursor.fetch()[0];
if(currentUser.status && currentUser.status.online){
console.log(currentUser.status.online); //gets undefined for other users other than me
return currentUser.status.online; //returns undefined for other users
}
}
return {};
}
});

Modify data in Meteor.publish before sending down to client

I have the following use case:
I have a users table in MongoDB on the backend, which is a separate service than the frontend. I use DDP.connect() to connect to this backend service.
Each user has a set of "subjects"
Each subject in the users table is referenced by id, not name. There is a separate table called "subjects" that holds the subjects by id.
I want to publish the user down to the client, however I want the published user to be populated with the subjects first.
I tried the following, inspired by this blog post:
// in the backend service on port 3030
Meteor.publish('users', function(userId) {
var _this = this;
Meteor.users.find({
_id: userId
}).forEach(function(user) {
user.profile = populate(user.profile);
console.log(user);
_this.changed('users', userId, {
_id: userId,
profile: user.profile
});
});
_this.ready();
});
// in the client
var UserService = DDP.connect('http://localhost:3030');
var UserServiceProfile = UserService.subscribe('users', Meteor.userId());
console.log(UserServiceProfile);
This gives the following error on the backend:
Exception from sub users id akuWx5TqsrArFnQBZ Error: Could not find element with id XhQu77F5ChjcMTSPr to change.
So I tried changing _this.changed to _this.added. I don't get any errors, but the changes aren't showing up in the client minimongo, even though I can see that the populate function worked through the console.log(user) line.
I'm not sure how you'd fix your code, but you might not need to. It looks like you want the https://atmospherejs.com/maximum/server-transform package.
Add the server-transform package to your project, and replace your code with this (I'm going to assume you also have underscore added to your project, and the subjects collection in your database corresponds to a global variable called Subjects in your code.):
Meteor.publishTransformed('users', function(userId) {
return Meteor.users.find({
_id: userId
}).serverTransform({
'profile.subjects': function(doc) {
var subjects = [];
_(doc.profile.subjects).each(function(subjectId) {
subjects.push(Subjects.findOne(subjectId));
});
return subjects;
}
});
});

Resources