MongoDb aggregation for daily number of views and downloads - node.js

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

Related

How do I update an array within an array in mongodb

In the function below, if the user, topic and subtopic do not exist, a new testRecord is inserted into the testRecords array of the User collection. This works fine. On the other hand, if the combination of the user, topic and subtopic exist, only the scores array of testRecords is to be populated with the new scores. This is not working. The code segment that begins: if(existingTopicSubtopic) is the part that is not working.
exports.updateUserTestRecord = async (req, res) => {
console.log('req.body', req.body)
const {topicName, subtopicName, rightAnswers, wrongAnswers, date} = req.body;
const {
userId
} = jwt.verify(req.headers.authorization, process.env.JWT_SECRET);
const existingTopicSubtopic = await User.findOne(
{
_id: userId,
'testRecords.topic': topicName,
'testRecords.subtopic': subtopicName
},
)
let user;
if(existingTopicSubtopic) {
console.log('EXISTING USER')
user = await User.findOneAndUpdate(
{
_id: userId,
'testRecords.topic': topicName,
'testRecords.subtopic': subtopicName
},
{
$addToSet : {
testRecords : [
{
'scores.$.rightAnswers': rightAnswers,
'scores.$.wrongAnswers': wrongAnswers,
'scores.$.date': date,
}
]
}
},
)
} else {
console.log('NOT EXISTING USER')
user = await User.findOneAndUpdate(
{
_id: userId,
},
{
$addToSet : {
testRecords : [
{
topic: topicName,
subtopic: subtopicName,
scores: [
{
rightAnswers,
wrongAnswers,
date,
}
]
}
]
}
},
)
}
}
For inner array operation, we need to use $[identifier] like $[element]. In your case:
if (existingTopicSubtopic) {
await User.findOneAndUpdate(
{
_id: userId,
"testRecords.topic": topicName,
"testRecords.subtopic": subtopicName,
},
{
$addToSet: {
"testRecords.$[element].scores": {
rightAnswers: rightAnswers,
wrongAnswers: wrongAnswers,
date,
},
},
},
{
arrayFilters: [
{
"element.topic": topicName,
"element.subtopic": subtopicName,
},
],
}
);
} else {
await User.findOneAndUpdate(
{
_id: userId,
},
{
$addToSet: {
testRecords: {
topic: topicName,
subtopic: subtopicName,
scores: [
{
rightAnswers,
wrongAnswers,
date,
},
],
},
},
}
);
}

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);
}
});

NodeJS (Express) + MongoDB + Angular stream a request

I want to stream a MongoDB query in NodeJS because, in Angular, i have a chart and i want to update the data without making a refresh.
So far, this is my code (NodeJS):
exports.gettoday = function (request, response) {
db.collection("nifi5", function (err, collection) {
collection
.aggregate([
{
$group: {
_id: {
minute: {
$minute: { $dateFromString: { dateString: "$created_at" } },
},
hour: {
$hour: { $dateFromString: { dateString: "$created_at" } },
},
day: {
$dayOfMonth: { $dateFromString: { dateString: "$created_at" } },
},
month: {
$month: { $dateFromString: { dateString: "$created_at" } },
},
year: {
$year: { $dateFromString: { dateString: "$created_at" } },
},
},
avg: { $avg: "$value_temperature" },
},
},
])
.stream()
.toArray(function (err, items) {
if (err) {
response.statusMessage = {
devMessage: err,
clientMessage: "Unexpected error, try again latter",
};
response.send(response.statusMessage).end();
}
let tarray = { labels: [], data: [] };
items.forEach((element) => {
element.date = element._id.hour + ":" + element._id.minute;
element.avg = Math.round(element.avg * 100) / 100;
});
items.sort(function (a, b) {
return a.date < b.date ? -1 : 1;
});
items.forEach((element) => {
tarray.labels.push(element.date);
tarray.data.push(element.avg);
});
return response.json(tarray);
});
});
};
Angular:
gettodaydata(): Observable<any> {
return this.http.get(`${environment.baseURL}gettoday`).subscribe((data) => {
this.data_line.push(...data.data);
this.labels_line.push(...data.labels);
this.isLineChartLoading = false;
});
}
Can u please help me streaming this controller and fetch the data in Angular to update my chart when new data comes do my database ?
Thank you all!
You should give a try to socket.io, it creates a persistent connection and responds to events in real time.

Unhandled promise rejection mongoose aggregate

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

Remove _Id from mongoose Aggregate response

I'm trying to remove the _Id from the returned documents, this is my code:
module.exports = function(app) {
// Module dependencies.
var mongoose = require('mongoose'),
Contacts = mongoose.models.Contacts,
api = {},
limit = 10;
api.contacts = function(req, res) {
Contacts.aggregate([{
$group: {
"_id": {
name: "$name",
city: "$city",
state: "$state"
}
}
}, {
$sort: {
AgencyTranslation: 1
}
}, {
$limit: req.query.limit | limit
}],
function(err, contacts) {
if (err) {
res.json(500, err);
} else {
res.json({
contacts: contacts
})
}
})
};
app.get('/api/contacts', api.contacts);
};
the current result-set looks like this:
{
"contacts":[
{"_id":{"name":"Joe","city":"ankorage","state":"AL"}},
{"_id":{"name":"Mark","city":"washington","state":"DC"}}
...
]
}
I tried to replace "_Id" with "$project", or $project, and adding "_Id": 0 to the object, as some have suggested elsewhere, but was not successful.
I also tried res.send(contacts), but that only stripped the super-object ('contacts').
Any suggestions are appreciated.
Like this
Contacts.aggregate( [
{ $group: { "_id": { name: "$name", city: "$city", state: "$state" } } },
{ $project: {_id: 0, name: '$_id.name', city: '$_id.city', state: '$_id.state'} },
{ $sort: { AgencyTranslation: 1 } },
{ $limit: req.query.limit | limit }
], function () {
});
Bunch of time but, here is the answer:
After making $group or $project, do this:
{ $unset: ["_id"] }

Resources