Mongoose find last messages grouping by two fields - node.js

I would like to put together a contact list based on the last messages sent or received from a contact.
To do this, I have to sort the last messages (both sent and received), add them and return the last one.
My response should be in the following format:
{
"success": true,
"chatlist": [
{
"id_user": "54228fe2c0df8d1120ed091b",
"lastMessage": {
"content": "message 6",
"date": "2016-11-09T02:54:41.687Z"
},
"unreadMessages": 3,
"name": "user 1"
},
{
"id_user": "12228fe2c0df8d11204g4d",
"lastMessage": {
"content": "message 3",
"date": "2016-11-09T02:54:23.329Z"
},
"unreadMessages": 2,
"name": "user 2"
},
{
"id_user": "58228fe2c0df8d1120e12sd",
"lastMessage": {
"content": "message 1",
"date": "2016-11-09T02:54:19.313Z"
},
"unreadMessages": 1,
"name": "user 3"
}
],
"pages": 2
}
My User Schema is:
var schema = new Schema({
name: {type: String, required: true},
email: {type: String, required: true, unique: true},
password: {type: String, required: true, select: false},
created_at: {type: Date, required: true, default: Date.now}
});
My Message Schema is:
var schema = new Schema({
content: {type: String, required: true},
type: {type: String, required: true, default: 'text'},
status: {type: String, default: 'not_read'},
created_at: {type: Date, default: Date.now},
read_at: {type: Date},
userFrom: {type: Schema.Types.ObjectId, ref: 'User', required: true},
userTo: {type: Schema.Types.ObjectId, ref: 'User', required: true}
});
I have tryed this:
var itensPerPage = 15;
var skip = page !== undefined ? page * itensPerPage : 0;
pages = Math.ceil(pages / itensPerPage);
Message
.aggregate([
{ '$sort': {
'created_at': -1
}},
{ "$skip": skip },
{ "$limit": itensPerPage },
{ '$match': {
$or: [
{ userFrom: user.id_user },
{ userTo: user.id_user }
]
}},
{ '$group': {
'_id': {
'userFrom': '$userFrom',
'userTo': '$userTo'
},
}
},
])
.exec(function (err, messages) {
res.send({"success": true, "chatlist": messages, "pages": pages});
});
How can I modify my query to get the desired response?
Thank you.

Try this
var itensPerPage = 15;
var skip = page !== undefined ? page * itensPerPage : 0;
pages = Math.ceil(pages / itensPerPage);
Message
.aggregate([
{ '$sort': {
'created_at': -1
}},
{ "$skip": skip },
{ "$limit": itensPerPage },
{ '$match': {
$or: [
{ userFrom: user.id_user },
{ userTo: user.id_user }
]
}},
{$unwind : $chatlist},
{$sort : {chatlist.date : -1}},
{$group : {_id : $_id},chatlist : {$push : $chatlist}},
])
.exec(function (err, messages) {
res.send({"success": true, "chatlist": messages, "pages": pages});
});

Related

How to get the total number of occurences of a specific item in every document's array element

I am implementing favoriting/unfavoriting functionality to my express app but I have a problem on how to count the the total number the post has been favorited.
Assuming I have this Schema for Recipe
RecipeSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
maxlength: 30
},
description: {
type: String,
default: ''
},
favoritesCount: {
type: Number,
default: 0
}
})
And Schema for User
const UserSchema = new mongoose.Schema({
username: {
type: String,
minlength: 8,
required: true,
unique: true
},
fullname: {
type: String,
maxlength: 40,
minlength: 4,
required: true
},
password: {
type: String,
required: true,
minlength: 8
}
favorites: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Recipe'
}]
}, { timestamps: true });
And now assuming I have this doc of Users,
How can I count the total number the Recipe ID (5daef9a2761d4b1668214dbc) present in each User doc's favorites array?
[{
username: 'john123',
email: 'john#test.com',
favorites: ['5daef9a2761d4b1668214dbc']
}, {
username: 'jane75',
email: 'jane#test.com',
favorites: []
}, {
username: 'johnwick',
email: 'johnwick#test.com',
favorites: ['5daef9a2761d4b1668214dbc']
}]
// Should yield 2
I looked up for answers but I can't find one. I'm new to mongodb and nodejs so please bear with me. Some answers that I saw are related to Aggregation.
So far I have tried this code. But it just return the number of User documents.
const User = require('./User') // the User model
RecipeSchema.methods.updateFavoriteCount = function() {
return User.count({
favorites: {
$in: [this._id]
}
}).then((count) => {
this.favoritesCount = count;
return this.save();
});
};
You can do it with the help of aggregation and with $size. For more detail, refer to this document.
Your query
db.collection.aggregate([
{
$project: {
username: 1,
email: 1,
totalFavritesCount: {
$cond: {
if: {
$isArray: "$favorites"
},
then: {
$size: "$favorites"
},
else: "NA"
}
}
}
}
])
Result
[
{
"_id": ObjectId("5a934e000102030405000000"),
"email": "john#test.com",
"totalFavritesCount": 1,
"username": "john123"
},
{
"_id": ObjectId("5a934e000102030405000001"),
"email": "jane#test.com",
"totalFavritesCount": 0,
"username": "jane75"
},
{
"_id": ObjectId("5a934e000102030405000002"),
"email": "johnwick#test.com",
"totalFavritesCount": 1,
"username": "johnwick"
}
]
You can also check out the running code in this link.

