Programmatically modify existing mongo document - node.js

Resolved, the issue was with the PUT request , I had to specify the header properly and go on with one at a time.
curl -X PUT -H "Accept: application/json" -d "prenom=Maxwell" localhost:3000/contact/51df5cec5e88a2bbce7fac05
I'm trying to modify a mongodb document via coffeescript in a node.js application I'm currently working on and when I try :
curl -X PUT -d '{"nom": "House", "prenom": "Maxwell"}' localhost:3000/contact/51ddb907ae3267d6154a3e64
on this
{
"_id": "51ddb907ae3267d6154a3e64",
"archive": 1,
"nom": "Bir",
"prenom": "Sim"
}
The ID and the routes are correct, so I'm pretty sure the error lies in the javascript logic but I can't seem to grasp the right angle to work it. Here is the defective code :
exports.modifyContact = (req, res) ->
db.collection "data-center", (erreur, collection) ->
collection.update { _id: new mongo.BSONPure.ObjectID(req.params.id) }, { $set: req.body } , (erreur, resultat) ->
res.send resultat
res.end()
and the result is
{
"_id" : ObjectId("51df4ad424f6d9207cc3e2d5"),
"nom" : "Bir",
"nom": "House",
"prenom": "Maxwell" : "",
"prenom" : "Sim"
}
I can't seem to find an effective way to set missing value and modify value already in there. What is wrong ?

Give this a try instead:
exports.modify = (req, res) ->
fields = if (typeof req.body == 'string') then JSON.parse(req.body) else req.body
db.collection "data-center", (erreur, collection) ->
// check erreur here
collection.update { _id: new mongo.BSONPure.ObjectID(req.params.id) }, { $set: fields }, (erreur, resultat) ->
// check erreur here
res.send resultat
res.end()
It is important to note that inserting data directly from req.body without some sort of validation/content checking is not secure and should be avoided (even if it works as expected).

If you want to just "merge" a set of fields into an existing object you do it this way
test:PRIMARY> db.t2.insert({b:1})
test:PRIMARY> db.t2.update({b:1}, {$set: {a:1, c:2}})
test:PRIMARY> db.t2.find({b:1})
{ "_id" : ObjectId("520a3f10e2d66ef50d3b042b"), "a" : 1, "b" : 1, "c" : 2 }
Ah yeah and for the json issue, you cannot use the "original" object to return as json. You need to perform a findOne to retrieve the updated object from mongodb or change to using a findAndModify with the parameter new to get the changed object in a single operation. The existing object might well have have circular references.

Related

How to Update only passed value in mongodb using mongoose?

I have built an API that updates records in MongoDB using mongoose, but currently what happening is if I am passing only 4 field values in the JSON file of postman and try to update then all the values are updating with value null except that 4 fields which I had passed in JSON so can anyone help me how I can pass dynamic field and value that update only passed values of collection not all the fields of collection.
Passes JSON :
{
"preferance_id" : "60fe9ba1766d10d65c64083c",
"is_active": true,
"price_blur":true,
"affiliate_commission":27,
"language_code": "en"
}
Update call which I have passed in node js
transaction.update(PreferencesMasterName,
{ _id: new mongoose.Types.ObjectId(preferance_id) }, {
subscriptin_vat : subscriptin_vat,
popular_normal : popular_normal,
popular_crawled : popular_crawled,
price_blur : price_blur,
blur_rule : blur_rule,
affiliate_commission : affiliate_commission,
red_lock : red_lock,
automatic_dummy_price : automatic_dummy_price,
...
is_active: is_active
})
I want to pass dynamic field and values here instead of this because due to this other values are set will null value. So, can anyone have an idea how to do this?
You can do something like this:
const data = res.body; // should be an object that needs to updated
transaction.update({_id: PreferanceMasterName._id}, data, {new: true }, ( error, obj ) => {
if( error ) {
console.error( JSON.stringify( error ) );
}
console.log( obj );
});
In certain cases new doesn't work, you can use : { returnOriginal: false };
for more details, you can check this thread there are multiple ways you can do this.
Please check update how to use it.

