Mongoose: cannot infer query fields to set, path 'participants' is matched twice - node.js

I'm using mongoose with Node.js to create a document of chat with participants as one of the fields if the chat doesn't exist.
If it does exist then simply increment the status to 1 or any number.
My current Solution:
try {
let query = { participants: { $all: [CURRENT_USER_ID, TARGETED_ID] } };
let update = { $inc: { status: 1 }};
let options = { upsert: true, new: true };
let chat = await Chat.findOneAndUpdate(
query,
update,
options
).exec();
console.log(chat);
} catch (err) {
console.log(err.message);
}
I will receive an error
"cannot infer query fields to set, path 'participants' is matched
twice"
I even use this solution and it doesn't work, it created an empty list of participants instead.
let query = {
participants: {
$all: [
{ $elemMatch: { $eq: CURRENT_USER_ID } },
{ $elemMatch: { $eq: TARGETED_ID } }
]
}
};
Any help would be really helpful. Thanks

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.

Conditionally update item or delete item in one query in mongoose

So, what I'm trying to achieve here is two things. First one is to decrement the quantity of a product. Second is that if that product quantity is 0 then delete that product.
So I'm trying to set a condition that if product quantity is 0 then delete that product otherwise simply decrement the quantity.
Here is my cart document:
And here is my function:
const cart = await Cart.findOneAndUpdate(
{ userID: req.body.userID, "items.productID": req.body.productID },
{$lt: ['items.productID', 0] ? { $pull: { items: { productID: req.body.productID } } } : { $inc: { 'items.$.quantity': -1 } }},
{ new: true }
);
I have tried to achieve that with ternary operator but it didn't worked. Can anybody help me with this please? Thank you.
You cannot use a ternary operator in your query. Not only is this not supported in MongoDB, it will also simply be evaluated by node.js, not by MongoDB itself. In fact, conditional update operations of the nature you're describing are themselves not something that MongoDB is capable of handling.
Your best bet is to perform two queries, where the first one performs the decrement operation and the second pulls any items out that have a quantity less than or equal to 0:
const cartAfterDecrement = await Cart.findOneAndUpdate(
{ userID: req.body.userID, "items.productID": req.body.productID },
{ $inc: { 'items.$.quantity': -1 } },
{ new: true }
);
const cartAfterPull = await Cart.findOneAndUpdate(
{ userID: req.body.userID, items: { $elemMatch: {
productID: req.body.productID,
quantity: { $lte: 0 }
} } },
{ $pull: {items: { quantity: { $lte: 0 } } } },
{ new: true }
);
const cart = cartAfterPull ? cartAfterPull : cartAfterDecrement;
If you wish to allow products with quantities equal to 0, then modifying the second query is a trivial matter.
The only downside to this approach is that there will be a natural, very slight delay between these two updates, which may result in a very rare case of a race condition occurring where a cart is returned and contains an item with a quantity less than or equal to 0. In order to avoid this, you will need to either filter these values out with server code or by retrieving your cart(s) using aggregation, or you will need to make use of transactions to ensure that the two operations occur in sequence without the document being retrieved between the two write operations.
With that said, constructing the appropriate array filtering logic or transaction management is beyond the scope of this question, so will not be included in this answer.
Query
you can do it with a pipeline update ( >= MongoDB 4.2)
find the user that has that productID
reduce the items, to an array with item but
if productID not the one i look for, keep it as it is
else if quantity=0 remove it(don't add it to the reduced array)
else keep it with quantity-1
*findAndModify accepts pipeline also, for nodejs driver you must find the method that wraps this command, or run the command directly
Test code here
update(
{"userID": req.body.userID,
"items.productID": req.body.productID},
[{"$set":
{"items":
{"$reduce":
{"input":"$items",
"initialValue":[],
"in":
{"$switch":
{"branches":
[{"case":{"$ne":["$$this.productID", req.body.productID]},
"then":{"$concatArrays":["$$value", ["$$this"]]}},
{"case":{"$gt":["$$this.quantity", 0]},
"then":
{"$concatArrays":
["$$value",
[{"$mergeObjects":
["$$this", {"$subtract":["$this.quantity", 1]}]}]]}}],
"default":"$$value"}}}}}}])
Thanks #B. Fleming and all others for your answer. I have just use a simple code implementation for that however it wasn't what I was expected but still it's good to go.
module.exports.deleteCartItem = async (req, res, next) => {
var condition;
var update;
const product = await Cart.findOne({ userID: req.body.userID,
items: {
$elemMatch: {
productID: req.body.productID,
quantity: { $lte: 0 }
}
}
});
// IF PRODUCT WITH 0 QUANTITY PULL/DELETE THAT PRODUCT
if (product !== null) {
condition = { userID: req.body.userID, items: { $elemMatch: { productID: req.body.productID, quantity: { $lte: 0 } } } },
update = { $pull: {items: { quantity: { $lte: 0 } } } }
}
// OTHERWISE DECREMENT THE QUANTITY FOR THAT PRODUCT
else {
condition = { userID: req.body.userID, "items.productID": req.body.productID }
update = { $inc: { 'items.$.quantity': -1 } }
}
try {
const cart = await Cart.findOneAndUpdate(condition, update, { new: true });
if (cart !== null) {
res.status(200).json({
statusCode: 1,
message: "Item Removed",
responseData: cart
});
} else {
res.json({
statusCode: 0,
msgCode: 462,
message: 'Not found',
responseData: null
});
}
} catch (err) {
console.log('Server Error: ', err);
next(new Error('Server Error, Something was wrong!'));
}
}

check an array of string value with array of object in mongodb

I have array of strings like this
let fromHour = ['2.5','3','3.5']
let toHour = ['2.5','3','3.5']
I have an array of object saved in mongoDB
timeRange = [
{
from:'2.5',
to:'3'
},
{
from:'3',
to:'3.5'
}
]
I want to check if any of my array of string value exist in that object value
I have tried this but it give me this error ( Unrecognized expression '$match' )
checkAppoint = await Appointment.aggregate([
{
$project: {
date: myScheduleFinal[k].date,
status: { $in: ['pending', 'on-going'] },
timeRange: {
'$match': {
'from': { $in: fromHolder },
'to': { $in: toHolder },
},
},
},
},
]);
also I have tried this solution and it work for me but it take to much time so I am trying this with aggregate
checkAppoint = await Appointment.findOne({
date: myScheduleFinal[k].date,
status: { $in: ['pending', 'on-going'] },
timeRange:{$elemMatch:{
from:{$in:fromHolder},
to:{$in:toHolder}
}}
});
So anyone have a solution for that
Just try $elemMatch and $in operators,
using find() method
checkAppoint = await Appointment.find({
timeRange: {
$elemMatch: {
from: { $in: fromHour },
to: { $in: toHour }
}
}
})
Playground
using aggregate() method
checkAppoint = await Appointment.aggregate([
{
$match: {
timeRange: {
$elemMatch: {
from: { $in: fromHour },
to: { $in: toHour }
}
}
}
}
])
Playground
So I have found a way around to solve this problem and I will share the solution I used
First I want to minimize my request to mongodb so I am now making just one request that bring all the appointment with the required date
and I want to make it this way because my fromHour and toHour array will change many time through single request
helperArray => contains all the day I want to check it's range
let checkAppoint = await Appointment.find({
date: { $in: helperArray },
status: { $in: ['pending', 'on-going'] },
});
now inside my for loop I will go through that data
checkAppoint.filter((singleAppoint) => {
if (singleAppoint._doc.date === myScheduleFinal[k].date) {
singleAppoint._doc.timeRange.map((singleTime) => {
if (fromHolder.includes(singleTime.from)) {
busy = true;
}
});
}
});

Select fields in mongoose query where field value not equal to something

I am basically trying to update a document and then select the fields from the result where the field value is not equal to something. Assume jwt_id to be b816cf00e9f649fbaf613e2ca2d523b5.
Query
const removeDevices = await Identity.findOneAndUpdate(
{
userID: user_id
},
{
$pull: {
activeTokens: {
jti: {
$ne: jwt_id
}
}
}
},
).select(["-_id", "activeTokens.jti"]);
Now, running this query gives the following output:
{ activeTokens:
[ { jti: '5d872359af2c47e5970c1fae531adf0e' },
{ jti: 'd3ac84f520614067b1caad504d7ab27f' },
{ jti: '25c6fa96705c4eec96e1427678c3ff50' },
{ jti: 'b816cf00e9f649fbaf613e2ca2d523b5' }
]
}
How can I get all the jti fields except { jti: b816cf00e9f649fbaf613e2ca2d523b5 } from the select command?
Desired Output
{ activeTokens:
[ { jti: '5d872359af2c47e5970c1fae531adf0e' },
{ jti: 'd3ac84f520614067b1caad504d7ab27f' },
{ jti: '25c6fa96705c4eec96e1427678c3ff50' },
]
}
It's hard to say for certain without testing, but i don't think mongoose returns the document after it was modified, but rather simply returns the matching document. So, i think in the case of findOneAndUpdate, you would have to have your query match to do the pull, and then manually filter the array again in application code to get the desired output.
This might work:
const removeDevices = await Identity.findOneAndUpdate(
{
userID: user_id
},
{
$pull: {
'activeTokens.jti': { $ne: jwt_id }
}
},
).select(["-_id", "activeTokens.jti"]).then(identity=>identity.activeTokens.filter(token=>token.jti!==jwt_id));
If the above doesn't work for some reason, then i would try something more simpler
simple:
const removeDevices = await Identity.findOne({userID: user_id}).select(["-_id", "activeTokens"]).then(identity=>{
const removedTokens = []
identity.activeTokens = identity.activeTokens.filter(token=>{
if(token.jti===jwt_id) {
return true;
}
removedTokens.push(token);
})
identity.save(err=>{
console.log('doc saved')
});
return removedTokens;
});
or (atomic):
const removeDevices = await Identity.findOne({userID: user_id}).select('activeTokens','jti _id').then(identity=>{
const removedTokens = identity.activeTokens.filter(token=>token.jti!==jwt_id);
const result = await Identity.update({userId:user_id},{$pull:{'activeTokens._id': { $in: removedTokens.map(t=>t._id) } }});
console.log(result.nModified);
return removedTokens;
});

How to update a value with aggregate in mongodb and node

I post this question in relation to my use case.
It is true that there is a lot of response in the same subject but I do not find an answer .
that is why I ask you for help Thanks.
I would like to be able to update the lineItemStatus inside lineItems array.
Here is my model :
const orderSchema = new Schema(
lineItems: [{
lineItemStatus: {
type: String,
default: 'en waiting for validation',
lowercase: true
}
}]
)
The result look like this
{
"_id": "5c659cd0be79c124126d5ec2",
"lineItems": [{
"lineItemStatus": "waiting for validation", //the status to update
"_id": "1"
},
{
"lineItemStatus": "delivered",
"_id": "2"
}
]
}
First I'm able to get a single item of lineItems.
this is the code
async updateStatus(req, res) {
let givenLineItemId = req.body.lineItemId
let givenlineItemStatus = req.body.status // the status to update
try {
const ObjectId = mongoose.Types.ObjectId
const aggregationStages = [
{
$unwind: '$lineItems'
},
{
$match: {
'lineItems._id': ObjectId(givenLineItemId)
}
}
]
await Order
.aggregate(aggregationStages)
.exec(function(err, orders) {
if (err) res.status(400).send(err)
res.status(200).send(orders)
})
} catch (err) {
return res.status(500).send(err)
}
}
But now i'm not able to to update the lineItemStatus i see some way to use set or push but it doesn't work.
Thanks a lot for the help.
The aggregation stage itself does not support updates. You have two options:
1) Collect the aggregate results into a variable and do a bulk update. See link.
2) Call forEach on the aggregate. You can see samples provided in this answer.

Resources