MongoDB findOneAndUpdate returnDocument not working - node.js

I have following code that returns findOneAndUpdate response. But returnDocument: "after" not working properly when I'm using it with arrayFilters. If I remove arrayFilters it works fine. What causes this or is there a better way to get modified document in response?
const addStartingCards = async (lobbyId) => {
const res = await db.findOneAndUpdate(
{ lobbyId: lobbyId },
{
$push: {
"seats.$[el].cards": {
$each: [Math.floor(Math.random() * 52), Math.floor(Math.random() * 52)]
}
}
},
{
arrayFilters: [
{
"el.status": true,
}
]
},
{ returnOriginal: false, returnDocument: "after" });
return (res.value);
};

As #rickhg12hs suggested in comments, this is the correct format to use them together;
{arrayFilters: [{"el.status": true}], returnDocument: "after" }

Related

mongoose query multiple operations in one request

I'm trying to use the $set, $addToSet and $inc at the same time for my report of sales and
tbh I'm not even sure if I did the right approach since it's not working.
once I send the request, the console gives me the error 404 but when I check the req.body the data was correct. so I was wondering if the problem is my query on mongoose because this was the first time I use multiple operations on mongoose query
export const report_of_sales = async (req, res) => {
const { id } = req.params;
console.log(req.body);
try {
if (!mongoose.Types.ObjectId.isValid(id)) return res.status(404).json({ message: 'Invalid ID' });
let i;
for (i = 0; i < req.body.sales_report.length; i++) {
await OwnerModels.findByIdAndUpdate(id, {
$inc: {
total_clients: req.body.total_clients,
total_product_sold: req.body.sales_report[i].qty,
sales_revenue: req.body.sales_report[i].amount
},
$set: {
"months.$[s].month_digit": req.body.months[i].month_digit,
"months.$[s].targetsales": req.body.months[i].targetsales,
"months.$[s].sales": req.body.months[i].sales,
},
$addToSet: {
sales_report: {
$each: [{
identifier: req.body.sales_report[i].identifier,
product_name: req.body.sales_report[i].product_name,
generic_name: req.body.sales_report[i].generic_name,
description: req.body.sales_report[i].description,
qty: req.body.sales_report[i].qty,
amount: req.body.sales_report[i].amount,
profit: req.body.sales_report[i].profit
}]
}
}
}, {
arrayFilters: [
{
"s.month_digit": req.body.months[i].month_digit
}
],
returnDocument: 'after',
safe: true,
}, { new: true, upsert: true })
}
} catch (error) {
res.status(404).json(error);
}
}
Well, you are looking at the body, but you are actually using query parameter named id. This is probably undefined, which leads to ObjectId.isValid(id) returning false.
You should decide on whether to pass this data as a query param or in the request body and adjust your code accordingly.

MongoDB : Push new item to the top of the array via findOneandUpdate

I am a nodejs and mongo newbie. I want to push an item into the array which is nested into the document via findOneandUpdate, but would like to add the new item as the first element (top of the array). Here is my structure :
This works fine for adding item to the end of the array:
const newNote = await NotesBlock.findOneAndUpdate({ blockId: req.params.blockId }, { $push: { notes: { noteTitle: req.body.noteTitle, noteDetail: req.body.noteDetail } } }, { upsert: true, new: true })
As I know we can't use unshift with mongodb, but $each and $position can be used for this purpose. So, I have tried this :
const newNote = await NotesBlock.findOneAndUpdate({ blockId: req.params.blockId }, { $push: { notes: { $each:[noteTitle: req.body.noteTitle, noteDetail: req.body.noteDetail], $position: 0 } } }, { upsert: true, new: true })
But unfortunately, this gives me an error for the syntax.
I can't figure out how to avoid this and make it work. Or is this the wrong approach and there is any other way to achieve?
Thanks for the help
I have figured out the issue. Following was close enough, except the curly brackets that should be inside the square brackets. So I had to change this :
const newNote = await NotesBlock.findOneAndUpdate({ blockId: req.params.blockId }, { $push: { notes: { $each:[noteTitle: req.body.noteTitle, noteDetail: req.body.noteDetail], $position: 0 } } }, { upsert: true, new: true })
To this :
const newNote = await NotesBlock.findOneAndUpdate({ blockId: req.params.blockId }, { $push: { notes: { $each: [{ noteTitle: req.body.noteTitle, noteDetail: req.body.noteDetail }], $position: 0 } } }, { upsert: true, new: true })
And it works as expected now.

Node: Updating DB with PUT not working for MongoDB