How to use $inc operator for variables in MongoDB using Node.JS

I am trying to build a "number of visitors" collection in mongoDb using Node.JS backend of my website. The frontend sends the following info to Node.JS backend as JSON.
isUniqueVisitor - 1 if yes, 0 if no
country - standard country code - "JP", "IN", "UK", etc
My database looks like following
{
"today": 2019-06-07,
"uniqueVisitors": {
"count": 230,
"countries": {
"JP": 102,
"IN": 88,
"UK": 30
}
}
}
It works well if I use $inc with fixed values
Eg. $inc: {count: 1} // for string/integers keys
Eg. $inc: {"uniqueVisitors.count": 1} // inside quotes to access key of a JSON
Main issue:
I am not able to access a document name using variable.
Eg. $inc: {`uniqueVisitors.countries[${req.body.country}]`}
This creates an error as backticks can't be used for Mongo.
I tried with
Eg. $inc: {uniqueVisitors["countries"][req.body.country]}
But even this creates error.
I followed the web and found that mongo $set using variables can be realized by passing the required JSON directly to $set. Hence I resorted to code it the following way.
mongoClient.connect(mongoURL, async function (err, db) {
if (err) throw err;
console.log("Database connected");
// Identifying my document with today's date
var myQuery = {
date: getTodayDate()
};
// Defining the JSON to be passed to uniqueVisitors $inc
var uniqueVisitorsInc = {
"uniqueVisitors": {
"count": 0,
"countries": {}
}
};
// Populating the JSON to be passed to uniqueVisitors $inc => essentially asking to increase count by 1 and increase that country's count by 1
uniqueVisitorsInc["uniqueVisitors"]["count"] = 1;
uniqueVisitorsInc["uniqueVisitors"]["countries"][myData.country] = 1;
var newValues = {
$inc: uniqueVisitorsInc
};
await db.collection("visitorStats").update(myQuery, newValues, {upsert: true});
db.close();
});
The above method worked well on editor but threw the following runtime error:
$inc requires numerical values
Basically asking me to pass values to $inc in {var1: 1, var2: 5} pattern.
Please help me bypass this weird situation.
I know I can do a two step process where I read the values first, increment in variable and $set it in Mongo.
But does anyone know how to overcome this situation using $inc?
If this update were hardcoded to update "JP" only, it'd need to look like:
$inc: { "uniqueVisitors.country.JP": 1 }
So you were almost there with the backtick method but change the syntax a bit and keep the : 1 part like so:
$inc: { [`uniqueVisitors.country.${req.body.country}`]: 1 }

Updating Mongo DB entries by params