aggregate base on match in the second collection

I'm using mongodb.
And I have collection Treatments and collection Patients.
I want to find all treatments that their patient.createdBy is equal to the user who asking the data.
So I tried this
const reminders = await Treatment.aggregate([
{
$lookup: {
from: 'patients',
localField: 'patientId',
foreignField: '_id',
as: 'patient'
}
},
{ $project: { reminders: 1, reminderDate: 1 } },
{ $match: { 'patient.createdBy': { $eq: req.user._id } } }
]);
According to some examples that i saw, it's should work like this.
But it's return me an empty array
If I remove the $match its return me object like this
{
"_id": "5d1e64bdc1506a00045c6a6f",
"date": "2019-07-04T00:00:00.000Z",
"visitReason": "wewwe",
"treatmentNumber": 2,
"referredBy": "wewew",
"findings": "ewewe",
"recommendations": "ewew",
"remarks": "wwewewe",
"patientId": "5cc9a50fd915120004bf2f4e",
"__v": 0,
"patient": [
{
"_id": "5cc9a50fd915120004bf2f4e",
"lastName": "לאון",
"momName": "רןת",
"age": "11",
"phone": "",
"email": "",
"createdAt": "2019-05-01T13:54:23.261Z",
"createdBy": "5cc579d71c9d44000018151f",
"__v": 0,
"firstName": "שרה",
"lastTreatment": "2019-08-02T14:20:08.957Z",
"lastTreatmentCall": true,
"lastTreatmentCallDate": "2019-08-04T15:17:35.000Z"
}
]
}
this is patient schema
const patientSchema = new mongoose.Schema({
firstName: { type: String, trim: true, required: true },
lastName: { type: String, trim: true, required: true },
momName: { type: String, trim: true },
birthday: { type: Date },
age: { type: String, trim: true },
lastAgeUpdate: { type: Date },
phone: { type: String, trim: true },
email: { type: String, trim: true },
createdAt: { type: Date, default: Date.now },
createdBy: { type: mongoose.Schema.Types.ObjectId, required: true },
lastTreatment: { type: Date },
lastTreatmentCall: { type: Boolean },
lastTreatmentCallDate: { type: Date }
});
And this is treatment schema
const treatmentSchema = new mongoose.Schema({
date: { type: Date, default: new Date().toISOString().split('T')[0] },
visitReason: { type: String, trim: true },
treatmentNumber: { type: Number, required: true },
referredBy: { type: String, trim: true },
findings: { type: String, trim: true },
recommendations: { type: String, trim: true },
remarks: { type: String, trim: true },
reminders: { type: String, trim: true },
reminderDate: { type: Date },
patientId: { type: mongoose.Schema.Types.ObjectId }
});
what I'm missing
You have vanished your patient field in the second last $project stage. So instead use it at the end of the pipeline. Also you need to cast your req.user._id to mongoose objectId
import mongoose from 'mongoose'
const reminders = await Treatment.aggregate([
{
$lookup: {
from: 'patients',
localField: 'patientId',
foreignField: '_id',
as: 'patient'
}
},
{ $match: { 'patient.createdBy': { $eq: mongoose.Types.ObjectId(req.user._id) } } },
{ $project: { reminders: 1, reminderDate: 1 } }
])
I think you can add using the pipeline, like below
const reminders = await Treatment.aggregate([
{
$lookup: {
from: 'patients',
localField: 'patientId',
foreignField: '_id',
as: 'patient',
pipeline: [{ $match: { 'age': { $eq: "100" } } }]
}
},
]);