My GET returns an array of 2 simple objects from the DB:
[
{
"_id": "60491b5741893d23216d2de3",
"text": "test`",
"score": 19,
"createdAt": "2021-03-10T19:17:43.809Z"
},
{
"_id": "604947c7b3a7ed28c43c05b7",
"text": "HELLO",
"score": 22,
"createdAt": "2021-03-10T22:27:19.739Z"
}
]
In Postman, I am trying to do a PUT to /604947c7b3a7ed28c43c05b7 to update that post. In the body, I am sending:
{
"text": "Updated post test",
"score": 100
}
and my node route looks like this:
router.put('/:id', async(req,res) => {
const posts = await loadPostsCollection();
const post = {};
if (req.body.text) post.text = req.body.text;
if (req.body.score) post.score = req.body.score;
await posts.findOneAndUpdate(
{ _id: req.params.id },
{ $set: post },
{ new: true }
);
res.status(200).send();
})
I am getting a success message back but when I do a GET to see the array, the value hasn't changed for that post.
I am assuming you're using the native mongodb node driver. In mongoose findOneAndUpdate() will not actually execute the query unless a callback function is passed. I don't know this with full certainly, but it sounds like the native driver works the same way. So you would have to rewrite your code like so:
posts.findOneAndUpdate(
{ _id: req.params.id },
{ $set: post },
{ new: true },
result => {console.log(result)}
);
Another way to do it is by appending .then() to the end, because according to the docs, it return a promise if no callback is passed. So here is how I would do it:
await posts.findOneAndUpdate(
{ _id: req.params.id },
{ $set: post },
{ new: true }
).then(r => r);

Getting error to integrate MongoDB transaction and Update query where I use arrayFilters and opts both

I have used transection in my nodeJs backend and it works fine. Here is a workable code.
await itemModel.updateOne(
{ itemCode: req.body.items[i].itemCode, isDisabled: false },
{ $push: { PiLocation: req.body.locationInfo } },
opts(session)
);
But facing error when I use arrayFilter in updateOne.
Example:
await itemModel.updateOne(
{
itemCode: req.body.items[i].itemCode,
isDisabled: false
},
{
$set: {
itemCount: prevResult.itemCount + req.body.items[i].quantity,
'locationList.$[i].isCounted': true,
'locationList.$[i].itemCount': req.body.items[i].quantity
}
},
{
arrayFilters: [
{
'i.binId': req.body.locationInfo.binId
}
]
}, opts(session)
);
opts(session) is a callback function use for transection
opts: session => ({ session, returnOriginal: false })

Mongoose $push keeps adding two entries

Here are my user and product schemas:
const productSchema = new Schema({
//...
addedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "users"
}
});
const userSchema = new Schema({
//...
addedItems: [{
type: mongoose.Schema.ObjectId,
ref: "products"
}]
});
mongoose.model("products", productSchema);
mongoose.model("users", userSchema);
In my Node back end route I do this query:
User.findOneAndUpdate(
{ _id: req.body.id },
{ $push: { addedItems: newProduct._id } },
{ upsert: true, new: true },
function(err, doc) {
console.log(err, doc);
}
);
The console.log prints out this:
{
//...
addedItems: [ 5ab0223118599214f4dd7803 ]
}
Everything looks good. I go to actually look at the data using the front-end website for my mongo db; I'm using mlab.com, and this is what shows:
{
//...
"addedItems": [
{
"$oid": "5ab0223118599214f4dd7803"
},
{
"$oid": "5ab0223118599214f4dd7803"
}
]
}
Question: What the heck happened? Why does it add an additional entry into addedItems ?! Even though my console.log only showed one.
Note:
I tested to see if the backend route was being called more than once. It is not.
It seems to be a problem with $push because if I just have { addedItems: newProduct._id } then only one entry goes in, but it overwrites the entire array.
Edit:
Made a test project to produce the same results: https://github.com/philliprognerud/test-mcve-stackoverflow
Can anyone figure out what's going on?
The problem is caused by your mixed used of promises (via async/await) and callbacks with the findOneAndUpdate call which ends up executing the command twice.
To fix the problem:
const updatedUser = await User.findOneAndUpdate(
{ id: userID },
{ $push: { addedItems: newProduct.id } },
{ upsert: true, new: true }
);
console.log(updatedUser);
Future readers note that the use of await isn't shown here in the question, but is in the MCVE.
I am facing similar issue. Just landed to this page. I find that previous answer is not very descriptive. So posting this:
export const updateUserHandler = async (req, res) => {
const request = req.body;
await User.findOneAndUpdate( //<== remove await
{ _id: request.id },
{ $push: { addedItems: newProduct._id } },
{ upsert: true, new: true },
(findErr, findRes) => {
if (findErr) {
res.status(500).send({
message: 'Failed: to update user',
IsSuccess: false,
result: findErr
});
} else {
res.status(200).send({
message: 'Success: to update user',
IsSuccess: true,
result: findRes
});
}
}
);
}
Here there are two async calls one is the async and other is await. Because of this there are two entries in the document. Just remove await from await User.findOneAndUpdate. It will work perfectly.
Thanks!!
When you await Query you are using the promise-like, specifically, .then() and .catch(() of Query. Passing a callback as well will result in the behavior you're describing.
If you await Query and .then() of Query simultaneously, would make the query execute twice
use:
await Model.findOneAndUpdate(query, doc, options)
OR
Model.findOneAndUpdate(query, doc, options, callback)
This code $push keeps adding two entries:
const ali={ "_id": "5eaa39a18e7719140e3f4430" };
// return await customerModel.findOneAndUpdate(
// ali,
// {
// "$push": {
// "address": [objAdr],
// },
// },
// function (error: any, success: any) {
// if (error) {
// console.log(error);
// } else {
// console.log(success);
// }
// }
// );
My solutions working true:
return await customerModel
.findOneAndUpdate(
{ _id: ids },
{ $push: { "address": objAdr } }
)
.catch((err: string | undefined) => new Error(err));

Resources