How can I get total length(count) of all comments inside Post? - mongodb

Let's say I have this Schema:
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
}
],
date: {
type: Date,
default: Date.now
}
});
And let's say I save 10 posts to the database. How can I get total length(count) of all comments they have?
This works only for whole collection of Post. It returns 10.
router.get( '/total', ( req, res ) => {
Post.estimatedDocumentCount().then( ( totalCount) => {
res.json( totalCount );
}).catch( ( err ) => {
console.log( err );
});
});
I don't want to use .count() method since it's deprecated.
Thank you

You can use $group and find total counts of comments as below:
db.collection.aggregate([
{
$group: {
_id: null,
count : { $sum: { $size: "$comments"}}
}
}
])

Related

Mongo How to use select to return multiple selected properties from document?

I am using findOneAndUpdate, where I want
to return updated document
i dont want to return the entire document but only the following:
one object out of an array + a virtual property in the document.
const notifications = {
to:
messages: [
{_id: "23452", title:"hello"}, {_id: "23452", title:"bye"}
]
...
}
so for example I would want to only return the object {_id: "23452", title:"bye"} AND unreadCount virtual field prop.
my code works so far as I am returning updated document and only the message I want, but I dont know how to return also the unreadCount prop.
schema:
const notificationSchema = new mongoose.Schema({
to: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
messages: [{
title: {
type: String,
required: true
},
isRead: {
type: Boolean,
default: false
},
createdAt: {
type: Date,
default: new Date()
}
}, ]
},
{timestamps: true, toObject: {virtuals: true}
});
notificationSchema.virtual('unreadCount').get(function() {
... return count;...
})
updateRead: async (userId, id) => {
const notification = await Notification.findOneAndUpdate({to: userId, 'messages._id': id}, {
$set: { "messages.$.isRead": true} },
{ select: {
messages: {
$elemMatch: {_id: id}
}
}, new: true});
}

How do you update a nested array with Mongoose?

This is what I have so far. This is my AnswerSchema with a comments array nested within that I am trying to update.
const AnswerSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
question: {
type: Schema.Types.ObjectId,
ref: 'question',
},
text: {
type: String,
required: true,
},
name: {
type: String,
},
avatar: {
type: String,
},
views: {
type: Number,
},
date: {
type: Date,
default: Date.now,
},
answerLikes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
},
],
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
text: {
type: String,
required: true,
},
name: {
type: String,
},
avatar: {
type: String,
},
commentLikes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
},
],
date: {
type: Date,
default: Date.now,
},
},
],
})
and here is my update route that I am trying to use to update the comments array text field
try {
const updatedAnswer = await Answer.findOneAndUpdate(
{ _id: req.params.answer_id },
{
$set: { 'comments.$[comment].text': formattedAnswer },
},
{
arrayFilters: [{'comment._id': req.params.comment_id }],
},
{ new: true }
)
res.json(updatedAnswer)
I keep getting the error 'Callback must be a function, got [object Object]' and cant figure out a fix.
Any ideas?
Thanks!
The problem in your code is that you are passing 4 parameters to the findOneAndUpdate function.
The 4th argument is a callback which accepts a function:
(err /* an error if occurred */, doc /* the updated document */) => {}
In order to solve that you need to combine your last 2 arguments into one object like:
{
arrayFilters: [{'comment._id': req.params.comment_id }],
new: true
}
Final query:
const updatedAnswer = await Answer.findOneAndUpdate(
{ _id: req.params.answer_id },
{
$set: { 'comments.$[comment].text': formattedAnswer },
},
{
arrayFilters: [{'comment._id': req.params.comment_id }],
new: true
}
)
The 4th argument in findOneAndUpdate function takes in a callback function that was where your error was.
Try this
try{
const updatedAnswer = await Answer.findOneAndUpdate(
{ _id: req.params.answer_id },
{
$set: { 'comments.$[comment].text': formattedAnswer },
},
{
arrayFilters: [{'comment._id': req.params.comment_id }],
new: true
}
);
res.json(updatedAnswer);
}catch(err){
//console.log(err)
}

Get results of aggregation query in mongoose using objectId, virtual types (it works in mongo shell)

My code on the backend, in case it matters (NodeJS and MogoDB):
//my includes at the top of the file
const mongoose = require('mongoose');
const Appt = mongoose.model('Appt');
const ApptType = mongoose.model('ApptType');
const ApptStatus = mongoose.model('ApptStatus');
var moment = require('moment-timezone');
moment().tz('America/New_York');
now = moment(); // add this 2 of 4
dayStart = now.startOf('day');
dayEnd = now.endOf('day');
// the aggregation query that's not returning correctly
Appt.aggregate([
{
$match: {
patientID: appt.patientID._id,
scheduled: {
$gte: new Date(start),
$lt: new Date(appt.pmtduedate)
}
}
},
{
$group: {
_id: 'id',
payment: { $sum: '$payment' },
pmtdue: { $sum: '$pmtdue' },
visits: { $sum: 1 }
}
}
]).exec(
err => {
console.log(`Error finding past payments`, err);
callback(err);
},
result => {
console.log(`RESULT: ${result}`);
pastPayments = result;
if (!pastPayments || pastdueamt === 0) {
pastdueamt = 0;
console.log(`2. getCurrentDue ${pastdueamt}`);
this.getCurrentDue(appt, pastdueamt, today, callback);
} else {
console.log(`pastPayments ${pastPayments}`);
console.log(
`planamt ${planamt} pmtdue ${pastPayments.pmtdue} payments: ${pastPayments.payment}`
);
pastdueamt =
pastPayments.pmtdue === 0
? planamt - pastPayments.payment
: pastPayments.pmtdue - pastPayments.payment;
console.log(`pastdueamt calculated: ${pastdueamt}`);
console.log(`2. getCurrentDue`);
this.getCurrentDue(appt, pastdueamt, today, callback);
}
}
);
When I run my query in mongo, the expected results return. In my app, the results of this query above return nothing (no error, either). I've tried doing the following:
$match: {
patientID: new mongoose.types.ObjectId(appt.patientID._id),
I've also tried:
$match: {
patientID: { $toObjectId: appt.patientID._id },
but I get errors on both of these options. The first returns an error of
TypeError: Cannot read property 'ObjectId' of undefined.
The second returns some sort of mongo error
errmsg: 'unknown operator: $toObjectId',
code: 2,
codeName: 'BadValue',
name: 'MongoError',
[Symbol(mongoErrorContextSymbol)]: {} }
How do I do mongoose aggregation successfully using objectIds, virtual types, etc.?
EDITED TO ADD MY SCHEMAS:
const apptSchema = new mongoose.Schema(
{
ID: Number,
patientID: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Patient'
},
oldPatientID: Number,
status: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApptStatus'
},
type: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApptType'
},
scheduled: Date,
note: String,
reminder: Boolean,
cell: Boolean,
email: Boolean,
subjective: String,
assessment: String,
plan: String,
planamt: Number,
objective: {
clearUC: Boolean,
UCcheck: String,
thompson: String,
activator: String,
other: String
},
updated: {
type: Date,
default: new Date()
},
pmtdue: Number,
pmtduedate: Date,
payment: Number,
pmttype: String,
paid: Boolean,
pmtnote: String
},
{ toJSON: { virtuals: true } }
);

