Meteor collection.update permisions - collections

Hi i dont understand why is this not working?
Notifications.update({'userId':Meteor.userId(), 'notifyUserId':notifyFriendId}, {$set: {read: 1}});
I have update allow method as well
Notifications = new Meteor.Collection('Notifications');
Notifications.allow({
update: function(userId, doc) {
return true;
}
});
Error appear:
Uncaught Error: Not permitted. Untrusted code may only update documents by ID. [403]

To update a collection you can only use the document's _id. So you need to query for it first
var docid = Notifications.findOne({'userId':Meteor.userId(), 'notifyUserId':notifyFriendId});
Notifications.update({_id:docid._id}, {$set: {read: 1}});
This is only for code that runs on the client. On the server you can run the code as you had it.

Just to update the above answer:
var documentIdentifiers = _.pluck(Documents.find({ param: 'yourParam'}, { fields: { _id: 1 }}).fetch(), '_id');
for (var i = 0; i < documentIdentifiers.length; i++)
Documents.update(documentIdentifiers[i], { $do: whatever });
This is what you do if you need to update multiple fields. The underscore pluck method, used in tandem with a field specifier, makes sure that data isn't being trucked around unnecessarily.
All my best,
Sam

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');

Different userId get same document with server publish this.userId

With autopublish package is removed, While this Meteor code is running, a different userId has been confirmed on 2 different browsers consoles Meteor.userId();
But when a string is typed in the inputText of one of them, and a collection.insert is done, the other shows the same string.
I thought that this.userId was good enough for the server to publish only the documents that belongs to each of the different clients simultaneously.
Why is this happening and how to fix it? Thanks
Server
Meteor.publish('displayCol', function () {
return DisplayCol.find({userId: this.userId});
});
DisplayCol.before.insert(function (userId, doc) {
doc.userId = userId;
});
Client
Template.index.helpers({
taskInputs: function () {
var ready = Meteor.subscribe('displayCol').ready();
var data = DisplayCol.find({});
return {items: data, ready: ready};
}
});
Do you have autopublish still installed? If so, both clients will get everything automatically. Remove it with 'meteor remove autopublish'
You can also add the {userId: Meteor.userId()} condition on the client side.

Meteor collections. After altering schema, how to check for invalid documents? How to fix?

I'm using SimpleSchema and Collection2.
Before pushing a new version of my app, I'd like to check if any documents would be invalid with the new schema. Is there an automated way to do this?
Or a command line utility?
How do you release new versions with altered schemas?
Thanks!
Mike
From your meteor app on either the client or server (assuming you have access to all the documents you want to check on the client):
MyCollection.find().forEach(function(doc){
check(doc,MyCollectionSchema);
});
You'll probably also want to log the doc and its _id on failures so you can go fix them.
Both SimpleSchema and Collection2 have validating methods.
For SimpleSchema, here are their example codes: https://github.com/aldeed/meteor-simple-schema#validating-data
For Collection2, check the sections starting from Validation Contexts https://github.com/aldeed/meteor-collection2#validation-contexts
I followed #michel's advice and made this server-side route for checking collections.
This is pretty dangerous, because it's cleaning and saving your data. That means it's erasing fields. Probably you didn't want those fields which is why you removed them from your schema, but also, it may be a bug. So caveat emptor cowboy. Obviously, this shouldn't be on a production server.
They can be checked using an url like this:
http://127.0.0.1:4000/v/users
Response:
{
"ok": 1
}
The route:
skipUpdatedAt = true;
Router.route('validate', {
path: '/v/:collection',
where: 'server',
action: function() {
var res = this.response;
var collections = {
users: Meteor.users,
puppies: Puppies,
unicorns: Unicorns,
rainbows: Rainbows,
};
var c = collections[this.params.collection];
if (!c) {
res.statusCode = 404;
return res.end('not found');
}
var s = c.simpleSchema();
var ret = { ok: 0 };
c.find().forEach(function(doc){
var id = doc._id;
try {
s.clean(doc);
check(doc,s);
c.update(id, doc, {validate: false});
ret.ok += 1;
} catch (e) {
ret[id] = {
doc: doc,
error: e.toString(),
};
console.log('=============ERROR');
console.log(doc);
console.log('-------------');
console.log(e);
}
});
res.statusCode = 200;
if (Object.keys(ret).length) res.statusCode = 500;
res.end(JSON.stringify(ret, null, 2));
}
});

Why is this collection empty? (Collection.find() is successful om server)

Ok this is what I've got.
The collection called Posts has content and I want to publish this under the name Merchs, the find() in the publish-function finds data but that is not shared to the client where Merchs is always empty.
//shared
Merchs = new Meteor.Collection('merchs');
// Posts has data I want to publish as "Merchs"
this.Posts = new Meteor.Collection('posts');
//server
Merchs.allow({
insert: function(userId, doc) {
return true;
},
update: function(userId, doc, fields, modifier) {
return true;
},
remove: function(userId, doc) {
return true;
}
});
Meteor.publish('merchs', function(data) {
return Posts.find();
});
//client
Deps.autorun( function() {
Session.get('selectedCategories');
subs.subscribe('merchs');
});
When creating your collection, the name in parentheses should be the name of the Mongo collection.
Merchs = new Meteor.Collection('merchs');
Should be:
Merchs = new Mongo.Collection('Posts');
That is, unless you already have a Posts variable defined in code that you didn't show. If you've already defined Posts and you're just looking to make another subscription to the same collection then you don't need this line at all:
Merchs = new Meteor.Collection('merchs');
You also don't need your allow() method (you can just use the one defined for Posts). All you need is the publish() method that you defined.
On the client side you also need:
Meteor.subscribe('merchs');
Also note the use of Mongo.Collection instead of Meteor.Collection which was renamed in Meteor 0.9.1.
You might want to read this excellent answer regarding publish/subscribe: https://stackoverflow.com/a/21853298/4665459

Most efficient way to ensure user owns document on update?

I'm using Meteor methods to update documents so I can share them easier and have more control. However i've ran into a problem with checking ownership.
How should I check to make sure the user calling the update method is the owner of the document? Currently i'm grabbing the document first then running the update.
Is there a better pattern to accomplish this?
Meteor.methods({
'Listing.update': function(docId, data) {
var doc = db.listings.findOne({_id: docId}) || {};
if (doc.userId !== this.userId) {
throw new Meteor.Error(504, "You don't own post");
}
// ensure data is the type we expect
check(data, {
title: String,
desc: String
});
return db.listings.update(docId, {$set: data});
}
});
You don't need the additional db call to fetch the original doc, just make the userId an additional criteria in the update selector. If no doc exists with the correct _id and userId no update will be done. update returns the number of docs updated so it will return 1 on success and 0 on failure.
like this:
'Listing.update': function(docId, data) {
var self = this;
check(data, {
title: String,
desc: String
});
if ( ! self.userId )
throw new Meteor.Error(500, 'Must be logged in to update listing');
res = db.listings.update({_id: docId, userId: self.userId}, {$set: data});
if ( res === 0 )
throw new Meteor.Error( 504, "You do not own a post with that id" );
return res;
}
Also, if you use findOne to check a document's existence, use the fields option to limit what you return from the db. Usually just {fields: {_id:1}}.

Resources