Just hit an insanely frustrating roadblock in prototyping. I need to update and increment values an array inside of a collection. To do this, I'm accessing the collection using the MongoDB syntax like so:
Players.update({_id: Session.get('p1_id'), 'opponents.$.id' : Session.get('p2_id')},
{$inc: {
'games_played' : 1
}}
);
When this runs I get an error saying: Uncaught Error: Not permitted. Untrusted code may only update documents by ID. [403]
Now, I searched the hell out of this and I know that it came down in an update and why they only allow update by id's. But my problem is that I can't seem to find a way around it. I tried forcing it by adding this to if (Meteor.isServer):
Players.allow({
insert: function(userId, doc, fields, modifier){
return true;
},
update: function(userId, doc, fields, modifier){
return true;
},
remove: function(userId, doc, fields, modifier){
return true;
}
});
Nothing seems to work, and all the examples I find talk about using a Meteor method (not really sure what that is) or are doing userId validation (I dont have any users and don't want to add them right now). I'm just prototyping/sketching and I'm not concerned about security. How can I proceed here?
Here's how you can make this into a method:
Meteor.methods({
incrementGames: function (player1Id, player2Id) {
check(player1Id, Meteor.Collection.ObjectID);
check(player2Id, Meteor.Collection.ObjectID);
Players.update({
_id: player1Id,
'opponents.$.id': player2Id
}, {
$inc: {
'games_played' : 1
}
}, function(error, affectedDocs) {
if (error) {
throw new Meteor.Error(500, error.message);
} else {
return "Update Successful";
}
});
}
});
And on your client:
Meteor.call("incrementGames", Session.get('p1_id'), Session.get('p2_id'), function(error, affectedDocs) {
if (error) {
console.log(error.message);
} else {
// Do whatever
}
});
You just got the update wrong. The first parameter of the update method should be the id. the second parameter is an object containing the modifiers.
Players.update(playerId, {$inc:{games_played:1}});
Optionally you can add a callback containing error as the first parameter and response as the second parameter.
Related
I'm trying to add a new object to my mongodb document inside an array.
I have a NodeJS project using MongoDB which has a collection called "Teste" where i'm saving some random data.
Amongst that data is an array called "ArrayTeste". Currently it is only saving multiple strings because I named my inputs the same thing, so it automatically does it for me.
But I don't want to save each element as an individual string, i need to get these informations, group them in an object and then add it to the array.
Here is my code snippet in NodeJS:
ServicosModel.prototype.Teste = function (req, res) {
console.log("Metodo Teste");
var query =
{
$push:
{
ArrayTeste:
{
Dado1: req.body.Dado1,
Dado2: req.body.Dado2
}
}
}
console.log(query)
this._connection.open(function (errConn, mongoClient) {
console.log("Entrou open")
if (errConn) {
res.end("Deu erro" + errConn);
}
mongoClient.collection("teste", function (errColl, collection) {
if (errColl) {
res.end("Deu erro" + errColl);
}
console.log("Entrou collection")
collection.update(query, function (errUpdate, result) {
console.log("Entrou update")
if (errUpdate) {
res.end("Deu erro" + errUpdate);
} else {
res.end("Deu certo " + result);
}
});
});
});
}
And here is the mongoDB document structure:
{
"_id" : ObjectId("595bf19febbf3c14e481bc28"),
"id" : "2",
"Titulo" : "TItulo do negocio",
"ArrayTeste" : [
"dado1",
"dado2"
]
}
The "id" parameter is one created by me to easy the $elemMatch used in previous tests, so I don't have to search for the _id of the document.
When I run the code and insert stuff into the inputs, I am presented with this error:
(node:8712) UnhandledPromiseRejectionWarning: Unhandled promise
rejection (rejection id: 1): MongoError: document must be a valid
JavaScript object
and I have absolutely no idea of what is happening. the application simply freezes. I have searched through the posts and tried some stuff with $set and $addToSet, but the same error persists.
Any help is appreciated, thanks in advance!
To update a document you need two mandatory parameters:
criteria - to select documents to update
update - to modify selected documents
Here is the driver documentation: https://mongodb.github.io/node-mongodb-native/markdown-docs/insert.html#update.
The error says that collection.update expects an object (the second parameter) and it is receiving a function (your callback function).
To get your code working:
var select = {id: '2'}; // Here we are choosing the document
collection.update(select, query, function (errUpdate, result) {
if (errUpdate) {
res.end("Deu erro" + errUpdate);
} else {
res.end("Deu certo " + result);
}
});
I am trying to add a custom "isContributor" field through a method to Meteor.Users but somehow it is not adding the field. (I am talking even before any safety check of who can do this update).
On client side I have the following event:
Template.Articles.events({
'click #BeContributor': function() {
userId = Meteor.userId();
Meteor.call('setContributorState', userId);
}
});
and in server/main.js the following:
Meteor.methods({
setContributorState: function(userId) {
Meteor.users.update(userId, {
$set: {
isContributor: true
}
});
}
});
Somehow it does not add the field for my user. No console or server errors. I guess I missed something in term of right to add-up the field. Any ideas ?
Thanks in advance.
--- EDIT ---
Actually the method was working but I did not publish back the result so I could not check in MeteorToys that the field was updated. With the following publication it works:
Meteor.publish(null, function(){
return Meteor.users.find({_id: this.userId}, {fields: {isContributor: 1}});
});
found at Publishing custom Meteor.user() fields
If you are going to modify the logged-in user's contributor status, you do not need to pass any parameter. This is a security hole. Server has the information who is calling the request.
Meteor.methods({
setContributorState: function() {
Meteor.users.update({_id:this.userId}, {
$set: {
isContributor: true
}
});
}
});
I have a sample code that goes like this:
Client Helper:
getUsername: function (userId) {
Meteor.call("getUsername", userId, function (err, result) {
if(!err) {
Session.set("setUsername", result);
else {
console.log(err);
}
});
return Session.get("setUsername");
}
Server
Meteor.methods({
"getUsername": function (userId) {
var x = Meteor.users.find({_id: userId}, {fields: {username:1}}).fetch()[0];
return x.username;
}
});
The result of this code is an infinite loop of username passing to the client. Is there a way to stop the loop and pass only the data that is needed on the client? I believe the reactivity is causing the data to loop infinitely and I am not sure how to stop it. I tried using "reactive":false on my query in the server but it does not work.
If you want to access username everywhere in client templates (so thats why you put it into session), I would not set it in template helper. I would set it on startup and get username from session in template helpers (without calling server method)
If you need username just in one template, so you want to return its value from your template helper, do not put it into session, just return it in your server method callback.
Based on your sample code, I assume, you have a set of posts and you are retrieving user name based on user id for each post. Then instead of doing it this way, you should use publish composite package to publish related users as well.
Meteor.publishComposite('getPosts', function (postIds) {
return [{
find: function() {
return Posts.find({ _id: { $in: postIds }});
// you can also do -> return Posts.find();
// or -> return Posts.find({ /* or what ever your selector is to get the posts you need*/ });
},
children: [{
find: function(post) {
return Meteor.users.find({
id: post.userId //or the correct field in your post document to get user id
}, {
fields: {
"profile": 1
}
});
}
}}
}]
});
This way your publication will take care of publishing related users along with posts. You don't need to use methods and call them each time.
I'm looking for a way to determine if Meteor.user() is set in a function that can be called both from the server and client side, without raising an error when it is not.
In my specific case I use Meteor server's startup function to create some dummy data if none is set. Furthermore I use the Collection2-package's autoValue -functions to create some default attributes based on the currently logged in user's profile, if they are available.
So I have this in server-only code:
Meteor.startup(function() {
if (Tags.find().fetch().length === 0) {
Tags.insert({name: "Default tag"});
}
});
And in Tags-collection's schema:
creatorName: {
type: String,
optional: true,
autoValue: function() {
if (Meteor.user() && Meteor.user().profile.name)
return Meteor.user().profile.name;
return undefined;
}
}
Now when starting the server, if no tags exist, an error is thrown: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
So in other words calling Meteor.user() on the server startup throws an error instead of returning undefined or null or something. Is there a way to determine whether it will do so prior to calling it?
I cannot solve this simply by wrapping the call with if (Meteor.isServer) within the autoValue function, as the autoValue functions are normally called from server side even when invoked by the user, and in these cases everything in my code works fine.
Note that this is related to How to get Meteor.user() to return on the server side?, but that does not address checking if Meteor.user() is available in cases where calling it might or might not result in an error.
On the server, Meteor.users can only be invoked within the context of a method. So it makes sense that it won't work in Meteor.startup. The warning message is, unfortunately, not very helpful. You have two options:
try/catch
You can modify your autoValue to catch the error if it's called from the wrong context:
autoValue: function() {
try {
var name = Meteor.user().profile.name;
return name;
} catch (_error) {
return undefined;
}
}
I suppose this makes sense if undefined is an acceptable name in your dummy data.
Skip generating automatic values
Because you know this autoValue will always fail (and even if it didn't, it won't add a useful value), you could skip generating automatic values for those inserts. If you need a real name for the creator, you could pick a random value from your existing database (assuming you had already populated some users).
Been stuck with this for two days, this is what finally got mine working:
Solution: Use a server-side session to get the userId to prevent
"Meteor.userId can only be invoked in method calls. Use this.userId in publish functions."
error since using this.userId returns null.
lib/schemas/schema_doc.js
//automatically appended to other schemas to prevent repetition
Schemas.Doc = new SimpleSchema({
createdBy: {
type: String,
autoValue: function () {
var userId = '';
try {
userId = Meteor.userId();
} catch (error) {
if (is.existy(ServerSession.get('documentOwner'))) {
userId = ServerSession.get('documentOwner');
} else {
userId = 'undefined';
}
}
if (this.isInsert) {
return userId;
} else if (this.isUpsert) {
return {$setOnInsert: userId};
} else {
this.unset();
}
},
denyUpdate: true
},
// Force value to be current date (on server) upon insert
// and prevent updates thereafter.
createdAt: {
type: Date,
autoValue: function () {
if (this.isInsert) {
return new Date;
} else if (this.isUpsert) {
return {$setOnInsert: new Date};
} else {
this.unset();
}
},
denyUpdate: true
},
//other fields here...
});
server/methods.js
Meteor.methods({
createPlant: function () {
ServerSession.set('documentOwner', documentOwner);
var insertFieldOptions = {
'name' : name,
'type' : type
};
Plants.insert(insertFieldOptions);
},
//other methods here...
});
Note that I'm using the ff:
https://github.com/matteodem/meteor-server-session/ (for
ServerSession)
http://arasatasaygin.github.io/is.js/ (for is.existy)
In JS, Is there a better way of doing the following:
I'm finding a user, and then checking password, then I wish to update the same users' document.
Can I leverage the already open document (var doc) for updating? Or do as the code below does and re-search for name:name when updating.
user_collection.findOne({ name:name }, function(err, doc) {
if(err)
throw err;
if(doc) {
// verify doc.password etc
user_collection.update({ name:name }, {$set: { last_joined:last_joined }}, { upsert:true }, function(err, doc) {
if(err) {
// log error
}
});
}
});
Yes - use the save method.
The document you have in doc is an in-memory copy of the record in the database. If you want to modify it then save it to the database, you need to either use the update method as you do, or use save(modified_doc).
Note: as freakish said, you probably should use user_collection.update({ _id: doc._id }, ...) instead of searching for name again, since it may not be unique.