1

hope my first question here is not a stupid one. Lets say we want to build a chat application with meteor, with logedin and anonymous users. The chat should be filled like that:

var post = {
   userId: user._id,                 // is empty if anonymous user
   submitted: new Date().getTime(),
   text: chat_message
   });

var postId = Posts.insert(post);

The publication could looks like this to make sure that the userId is not transfered

Meteor.publish('getTheChat', function() {
  return Post.find({}, {fields: {userId: false});
});

But is there a way to add a field in the returned collection dynamically? The userId should not be published but a status like "Your_Post","another_logedin_user" or "an_anonymous_user". By having that, I could include some css, so the chat looks a little bit more like WhatsApp or iMessage.

The logic inside the publish method could be something like

if (userId == this.userId) {
   status = "Your_Post";
} else if (userId != null) {
   status = "another_logedin_user";
} else {
   status = "an_anonymous_user";
}

You see, the publication should include different values when called from different users. Is there a way in Meteor.publish?

Thanks for any insight or suggestions

Faceplant
  • 58
  • 5

3 Answers3

1

Thank you both for your ideas! But as I had to find out (just for my inward peace) how it is possible inside the publish method server sided, I came, with the help of David's link, to this solution -maybe it will help someone later:

Meteor.publish('getTheChat', function(postId) {
  var currentUserId = this.userId;
  var ownerUserId = Posts.findOne({'_id':postId}).userId;
  var findOptions = {};   // in my final coding these differ for 'chat room owners' and 'normal users'

  var transform = function(post) {
    if (currentUserId && post.userId == currentUserId) {
      post.userId = "posted by you";    
    } else if (post.userId == null) {
      post.userId = "anonym posted";
    } else if (post.userId == ownerUserId) {
      post.userId = "posted by owner";
    } else {
      post.userID = "posted by another loged in";

    return post;
  }; 

  var self = this;

  var handle = Posts.find(findOptions).observe({
    added: function (document) {
      self.added('posts', document._id, transform(document));
    },
    changed: function (newDocument, oldDocument) {
      self.changed('posts', document._id, transform(newDocument));
    },
    removed: function (oldDocument) {
      self.removed('posts', oldDocument._id);
    }
  });

  self.ready();

  self.onStop(function(){
    handle.stop();
  });

By having this I am finally able to overwrite values dynamically.

Faceplant
  • 58
  • 5
0

Sadly, this has been a huge issue for me too, and I am sorry to say, it is not technically possible to just add a field on the publisher's query and use it conveniently in your view. BUT, I have a solution that may work for you. It will also give you an idea of how complex it can become as soon as you want to keep some reactive data private in Meteor.

Server side:

First, create two different publishers: one for the current user's posts, one for all the others:

Meteor.publish('getTheOthersChat', function() {
  return Post.find({$ne:{userId: this.userId}}, {fields: {userId: false});
});

Meteor.publish('getTheOwnChat', function() {
  return Post.find({userId: this.userId});
});

Client/router side:

Then, subscribe to both of these: what this will do is include the post's userId only when it is the own user's id. If not, it'll be undefined.

Then, we still need to identify the case "anonymously posted" vs "posted by user". For this, you can add another field during the post creation, for example is_anonymous, which you then set to true or false depending on the case if the user is logged in or not. The check would then be:

if (userId) {
   status = "Your_Post";
} else if (is_anonymous === false) {
   status = "another_logedin_user";
} else {
   status = "an_anonymous_user";
}

This solution should work. I know, it is sad to have to come to this kind of means. It makes Meteor look clunky and impractical for tasks that should be dead easy. Such a shame for such a cool framework!

Community
  • 1
  • 1
SylvainB
  • 4,765
  • 2
  • 26
  • 39
0

It looks like you need to add a transform on your Posts collection. You can do this in your publish function (as seen here), but server-side transforms are computationally inefficient and tricky to write. Though they are necessary in cases where only the server could perform the action - e.g. signed URLs. In your case, I'd recommend a standard collection transform which is a filter applied after the documents are fetched.

Unfortunately, this kind of transform would require the userId on the client. I've never seen a case where simply publishing a id could cause a security issue. If you believe this is the case with your app, I'm very interested to know why. If you can overcome this restriction, keep reading...

You can read about transforms in the documentation on collections, and you can see an example on evented mind. Personally I like to use the collection-helpers package for this.

If you try collection-helpers, your transform could look like:

Posts.helpers({
  status: function() {
    if (this.userId === Meteor.userId()) {
      return 'Your_Post';
    } else if (this.userId != null) {
      return 'another_logedin_user';
    } else {
      return 'an_anonymous_user';
    }
  }
});

And then you could use it in your template like:

{{#each posts}}
  <p>{{text}} - <span class='status'>{{status}}</span></p>
{{/each}}

Of course, you can also use template helpers to achieve the same result but transforms are more easily reusable across your application.

Community
  • 1
  • 1
David Weldon
  • 63,632
  • 11
  • 148
  • 146
  • I'm actually very interested by your collection-helpers solution, but I'm doubtfull... on the package's readme it clearly states: "It's recommended to set up helpers to run on both server and client. This way your helpers can be accessed both server side and client side." I conclude that to be accessed in the template as in your example, the helper has to be defined on the client side, therefore the userId of every post has to be defined too... but the point is to keep that property hidden from the client. Am I missing something? – SylvainB Jul 24 '14 at 21:05
  • Oops, my bad, you stated that it wasn't possible right before. – SylvainB Jul 24 '14 at 21:13