The goal of this program is to add a property called 'userResponse' to the problem objects that reflects the input from the user. The problem object in Mongo DB is held in an array of objects, called 'problems', that is held in an object called 'session':
{
"_id" : ObjectId("59df5ee7adb378237377dbb4"),
"updatedAt" : ISODate("2017-10-12T12:24:07.269Z"),
"createdAt" : ISODate("2017-10-12T12:24:07.269Z"),
"userId" : "59df5edbadb378237377dbb3",
"problems" : [
{
"correctResponse" : 23,
"problem" : "20 + 3",
"secondTerm" : 3,
"firstTerm" : 20
} ]
}
Here is the logic for the endpoint that I have been using:
router.patch( '/session/:sessionId/:index', passport.authenticate( 'jwt', { session: false } ), ( req, res ) => {
Session.findOne({_id: req.params.sessionId})
.then( (item)=>{
item.problems[req.params.index].userResponse = req.body.userResponse;
Session.update({_id: req.params.sessionId}, item).then( (updated)=>{
res.json(updated.problems[req.params.index]);
});
})
})
I looked at some other examples ( one two ) but they do not seem relavant since this is a patch to single elements of the array as identified by the params.
This approach works, in that it successfully updates the database with the new properties on the objects, but during the execution the server logs this error:
(node:10955) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property '0' of undefined
My understanding is that this means the object can not be found in the database at some point, but since the database does include the new properties it must be resolved later. Is that corrrect?
Additionally it seems brittle to have two chained promises with no catch statements...
Can anyone offer suggestions on how to improve this process? This logic does successfully update the database, with errors. Is there a better way?
Thank you for your time.
Well i can see multiple issues in the code that can cause your problem
1- From the structure of your database session document _id is of type ObjectId and when you try to find, you are finding by just the id which is probably string you will need to use ObjectId(req.params.sessionId)
2- from the error, i can tell that the value of item does not contain an array of problems ... so items.problems is undefined so undefined[0] is an error.
3- You don't need to find session document item twice to update the array, i updated the code to update the userResponse value in array by using a single update operation.
4- for a better error handling and code maintainability you need to handle promise rejection so you need to handle catch as well as you are doing with then
So from the above comments i can update your code to be
var mongoose = require('mongoose');
router.patch( '/session/:sessionId/:index', passport.authenticate( 'jwt', { session: false } ), ( req, res ) => {
var index = req.params.index;
Session.update({_id: mongoose.Types.ObjectId(req.params.sessionId) }, {$set : {"problems." + index + ".userResponse" : req.body.userResponse } )
.then( (updated)=>{
console.log(updated);
res.json(updated.problems[req.params.index]);
})
.catch((err) => {
console.log(err.message);
res.json({status: "error" , message:err.message});
});
})

how to remove object in array by index mongodb / mongoose [duplicate]

In the following example, assume the document is in the db.people collection.
How to remove the 3rd element of the interests array by it's index?
{
"_id" : ObjectId("4d1cb5de451600000000497a"),
"name" : "dannie",
"interests" : [
"guitar",
"programming",
"gadgets",
"reading"
]
}
This is my current solution:
var interests = db.people.findOne({"name":"dannie"}).interests;
interests.splice(2,1)
db.people.update({"name":"dannie"}, {"$set" : {"interests" : interests}});
Is there a more direct way?
There is no straight way of pulling/removing by array index. In fact, this is an open issue http://jira.mongodb.org/browse/SERVER-1014 , you may vote for it.
The workaround is using $unset and then $pull:
db.lists.update({}, {$unset : {"interests.3" : 1 }})
db.lists.update({}, {$pull : {"interests" : null}})
Update: as mentioned in some of the comments this approach is not atomic and can cause some race conditions if other clients read and/or write between the two operations. If we need the operation to be atomic, we could:
Read the document from the database
Update the document and remove the item in the array
Replace the document in the database. To ensure the document has not changed since we read it, we can use the update if current pattern described in the mongo docs
You can use $pull modifier of update operation for removing a particular element in an array. In case you provided a query will look like this:
db.people.update({"name":"dannie"}, {'$pull': {"interests": "guitar"}})
Also, you may consider using $pullAll for removing all occurrences. More about this on the official documentation page - http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull
This doesn't use index as a criteria for removing an element, but still might help in cases similar to yours. IMO, using indexes for addressing elements inside an array is not very reliable since mongodb isn't consistent on an elements order as fas as I know.
in Mongodb 4.2 you can do this:
db.example.update({}, [
{$set: {field: {
$concatArrays: [
{$slice: ["$field", P]},
{$slice: ["$field", {$add: [1, P]}, {$size: "$field"}]}
]
}}}
]);
P is the index of element you want to remove from array.
If you want to remove from P till end:
db.example.update({}, [
{ $set: { field: { $slice: ["$field", 1] } } },
]);
Starting in Mongo 4.4, the $function aggregation operator allows applying a custom javascript function to implement behaviour not supported by the MongoDB Query Language.
For instance, in order to update an array by removing an element at a given index:
// { "name": "dannie", "interests": ["guitar", "programming", "gadgets", "reading"] }
db.collection.update(
{ "name": "dannie" },
[{ $set:
{ "interests":
{ $function: {
body: function(interests) { interests.splice(2, 1); return interests; },
args: ["$interests"],
lang: "js"
}}
}
}]
)
// { "name": "dannie", "interests": ["guitar", "programming", "reading"] }
$function takes 3 parameters:
body, which is the function to apply, whose parameter is the array to modify. The function here simply consists in using splice to remove 1 element at index 2.
args, which contains the fields from the record that the body function takes as parameter. In our case "$interests".
lang, which is the language in which the body function is written. Only js is currently available.
Rather than using the unset (as in the accepted answer), I solve this by setting the field to a unique value (i.e. not NULL) and then immediately pulling that value. A little safer from an asynch perspective. Here is the code:
var update = {};
var key = "ToBePulled_"+ new Date().toString();
update['feedback.'+index] = key;
Venues.update(venueId, {$set: update});
return Venues.update(venueId, {$pull: {feedback: key}});
Hopefully mongo will address this, perhaps by extending the $position modifier to support $pull as well as $push.
I would recommend using a GUID (I tend to use ObjectID) field, or an auto-incrementing field for each sub-document in the array.
With this GUID it is easy to issue a $pull and be sure that the correct one will be pulled. Same goes for other array operations.
For people who are searching an answer using mongoose with nodejs. This is how I do it.
exports.deletePregunta = function (req, res) {
let codTest = req.params.tCodigo;
let indexPregunta = req.body.pregunta; // the index that come from frontend
let inPregunta = `tPreguntas.0.pregunta.${indexPregunta}`; // my field in my db
let inOpciones = `tPreguntas.0.opciones.${indexPregunta}`; // my other field in my db
let inTipo = `tPreguntas.0.tipo.${indexPregunta}`; // my other field in my db
Test.findOneAndUpdate({ tCodigo: codTest },
{
'$unset': {
[inPregunta]: 1, // put the field with []
[inOpciones]: 1,
[inTipo]: 1
}
}).then(()=>{
Test.findOneAndUpdate({ tCodigo: codTest }, {
'$pull': {
'tPreguntas.0.pregunta': null,
'tPreguntas.0.opciones': null,
'tPreguntas.0.tipo': null
}
}).then(testModificado => {
if (!testModificado) {
res.status(404).send({ accion: 'deletePregunta', message: 'No se ha podido borrar esa pregunta ' });
} else {
res.status(200).send({ accion: 'deletePregunta', message: 'Pregunta borrada correctamente' });
}
})}).catch(err => { res.status(500).send({ accion: 'deletePregunta', message: 'error en la base de datos ' + err }); });
}
I can rewrite this answer if it dont understand very well, but I think is okay.
Hope this help you, I lost a lot of time facing this issue.
It is little bit late but some may find it useful who are using robo3t-
db.getCollection('people').update(
{"name":"dannie"},
{ $pull:
{
interests: "guitar" // you can change value to
}
},
{ multi: true }
);
If you have values something like -
property: [
{
"key" : "key1",
"value" : "value 1"
},
{
"key" : "key2",
"value" : "value 2"
},
{
"key" : "key3",
"value" : "value 3"
}
]
and you want to delete a record where the key is key3 then you can use something -
db.getCollection('people').update(
{"name":"dannie"},
{ $pull:
{
property: { key: "key3"} // you can change value to
}
},
{ multi: true }
);
The same goes for the nested property.
this can be done using $pop operator,
db.getCollection('collection_name').updateOne( {}, {$pop: {"path_to_array_object":1}})

mongoose-encryption and updating objects

I know the mongoose-encryption doc states:
update will work fine on unencrypted and unauthenticated fields, but will not work correctly if encrypted or authenticated fields are involved.
And I've observed that when I use the mongoose create method that my fields are encrypted into the _ct field. However if I then use findByIdAndUpdate to update my object I see the fields are created in plain text (as output from mongodb console via find command).
From save
> db.tenants.find().pretty()
{
"_id" : ObjectId("554b7f8e7806c204e0c7589e"),
"_ac" : BinData(0,"YdJjOUJhzDWuDE5oBU4SH33O4qM2hbotQTsF6NzDnx4hWyJfaWQiLCJfY3QiXQ=="),
"_ct" : BinData(0,"YaU4z/UY3djGCKBcgMaNIFHeNp8NJ9Woyh9ahff0hRas4WD80V80JE2B8tRLUs0Qd9B7IIzHsq6O4pYub5VKJ1PIQA+/dbStZpOH/KfvPoDC6DzR5JdoAu+feU7HyFnFCMY81RZeJF5BKJylhY1+mG4="),
"__v" : 0
}
After findByIdAndUpdate
> db.tenants.find().pretty()
{
"_id" : ObjectId("554b7f8e7806c204e0c7589e"),
"_ac" : BinData(0,"YdJjOUJhzDWuDE5oBU4SH33O4qM2hbotQTsF6NzDnx4hWyJfaWQiLCJfY3QiXQ=="),
"_ct" : BinData(0,"YaU4z/UY3djGCKBcgMaNIFHeNp8NJ9Woyh9ahff0hRas4WD80V80JE2B8tRLUs0Qd9B7IIzHsq6O4pYub5VKJ1PIQA+/dbStZpOH/KfvPoDC6DzR5JdoAu+feU7HyFnFCMY81RZeJF5BKJylhY1+mG4="),
"__v" : 0,
"userId" : ObjectId("55268f43cbfc87be221cd611"),
"social" : "123-45-6789",
"last" : "bar",
"first" : "foo"
}
Is there a recommended strategy for updating objects and maintaining the encryption with mongoose-encryption?
As you quoted, the documentation for mongoose-encryption clearly tells that it does not work for update.
https://github.com/joegoldbeck/mongoose-encryption
Mongoose update hook is little tricky as well.
What you can do potentially is model your collection in such a way that fields which needs to be encrypted are a separate collection altogether and in the paren collection just link them via ids.
Person = {
_id: <ObjectId>
name: Blah
..
..
documents: [
{ 'doc_id': <ObjectId1> },
{ 'doc_id': <ObjectId2> },
]
}
Documents = [
{
"_id" : <ObjectId1>,
"_ac" : BinData(0,"YdJjOUJhzDWuDE5oBU4SH33O4qM2hbotQTsF6NzDnx4hWyJfaWQiLCJfY3QiXQ=="),
"_ct" : BinData(0,"YaU4z/UY3djGCKBcgMaNIFHeNp8NJ9Woyh9ahff0hRas4WD80V80JE2B8tRLUs0Qd9B7IIzHsq6O4pYub5VKJ1PIQA+/dbStZpOH/KfvPoDC6DzR5JdoAu+feU7HyFnFCMY81RZeJF5BKJylhY1+mG4="),
"__v" : 0
}
...
...
]
This will increase code reuse as well.
I have implemented an strategy that i don´t think it is most efficient but it works.
I need to have all my data in database encrypted so i can´t use the above approach.
What i did is to create an update function that finds the document i want to modify, then i construct a new schema object and assing the _id of the found document to the new object.
Then i delete the original document and after that save the new object wich has the original _id. The only problem i found is that mongoose throw an error because duplicated _id that is printed in the console but it still works and _id aren´t duplicated.
I have tried replacing the_id and traking the document with another property but it still throw that error, anyway data is stored as expected.
exports.update= (req, res, next) => {
Solucion.findOne({_id: req.params.id})
.then(document => {
if (!document) {
res.status(404).json({
message: notFoundMessage,
data: null,
error: null
})
} else {
const solucion = new Solucion({
_id: document._id,
identificacion: document.identificacion,
informacion: document.informacion,
estado: req.body
})
Solucion.deleteOne({_id: document._id})
.then(() => {return solucion.save()})
.then(result=> {
return res.status(201).json({
message: editedSavedMessage,
data: result,
error: null
});
})
.catch(err => {
errorHandler.errorHandler(err, res);
})
}
})
};
UPDATE 29/07/2020
I have found that if you use the save method using the same _id, data is stored encrypted but Mongo creates your schema structure but with all values set to null.
Beyond that it seems to work as expected as data is not visible in DB.

Resources