Basic question about publications


#1

My app is about mirroring personality with items (images, colors and words) for a specific topic e g self or an organization. The user picks items that mirrors the topic. You can also create a group and let others have their say. Then you can view all items together if you’re a member of the group.

This is the idea. How do I solve this? So far I have a userId, mirrorId and groupId to each item in the db but not sure how to solve the publication for either getting by userId and mirrorId or groupId.

Meteor.publish('items', function(){ return Items.find(????); });


#2

Can I have two publications for one collection in the db, one called ‘items’ that find by userId and mirrorId, plus one called ‘groupitems’ that find by groupId? Is that the solution?

(My next step: how to make the find reactive)


#3

Yes - that’s perfectly okay. You should be aware that Meteor’s merge box will make all relevant documents available in the client, which means you do need to ensure your client finds have the same filter criteria as the server publications.


#4

Thanks for the help but I’m almost done with meteor. Jessus…Not working. I’m confused, don’t get it, not understand the publish and subscribe thing.


#5

This was a good read for me when I was getting started (and having trouble understanding pub/sub):

https://www.discovermeteor.com/blog/understanding-meteor-publications-and-subscriptions/


#6

Thank you but I get that, not finding my answer - though a good explanation that should be part of meteors site.

Let me rephrase my question (such a fundamental aspect of a web app and getting data from the db that I think my initial question is the same thing).

Peter has selected a color ‘red’. John has selected ‘green’. They can see their own selections separately but also click on “view group” that both are a part of.

They can also select other colors that are not part of the group, meaning that John should not be able to see other selections Peter has made.

How do you publish and subscribe that?


#7

At the most basic, you have something like:

// client.js
let query = {};
query.color = 'red';
Meteor.subscribe('items', query);

// server.js
import { check } from 'meteor/check';
import { Match } from 'meteor/check';

Meteor.publish('items', function(query) {

  check(query, {
    color: Match.Maybe(String) // Check expected fields
  });

  return Items.find(query);
});

This would return all of the items that match the { color: 'red' } criteria. You can build your subscriptions dynamically, and each user will only see the records that s/he is subscribed to.

If you have your groupId stored in (for example) your Meteor.user doc, you could also do something like:

// client.js
let query = { groupId: Meteor.user().groupId };

// server.js
import { check } from 'meteor/check';
import { Match } from 'meteor/check';

Meteor.publish('items', function(query) {
  
  check(query, {
    color: Match.Maybe(String), // Check expected fields
    groupId: Match.Maybe(String)
  });

  return Items.find(query);
});

OR, if you wanted something more secure:

// server.js
import { check } from 'meteor/check';
import { Match } from 'meteor/check';

Meteor.publish('items', function(query) {
 
 let groupId;

  if(this.userId) {
    groupId = Meteor.users.findOne({ _id: this.userId }).groupId;
    query.groupId = groupId;
  }

  check(query, {
    color: Match.Maybe(String), // Check expected fields
    groupId: Match.Maybe(String)
  });

  return Items.find(query);
});

#8

Thank you. Will calm down and try again. :slight_smile:


#9

That publication code is not completely secure.

  1. If this.userId changes, the publication is (re) run and works as expected. However, if the _id of the document being retrieved is changed to another user’s _id, the publication will not re-run (publications are not inherently reactive), so the user still gets to see the original findOne's result. I realise this is a contrived example, but it’s better to get good practices in early.
    The way to secure this example is to use this.userId in the findOne. You can also get rid of a line of code as well :slight_smile:.
    The Meteor Guide illustrates this with a similar example: https://guide.meteor.com/security.html#publications-user-id

  2. Passing a search object from the client to be used on the server without sanitizing is very dangerous. Check out @pcorey’s excellent http://www.east5th.co/blog/2015/04/06/nosql-injection-or-always-check-your-arguments/ (and all the other blogs on East5th, of course!)


#10

Shouldn’t have left that out, so edited the post above. This is the basic kind of check I do for queries – does it look substantial enough?


#11

If the user is not logged in and they know the form of the query object, they can still execute the Items.find().


#12

So just to follow up, if the expectation was that a non-logged in user could query the color but not the groupId, you’d have:

Meteor.publish('items', function(query) {
 
 let groupId;

  if(this.userId) {

    groupId = Meteor.users.findOne({ _id: this.userId }).groupId;
    query.groupId = groupId;

    check(query, {
      color: Match.Maybe(String), // Check expected fields
      groupId: Match.Maybe(String)
    });

  } else {

      check(query, {
        color: Match.Maybe(String), // Check expected fields
      });

  }

  return Items.find(query);
});

#13

Yes, or if a non-logged-in user could do nothing:

Meteor.publish('items', function(query) {
 
  if(this.userId) {

    const groupId = Meteor.users.findOne({ _id: this.userId }).groupId;
    query.groupId = groupId;

    check(query, {
      color: Match.Maybe(String), // Check expected fields
      groupId: Match.Maybe(String)
    });
    return Items.find(query);

  } else {

      this.ready(); // or: return [];

  }

});

#14

@palbergstrom : I hope you’re still feeling calm!

It’s probably correct to say that publications are basically simple, but the security around them needs careful thought. We didn’t even mention allow/deny rules.

You should also consider whether publications are the best approach for your use case(s). Many times, we don’t really need sub-second reactivity and Meteor methods are a better choice (you can still get optimistic UI with these).

Please seek clarification on anything you don’t fully understand. We’re here to help :slight_smile:


#15

Yes, back in zen mode. :slight_smile:

Thank you very much for your help. Much appreciated. The help one gets here is one of the beautiful things about meteor.


#16

I have it working and stand at the next problem.

Everyone in the group can see what colors others picked in the group view. Each member can click any of the groups colors and say how they feel about it. That is saved in the db with itemId (the color), groupId and userId.

This is working but I realize a problem. I want to mark each color that each member already has selected and planned to use the userId for that.

However, I ask the publication not to pass the userId field along so I can not check it on the client. If I pass it everyone who wants and know how will be able to get it, right? Not a good thing.

Is there any way around this? What about an aliasId instead of userId, is that the solution?