Unhandled promise rejection mongoose aggregate - node.js

I know there are multiple posts handling similar types of issue but none of them seems to work for me.
In my application, I need to fetch the graphical data for the vertical bar chart from my database. The filtration is based on the two status types and the updatedAt field. The data will be plotted for each month of the year.
I tried two approaches to the same:
First:
exports.leads_based_on_status = async (req, res) => {
const { userId } = req.user;
const fetchMonths = getMonths();
try {
const fetch_leads_new = await fetchMonths.map(async (month) => {
return Lead.aggregate([
{
$match: {
userId: mongoose.Types.ObjectId(userId),
},
},
{
$unwind: "$leads",
},
{
$match: {
$and: [
{ updatedAt: { $gt: month._start, $lt: month._end } },
{ "leads.status": "New" },
],
},
},
]);
});
const fetch_leads_pending = await fetchMonths.map(async (month) => {
return Lead.aggregate([
{
$match: {
userId: mongoose.Types.ObjectId(userId),
},
},
{
$unwind: "$leads",
},
{
$match: {
$and: [
{ updatedAt: { $gt: month._start, $lt: month._end } },
{ "leads.status": "Pending" },
],
},
},
]);
});
Promise.all([fetch_leads_new, fetch_leads_pending]).then(
(resultnew, resultpending) => {
console.log("show result new", resultnew);
console.log("show result pending", resultpending);
//both these results in Promise <pending>
}
);
const leads_status_statics = [
{
New: fetch_leads_new,
},
{
Pending: fetch_leads_pending,
},
];
res.status(200).json({ message: "Graphical Data", leads_status_statics });
} catch (error) {
console.log(error) || res.status(500).json({ error });
}
};
Second:
exports.leads_based_on_status = async (req, res) => {
const { userId } = req.user;
const fetchMonths = getMonths();
try {
fetchMonths.map(async (month) => {
const fetch_leads_new = await Lead.aggregate([
{
$match: {
userId: mongoose.Types.ObjectId(userId),
},
},
{
$unwind: "$leads",
},
{
$match: {
$and: [
{ updatedAt: { $gt: month._start, $lt: month._end } },
{ "leads.status": "New" },
],
},
},
]);
const fetch_leads_pending = await Lead.aggregate([
{
$match: {
userId: mongoose.Types.ObjectId(userId),
},
},
{
$unwind: "$leads",
},
{
$match: {
$and: [
{ updatedAt: { $gt: month._start, $lt: month._end } },
{ "leads.status": "New" },
],
},
},
]);
const leads_status_statics = [
{
New: fetch_leads_new,
},
{
Pending: fetch_leads_pending,
},
];
res.status(200).json({ message: "Graphical Data", leads_status_statics });
//de:16484) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
});
} catch (error) {
console.log(error) || res.status(500).json({ error });
}
};
But none of them is able to help me resolve my issue. The first approach keeps returning Promise <Pending>, while the second approach returns Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:467:11)
Any help to rectify the problem is appreciated :)

Regarding your first approach, The Promise.all(iterable) method takes an iterable as input. In your case, fetch_leads_new and fetch_leads_pending is already returning an array of pending Promise, something like
: [ Promise { <pending> }, Promise { <pending> } ].
So currently you are passing an array with arrays or pending promise(Promise.all([fetch_leads_new, fetch_leads_pending])) to the Promise.all function, something like
Promise.all([[ Promise { <pending> }, Promise { <pending> } ], [ Promise { <pending> }, Promise { <pending> } ]])
So I think you can consider having two Promise.all methods with await one for fetch_leads_new other for fetch_leads_pending
const newRecords = await Promise.all(fetch_leads_new);
const pendingRecords = await Promise.all(fetch_leads_pending);
const leads_status_statics = [
{
New: newRecords,
},
{
Pending: pendingRecords,
},
];
Regarding the second approach
When the fetchMonths has more that one entry that means res.status(200).json(... is also called more than once(In each iteration of map function) and that why you are getting Cannot set headers after they are sent to the client error

Related

MongoDb aggregation for daily number of views and downloads

const fetchSummary = expressAsyncHandler(async (req, res) => {
//GET DAILY SUMMARY
const dailySummary = await Post.aggregate([
{
$group: {
_id: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } },
downloads: { $sum: "$downloadCount" },
totalViews: { $sum: "$numViews" },
},
},
{ $sort: { _id: -1 } },
]);
res.send({ dailySummary });
});
Can someone please help me out here, I'm trying to fetch sum total for daily views and downloads for a post
And here is my result
"dailySummary": [
{
"_id": "2023-01-07",
"downloads": 49,
"totalViews": 227
},
{
"_id": "2023-01-06",
"downloads": 41,
"totalViews": 605
},
{
"_id": "2023-01-05",
"downloads": 0,
"totalViews": 0
}
],
And this result is a wrong
number of views for today is not even up 40,and downloads 10
//============
// Fetch single Post
//============
const fetchPostCtrl = expressAsyncHandler(async (req, res) => {
const { id } = req.params;
validateMongodbId(id);
try {
const post = await Post.findById(id).populate("user comments");
//Updating Number of views
await Post.findByIdAndUpdate(
id,
{
$inc: { numViews: 1 },
},
{ new: true }
);
res.json(post);
} catch (error) {
res.json(error);
}
});
This is how I'm fetching post details