mongoose, match using upper model field in deep population

I'm a newbie in MongoDB and mongoose. and I'm trying to create a simple messenger using MongoDB.
Subscription model:
const SubscriptionSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User',
index: { unique: true }
},
conversations: [
{
conversation: {
type: Schema.Types.ObjectId,
ref: 'Conversation'
},
lastSeenMessage: {
type: Schema.Types.ObjectId,
ref: 'Message'
}
}
]
});
Conversation model:
const ConversationSchema = new Schema(
...,
{ timestamps: true, toJSON: { virtuals: true } }
);
ConversationSchema.virtual('messages', {
ref: 'Message',
localField: '_id',
foreignField: 'conversation'
});
Message model:
const MessageSchema = new Schema(
{
content: {
type: String,
maxlength: [1440, 'Text too long.']
},
type: {
type: String
},
conversation: {
type: Schema.Types.ObjectId,
ref: 'Conversation'
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
}
)
I need to retrieve m latest conversations of user and n messages within each conversation when they are written after last seen message. So I used mongoose like this:
let subscription = await Subscription.findOne(
{ user }
//{ ObjectArray: { $slice: [(page - 1) * limit, (page - 1) * limit + limit] } }
).populate({
path: 'conversations.conversation',
model: 'Conversation',
populate: {
path: 'messages',
match: { _id: { $gt: 'conversations.lastSeenMessage' } },
options: {
skip: 0,
limit: 50,
sort: { updatedAt: -1 }
}
}
});
the match condition doesn't works in above query. how can use upper model in match condition in the deep population ?
Is it possible or I must try another way?

How to Check current user's vote before votes are grouped and sumed in same aggregate function

var PostSchema = new mongoose.Schema({
item: {
type: mongoose.Schema.ObjectId,
ref: 'item',
required: true
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
vote: {
type: Number,
default: 0
},
total: {
type: Number,
default: 0
},
awsPostKey: {type: String},
picture: {type: String, required: true}
});
var data = function(){
return Post
.find({})
.then(function(post){
return post;
})
};
var userId = //mongo objectId for current user
//postVote schema:
var PostVoteSchema = new mongoose.Schema({
post: {
type: mongoose.Schema.ObjectId,
ref: 'Post',
required: true
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
vote: {
type: Number,
default: 0
}
});
//pass data from Post query to PostVote sum function:
PostVoteSchema.statics.sum = function (data, userId) {
var postIds = data.map(function (a) {
return a._id;
});
return PostVote
.aggregate(
[
{ $match: { 'post': {$in: postIds}}},
{ $group: { _id:'$post' ,vote:{$sum:'$vote'}}}
])
.execAsync()
.then(function(votes){
return votes;
//desired output to client, _id is for specific post
{_id: 5802ea4bc00cb0beca1972cc, vote: 3, currentUserVote: -1}
});
};
I'm successfully able to get the total sum of all votes with the same postId.
Now, I"m wanting to see if the current user (userId) has placed a vote for the given post as well, then to return how they voted (+1 or -1) along with the sum of all votes for the specific post.
Is it possible to do this, or will I have to do this outside of my aggregate pipeline -- within a second query? It just seems potentially taxing to have to query the collection again.
Yes, that's possible. Within the $group pipeline, you can use the $cond operator as the logic for feeding the $sum accumulator operator. For example:
return PostVote.aggregate([
{ "$match": { "post": { "$in": postIds } } },
{
"$group": {
"_id": "$post",
"votes": { "$sum": "$vote" },
"userVotes": {
"$sum": {
"$cond": [
{ "$eq": ["$user", userId] },
"$vote",
0
]
}
}
}
}
]).execAsync().then(function(votes){
return votes;
});