0

I have a set of users defined like this:

Accounts.createUser({
    username:'Simon', 
    email:'simon@email.com', 

    profile:{
        firstname:'Simon',
        lastname:'Surname',
        location:'Home Address',

        privacy: {
            location:0,
            emails:0 } //Location and emails are private and should not be disclosed
    }
});

My question is how can I publish this user's record for other users to view, taking into account the profile privacy settings. In this example, I have set the privacy for location and emails to zero with the intention that this information is not published for this user.

I would like to publish it using the standard method:

Meteor.publish("usersWithPublicEmails", function () {
    return Meteor.users.find();
});

But I cannot see a way to specify the selector or fields in such a way that only public information will be published.

I have tried adding additional publications of the form:

Meteor.publish("allUsers", function () {
    return Meteor.users.find( {}, {fields:{username:1}} );
});

Meteor.publish("usersWithPublicEmails", function () {
    return Meteor.users.find( {"profile.privacy.emails":1}, {fields:{username:1, emails:1}} );
});

but the selector does not seem to be returning the emails as I expected. I am looking for optimal way to do this from a performance point of view.

Kyll
  • 7,036
  • 7
  • 41
  • 64
Braunius
  • 95
  • 1
  • 11
  • Mmm. I got this working by adding a missing subscription in my router.js module. However, after adding further privacy settings (including publications and subscriptions) I am seeing some odd behaviour. With 4 subscriptions in my router: `waitOn: function(){ return Meteor.subscribe('allUsers') && Meteor.subscribe('usersWithPublicName') && Meteor.subscribe('usersWithPublicLocation') && Meteor.subscribe('usersWithPublicEmails') ; },` I am finding that the 3rd one is not effective. Changing the order removes that corresponding data from my users page! – Braunius Jan 27 '15 at 07:45
  • I also tried specifying my waitOn like this `waitOn: function(){ return [ Meteor.subscribe('allUsers'), Meteor.subscribe('usersWithPublicName'), Meteor.subscribe('usersWithPublicLocation'), Meteor.subscribe('usersWithPublicEmails') ] ; },` with the same results. – Braunius Jan 27 '15 at 08:03

3 Answers3

0

Mongodb is not a relational database so whenever I want to join or query based on metadata I remember I have to do things differently. In your case I would make a separate Collection for user privacy if I wanted to query on user privacy. In addition, if I cared about performance I probably would never want "all of x", I would just want enough to show the user, thus paginate. With these two ideas in mind you can easily get what you want: query based on privacy settings and performance.

Privacy = new Mongo.Collection("privacy");

Whenever we want to add privacy to an account:

Privacy.insert({
  emails: 1,
  userId: account._id,
});

Then later, one page at a time, showing ten results each page, tracking with currentPage:

Meteor.publish("usersWithPublicEmails function (currentPage) {
    var results = []
    var privacyResults = Privacy.find({"emails":1}, {skip: currentPage,  
        limit: 10});
    var result;
    while (privacyResults.hasNext() ) {
       result = privacyResult.next();
       results.append(Meteor.users.find({_id: result.userId});
    }
    return result;
});

I didn't test this code, it may have errors, but it should give you the general idea. The drawback here is that you have to keep privacy and users in sync, but these are the kinds of problems you run into when you're not using a relational database.

Mongodb has a way to do this kind of reference lookup with less code, but it still happens on demand and I prefer the flexibility of doing it myself. If you're interested take a look at Database references

Bjorn
  • 69,215
  • 39
  • 136
  • 164
  • Hi Bjorn, I have tracked down what was going wrong in my case (missing subscription) but this advice is really useful. I have been wondering how to limit the return values and this example is really helpful. Many thanks. – Braunius Jan 27 '15 at 07:16
  • Braunius, on StackOverflow you thank with upvotes. ;\ – Bjorn Jan 27 '15 at 08:09
  • Bjorn, I tried but I don't have enough reputation - which seems like an odd thing but there you go. BTW I found that you need to use privacyResults.forEach to loop around cursors. I couldn't find a way to manipulate the value in a record set prior to returning it - which would be ideal for my scenario. – Braunius Jan 27 '15 at 09:33
0

That's because you have a typo in your publish function's fields object, instead of email you've typed emails

So the correct function would be:

Meteor.publish("usersWithPublicEmails", function () {
    return Meteor.users.find( {"profile.privacy.emails":1}, {fields:{username:1, email:1}} );
});

Furthermore, you're already publishing all usernames in your allUsers publication, therefore, in order to add the missing data for relevant public users, you'll just need this:

Meteor.publish("usersWithPublicEmails", function () {
    return Meteor.users.find( {"profile.privacy.emails":1}, {fields:{email:1}} );
});

and Meteor will automatically merge those records for you.

Serkan Durusoy
  • 5,447
  • 2
  • 20
  • 39
  • Hi Serkan, it isn't a typo. Meteor accounts allows you to create an email with the createUser method but stores multiple emails against each user. I was expecting meteor to merge the records as you say and am surprised that isn't happening. I must have done something else wrong. Thanks for taking the time to look at my question. – Braunius Jan 27 '15 at 07:07
  • Hi Serkan, I have tracked it down. I was missing the matching subscription in the waitOn clause in the router. :-( – Braunius Jan 27 '15 at 07:14
0

A simple solution in the end. I had missed the additional subscription in my router:

Router.route('/users', {

    name: 'userList',

    waitOn: function(){
        return Meteor.subscribe('allUsers') &&
               Meteor.subscribe('usersWithPublicEmails');
    },

    data: function(){
        return Meteor.users.find();
    }
});

A basic mistake:-(

Braunius
  • 95
  • 1
  • 11