I have an error about mongoose Error: Arguments must be aggregate pipeline operators

Hi I'm new to the mongoose I got an error that Error: Arguments must be aggregate pipeline operators this
I just try to get posts from users who I'm following
I have no idea to solve this problem. It's my first time using aggregate function
here's my code
export const getPosts = async (req, res) => {
const user = req.user;
console.log(user);
try {
if (user.following.length === 0) return res.json("No following users");
//user.following = string[]
const followingPosts = await Post.aggregate([
{
$match: {
userId: { $in: user.following },
},
$sort: {
createdAt: 1,
},
$limit: 10,
},
]);
res.status(200).json(followingPosts);
} catch (error) {
console.log(error);
res.status(404).json({ message: error.message });
}
};
is there a good way to solve this problem let me know
I'd really appreciate it. thanks for reading my qeustion
Your query is syntactically wrong, you are creating one single object for different stages, and mongo expects each stage to be a separate object.
const followingPosts = await Post.aggregate([
{
$match: {
userId: { $in: user.following },
},
$sort: {
createdAt: 1,
},
$limit: 10,
},
]);
The above syntax is wrong. Try this:
const followingPosts = await Post.aggregate([
{
$match: {
userId: { $in: user.following },
}
},
{
$sort: {
createdAt: 1,
},
},
{
$limit: 10,
},
]);

mongoose divide two fields in put request

Can I update a field of a document with a division of two fields? Using Node and MongoDB, I'm trying to create a rating function, and I have to make a division, but nothing seems to work. I want the new value of rating to be, the current one divided by the number of votes.
router.put("/:id/:rating", async (req, res) => {
const movie_rating = parseInt(req.params.rating);
try {
const updatedMovie = await Movie.findByIdAndUpdate(
req.params.id,
{
$inc: { noVotes: 1 },
$inc: { rating: movie_rating },
$divide: { rating: [rating, noVotes] },
// rating: { $divide: [rating, noVotes] }
},
{ new: true }
);
res.status(200).json(updatedMovie);
} catch (err) {
res.status(500).json(err);
}
});
You need to change few things
Sample
db.collection.update({},
[
{
"$set": {
"key2": {
$add: [
"$key2",
1
]
},
key3: {
"$divide": [
{
$add: [
"$key2",
1
]
},
"$key"
]
},
}
}
],
{
"multi": true,
"upsert": false
})
You need aggregate update as you need divide
You cannot use the updated value in the same operation
You cannot combine $inc, $set in aggregate update
Alternatively, you can use $add instead $inc
you can reperform the operation for the divide operation than making another update call
This can be done with $set,
It will look like this:
router.put("/:id/:rating", async (req, res) => {
const movie_rating = parseInt(req.params.rating);
try {
const updatedMovie = await Movie.findByIdAndUpdate(
req.params.id,
[
{
$set: {
noVotes: { $sum: ["$noVotes", 1] },
rating: { $sum: ["$rating", movie_rating] },
averageRating: { $divide: ["$rating", "$noVotes"] },
},
},
],
{ new: true }
);
res.status(200).json(updatedMovie);
} catch (err) {
res.status(500).json(err);
}
});

Decrement a field and delete entire object if field's value reaches to 0 in MongoDB & Node.js