MongoDB: A complex query with array input

I'm stuck at finding a solution for the following query.
1) a user can select many categories and subcategories.
2) the user can see all other users how are selected the same categories and subcategories within a certain radius.
Here is the Schema of the user
const userSchema = new Schema(
{
image: { type: String, default: 'NA' },
firstName: { type: String, default: 'first name' },
lastName: { type: String, default: 'last name' },
email: { type: String, lowercase: true, unique: true, trim: true },
password: { type: String, min: 6 },
gender: { type: String, emun: ['male','female','other'] },
about: { type: String, default: 'about you' },
address: {
zipCode: { type: Number, default: 000000 },
place: { type: String, default: 'place' },
street: { type: String, default: 'street' },
country: { type: String, default: 'Country' },
location: {
type: { type: String, default:'Point'},
coordinates: { type:[Number], index:'2dsphere', default:[0,0] }
}
},
interests: [
{
_id : false,
category: {
id: { type: Schema.Types.ObjectId, ref: 'Category' },
name: { type: String }
},
subCategory: [
{
_id : false,
id: { type: Schema.Types.ObjectId, ref: 'Subcategory' },
name: { type: String }
}
]
}
]
}
);
In my controller here is what I tried
homeData: async (req, res, next) => {
const limit = Number(req.params.limit);
const { latitude, longitude, minDistance, maxDistance } = getUserCurrentLocation(req);
const usersWithSameInterests = await User.aggregate([
{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": [longitude, latitude]
},
"distanceField": "distance",
"minDistance": minDistance,
"maxDistance": maxDistance,
"spherical": true,
"query": { "location.type": "Point" }
}
},
{
"$match": { "interests": { "$elemMatch": {name: 'Sports'} }} // hard coded for testing
},
{ "$sort": { "distance": 1 } },
{ "$limit" : limit },
{
"$project": {
"_id": 1,
"image": 1,
"firstName":1,
"lastName":1,
"distance": 1,
"createdAt": 1
}
}
]);
return respondSuccess(res, null, {
newNotification: false,
usersWithSameInterests: usersWithSameInterests
});
},
The response i'm getting is
{
"success": true,
"message": "query was successfull",
"data": {
"newNotification": false,
"usersWithSameInterests": []
}
}
Sample categories and subcategories
Category: Sports
Subcategories: Cricket, Football, Hockey, Tennis
Category: Learning Languages
Subcategories: English, German, Spanish, Hindi
looking forward for much-needed help.
thank you.
It seems that you have a few mismatched columns.
On the $geonear pipeline, the line "query": { "location.type": "Point" } should be: 'query': {'address.location.type': 'Point'}.
And on the $match pipeline, the line { "interests": { "$elemMatch": {name: 'Sports'} } should be 'interests': { '$elemMatch:' {'category.name': 'Sports'} }
Edit:
To match multiple interests on the category and subcategory field, You can use the $in operator on the $match pipeline. Like this:
{
'interests.category.name': { $in: ['Sports'] },
'interests.subCategory.name': {$in: ['Soccer']}
}
It'll return anyone that have Sports in the category name, and Soccer on subcategory name.

Node.js with mongoose delete one array element

I am trying to delete one array element when I click delete button on jade view page.
When clicked, it's going to send selected instructor objected as req.body.
At sever side, it will find courses that contain the instructor objectId.
Any idea for me?
Thank you for reading it.
here is my code:
var id = req.body._id;
clist.find({ instructors: { $in: [id] } }).exec(function (err, result) {
result.forEach(function (obj) {
clist.update(
{ _id: new mongoose.Types.ObjectId(obj._id)},
{ $pull: { instructors : [new mongoose.Types.ObjectId(id)] } }
);
console.log(new mongoose.Types.ObjectId(obj._id) + ' was deleted');
});
});
Schema Clist and ilist:
var instructorlist = mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, required: true },
gender: { type: String, required: true },
DOB: { type: Date, required: true, default: Date.now },
email: { type: String, required: true },
phone: { type: Number, required: true },
address: { type: String, required: true },
dateofstart: { type: Date, required: true},
courses: [{
type: mongoose.Schema.Types.ObjectId,
ref: "clist"
}]
});
var courselist = mongoose.Schema({
coursename: { type: String, required: true },
coursenumber: { type: String, required: true },
coursecredit: { type: Number, required: true },
courseroom: { type: String, required: false },
courseregisteddate: {type: Date, default: Date.now},
students: [{
type: mongoose.Schema.Types.ObjectId,
ref: "slist"
}],
instructors: [{
type: mongoose.Schema.Types.ObjectId,
ref: "ilist"
}]
});
one example for mongodb :
{
"_id": {
"$oid": "591a7a3b391a1842e8a69e23"
},
"coursename": "JDKD",
"coursenumber": "COMP4483",
"coursecredit": 4,
"courseroom": "sdaf",
"instructors": [
{
"$oid": "591a374422a3a13d38c0bbe5"
}
],
"students": [],
"courseregisteddate": {
"$date": "2017-05-16T04:04:11.848Z"
},
"__v": 0
}
When I add instructor objectID in Course.
var newcourse = new clist({
'coursename': req.body.coursename, 'coursenumber': req.body.coursenumber, 'coursecredit': req.body.coursecredit
, 'courseroom': req.body.room, 'instructors': instructors._id
});
Use same operation to find and update multiple
clist.update(
{ instructors: { $in: [id] }},
{ $pull: { instructors : { _id : new mongoose.Types.ObjectId(id) } } }, //or{ $pull: { instructors: mongoose.Types.ObjectId(id) } }
{
multi:true
},
function(error, success){
if(error){
console.log("error",error)
}
console.log("success",success)
});

Mongoose - Add 'is_self' field in aggregate

I'm building a chat app, that should retrieve all new messages from MongoDB, grouped in to conversations. But each message should have a new 'is_self' field
Edit:
The 'is_self' field contains a boolean for if the message if from the user.
so pseudo:
is_self: {$cond: {if: {message.sender == MYID)}, then: true, else: false}
So lets say I have Message model
var MessageSchema = new Schema({
conversation_id:{type: mongoose.Schema.ObjectId, ref: 'Conversation', required: true},
message: {type: String, required: true},
sender: {type: mongoose.Schema.ObjectId, ref: 'User'},
created: {type: Date, default: Date.now},
read: {type: Boolean, default: false}
});
And a Conversation model
var ConversationSchema = new Schema({
from: {type: mongoose.Schema.ObjectId, ref: 'User', required: true},
to: {type: mongoose.Schema.ObjectId, ref: 'User', required: true},
last_changed: {type: Date, default: Date.now},
created: {type: Date, default: Date.now}
});
Now I try to do an Aggregate, to load all messages that are inside a conversation_id array and are created > last_checked date...
So it looks like this:
mongoose.model("Message").aggregate([
// First find all messages
{
$match: {
$and: [{conversation_id: {$in: idArray}}, {created: {$gt: lastChecked}}]
}
},
// Add is self field
{
$group: {
_id: $_id,
$is_self: {
$cond: {'if(message.sender == MYID then true else false': '??'}
}
}
},
// Sort by date
{$sort: {created: -1}},
// Then group by conversation
{
$group: {
_id: '$conversation_id',
messages: {
$push: '$$ROOT'
},
}
}
// TODO: find users for unknown conversation
/*,
{
$project: {
user: {
$or: [{conversation_id: {$in: knownConversations}}]
}
}
}*/
])
I tried with $cond and if / else statement but Mongo doesn't allow that..
Thanks!
Simple usage of the $eq operator which returns boolean. Also $push will take any object format you throw at it:
var senderId = // whatever;
mongooose.model("Message").aggregate([
{ "$match": {
"conversation_id": { "$in": idArray },
"created": { "$gt": lastChecked }
}},
{ "$group": {
"_id": "$conversation_id",
"messages": {
"$push": {
"message": "$message",
"is_self": {
"$eq": [ "$sender", senderId ]
}
}
}
}}
// whatever else
],function(err,results) {
})
If you want, then combine with $cond to alternately add "is_self" only when detected:
{ "$group": {
"_id": "$conversation_id",
"messages": {
"$push": {
"$cond": [
{ "$eq": [ "$sender", senderId] },
{
"message": "$message",
"is_self": true
},
{
"messsage": "$message"
}
]
}
}
}}

Resources