I have array of objects like shopping list .for example i added 2 product to my list
cart:[{id:1,quantity:15},{id:2,quantity:5}]
i can increment and decrement quantities well , but what i want to do is when i decrement to 0 i want it to show it as i dont have in my cart, i know its possible for pull and i tried this and fail.idk what i am doing wrong , when i try console.log(user) it gives me null ,because it doesnt see "cart.quantity":0 , idk why not. User info and layout is ok btw.
router.get("/:productId/decrement", auth, async (req, res) => {
const user = await User.findOne({
id: req.user._id,
"cart.quantity": 0,
});
if (user) {
await User.findOneAndUpdate(
{
id: req.user._id,
"cart.id": req.params.productId,
"cart.quantity": 0,
},
{ $pull: { cart: { id: req.params.productId } } }
);
} else {
await User.findOneAndUpdate(
{
id: req.user._id,
"cart.id": req.params.productId,
},
{ $inc: { "cart.$.quantity": -1 } },
{ new: true },
(err, doc) => {
if (err) {
return res.json({ success: false, err });
}
return res.json({ success: true, cart: doc });
}
);
}
});
and here is my user modal
const userSchema = mongoose.Schema({
name: {
type: String,
maxlength: 50,
},
email: {
type: String,
trim: true,
unique: 1,
},
password: {
type: String,
minglength: 5,
},
lastname: {
type: String,
maxlength: 50,
},
cart: { type: Array, default: [] },
On MongoDB version >= 3.2 :
router.get("/:productId/decrement", auth, async (req, res) => {
try {
let bulkArr = [
{
updateOne: {
filter: {
id: req.user._id,
"cart.id": req.params.productId,
"cart.quantity": 1, // You can use {$lte : 1}
},
update: { $pull: { 'cart': { id: req.params.productId } } },
},
},
{
updateOne: {
filter: {
id: req.user._id,
"cart.id": req.params.productId
},
update: { $inc: { "cart.$.quantity": -1 } },
},
},
];
await User.bulkWrite(bulkArr);
return res.json({ success: true });
} catch (error) {
console.log("Error ::", error);
return res.json({ success: false, ...{error} });
}
});
Note :
I believe you're mixing async-await's & callbacks(). Please try to avoid it.
In bulkWrite all filter conditions will be executed but update operation will only be executed if respective filter matches (kind of individual ops) but in one DB call.
bulkWrite outputs 'write result' but if you need result doc you need to do .find() call. But as the whole point of using bulkWrite is to avoid multiple calls to DB - If result has no errors you can send success message to front-end then they can show appropriate count (by doing decrement).
Ref : .bulkwrite()
On MongoDB version >= 4.2 :
As we can use aggregation in updates you can do like below :
User.findOneAndUpdate({id: req.user._id, 'cart.id': req.params.productId},
[
{
$addFields: {
cart: {
$reduce: {
input: "$cart",
initialValue: [],
in: {
$cond: [
{ $and: [ { $eq: [ "$$this.id", req.params.productId ] }, { $gt: [ "$$this.quantity", 1 ] } ] }, // condition
{
$concatArrays: [ "$$value", [ { $mergeObjects: [ "$$this", { quantity: { $add: [ "$$this.quantity", -1 ] } } ] } ]]
}, // condition is true, So we're decrementing & pushing object to accumulator array
{
$cond: [ { $eq: [ "$$this.id", req.params.productId ] }, "$$value", { $concatArrays: [ "$$value", [ "$$this" ] ] } ]
} // condition failed, so pushing objects where id != input productId
]
}
}
}
}
}
], {new: true})
Ref : aggregation-pipeline-for-updates, $reduce
Test aggregation pipeline here : mongoplayground
Note :
Few MongoDB clients may throw an error saying 'update operations may only contain atomic operators' that case you can use .update() or testing it using code as most drivers that support 4.2 doesn't throw such errors.
Try this:
Create a partial index that only indexes documents where the field value equals or is less than 0 (depending on your needs)
Add a TTL to "expire after 0 seconds" on a field that is set to 0. (i.e. all indexed documents are set to expire immediately).
On Java it would look like:
new IndexModel(
Indexes.ascending(ZERO_FIELD),
new IndexOptions().expireAfter(0L, TimeUnit.SECONDS)
.partialFilterExpression(Filters.lte(VALUE_FIELD, 0)
)
)

How to improve the query performance it taking 30 sec to execute the query MongoDb

I am using node is along with mongoose.while I am executing this query its taking 30+ sec for execution
I think its due to looping how to improve the performance can anyone guide me I am new to mongoose
companies
.findById(
{
_id: companyProfile,
},
function(err, company) {
if (err) {
return res.status(400).send({
message: "Some Error Occured!",
});
} else {
var responseJson = [];
company.ambulances.forEach(function(doc) {
Gps.find({
$and: [
{
device: doc.deviceId,
},
{
longitude: {
$exists: true,
$ne: "",
},
},
{
locationDate: {
$exists: true,
$ne: "",
},
},
{
latitude: {
$exists: true,
$ne: "",
},
},
],
})
.sort({
$natural: -1,
})
.limit(1)
.exec(function(err, gpsLocations) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err),
});
} else {
responseJson.push({
gps: gpsLocations,
});
if (
company.ambulances.length === responseJson.length
) {
res.json(responseJson);
}
}
});
});
}
},
)
.populate("ambulances");
Well, first try to convert the callbacks to Promises, in order the code to be easier to understand. Now about the speed, you can match all the Gps objects using the $in operator. So the result will be something like that.
try {
const company = await companies.findById({ _id: companyProfile });
const gpsLocations = await Gps.find({
$and: [
{
device: { $in: company.ambulances },
},
{
longitude: {
$exists: true,
$ne: "",
},
},
{
locationDate: {
$exists: true,
$ne: "",
},
},
{
latitude: {
$exists: true,
$ne: "",
},
},
]
}).sort({
$natural: -1,
}).populate("ambulances");
return res.json(gpsLocations.map(gpsLocation => ({
gps: gpsLocation,
})));
} catch(e) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err),
});
}

Resources