Mongoose - Add 'is_self' field in aggregate - node.js

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"
}
]
}
}
}}

Related

mongodb/mongoose addToSet adds object to array with the same id

So the issue that I'm having is that I want to like user comment only one time, currently im using addToSet operator, since by definition it doesn't add value if that value is already present.
But in my case it adds, probably because I am adding object instead of value and when I add mongo generates _id?
This is my event model:
creator: {
type: Schema.Types.ObjectId,
ref: 'User'
},
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
text: {
type: String,
required: true
},
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}
]
}
]
}
And this is my addLike function:
commentLike: async (req, res) => {
console.log('working', req.params.id, req.params.idas, req.params.commentID);
Events.findOneAndUpdate(
{ _id: req.params.idas, comments: { $elemMatch: { _id: req.params.commentID } } },
{ $addToSet: { 'comments.$.likes': { user: req.params.id } } },
(result) => {
res.json(result);
}
);
}
My likes array looks like this when I add like:
"likes" : [
{
"user" : ObjectId("5b53442c1f09f525441dac31"),
"_id" : ObjectId("5b54a5263e65324504035eac")
},
{
"user" : ObjectId("5b4b4725a3a5583ba8f8d513"),
"_id" : ObjectId("5b54a5bb3e65324504035eb0")
},
]
You can follow this
db.collection.update(
{ "_id": req.params.idas, "comments": { "$elemMatch": { "_id": req.params.commentID, "likes.user": { "$ne": req.params.id } } } },
{ "$push": { "comments.$.likes": { "user": req.params.id } } }
})
And if you just started with your project then you should follow JohnnyHK opinion and make your array some thing like this to make $addToSet workable
likes: [{ type: Schema.Types.ObjectId, ref: 'User' }]
Another option is to change your schema definition to add "_id: false" to prevent the _id field from being generated for subdocs in the array.
In this case, $addToSet will work as you expect, even with nested subdocs as long as your doc exactly matches.
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
text: {
type: String,
required: true
},
likes: [
{
_id: false, //add this
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}
]
}
]

Check if sub document is found in $project

I am trying to find all the trips provided by a company, grouped by the driver of the bus, and check if a given passenger was part of the trip.
Content is an array that can have reference to multiple models: User, Cargo, etc.
I can somewhat achieve my desired result using:
traveled: { $in: [ passengerId, "$content.item" ] },
But i want to confirm that the matched id is indeed a 'User'(passenger). I have tried:
traveled: { $and: [
{ $in: [ passengerId, "$content.item" ] },
{ $in: [ `Passenger`, "$content.kind" ] },
]},
But it also matches if the passed id has a kind of 'Cargo' when there is another content with a kind of 'User' is inside the array.
// Trip
const schema = Schema({
company: { type: Schema.Types.ObjectId, ref: 'Company', required: false },
driver: { type: Schema.Types.ObjectId, ref: 'User', required: true },
description: { type: String, required: true },
....
content: [{
kind: { type: String, required: true },
item: { type: Schema.Types.ObjectId, refPath: 'content.kind', required: true }
}]
});
const Trip = mongoose.model('Trip', schema, 'trips');
Trip.aggregate([
{ $match: { company: companyId } },
{
$project: {
_id: 1,
driver: 1,
description: 1,
traveled: { $in: [ passengerId, "$content.item" ] },
// traveled: { $and: [
// { $in: [ passengerId, "$content.item" ] },
// { $in: [ `Passenger`, "$content.kind" ] },
// ]},
}
},
{
$group : {
_id: "$driver",
content: {
$push: {
_id: "$_id",
description: "$description",
traveled: "$traveled",
}
}
},
}
]).then(console.log).catch(console.log);
There is no $elemMatch operator in $project stage. So to use mimic similar functionality you can create $filter with $size being $gt > 0.
Something like
"traveled":{
"$gt":[
{"$size":{
"$filter":{
"input":"$content",
"as":"c",
"cond":{
"$and":[
{"$eq":["$$c.item",passengerId]},
{"$eq":["$$c.kind","User"]}
]
}
}
}},
0
]
}

Mongoose find last messages grouping by two fields

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

Mongoose aggregate get total numbers of members in a group model

I have a model like this :
var field = {
createdAt: {type: Date, default: Date.now()},
updatedAt: {type: Date, default: Date.now()},
pic : {type: String, default: 'http://lorempixel.com/400/400/abstract/', writable: true},
thumb : {type: String, default: 'http://lorempixel.com/100/100/abstract/', writable: true},
name: {type: String},
description: {type: String},
isPublic: {type: Boolean, default: true},
members: [{type: Schema.Types.ObjectId, ref: 'Member'}],
}
Im using this code below to get the total count of the Member's ID in members field.
Group.aggregate([
{$match: {_id:req.params.group_id}},
{$group: {
_id: '$members',
count: {$sum: 1}
}}
], function (err, count) {
res.send(count);
});
But it returns and empty array [] how do I get the proper count?
Thanks.
Mongoose does not "autocast" to OjectId or any other schema type within the aggregation pipeline. So your operation returns nothing since it matches nothing.
Also, that would not be the approach to counting members of an array.
Better to use the $size option instead to count array members per document:
Group.aggregate([
{ "$match": { _id: ObjectId(req.params.group_id) }},
{ "$group": {
"_id": null,
"count": { "$sum": { "$size": "$members" } }
}}
], function (err, count) {
res.send(count);
});
And $group with the _id for the appropriate roll-up, or null as used here for all items in the collection.
If you wanted the "count of each distinct member" then you would process with $unwind across document(s) instead:
Group.aggregate([
{ "$match": { _id: ObjectId(req.params.group_id) }},
{ "$unwind": "$members" },
{ "$group": {
"_id": "$members",
"count": { "$sum": 1 }
}}
], function (err, count) {
res.send(count);
});

How can i use MONGODB aggregation framework with nested external document - Mongoose and NodeJS

I have these Mongoose schemes:
// User schema
exports.User = new Schema({
name: {
type: String,
required: true
},
home: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}]
});
// Post schema
exports.Post = new Schema({
likes: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
author: {
id: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
name: {
type: String,
required: true
},
shortId: String, // this is User id
},
created: {
type: Date,
default: Date.now,
required: true
}
});
// THE DATA IN THE DATABASE
// User
{"name" : "Mio Nome",
"home" : [
ObjectId("533af14b994c3647c5c97338")
]}
// Post
{ "author" : {
"id" : ObjectId("533af14b994c3647c5c97338"),
"name" : "AutoreDelPost"
},
"likes" : [
ObjectId("533af14b994c3647c5c97337"),
ObjectId("533af14b994c3647c5c97339"),
ObjectId("533af14b994c3647c5c97340")
]
}
And i want to get from users the posts in home field and count how many likehave one user
With this code i can show all posts in home whit populate, but i can't count likes.
req.db.User.find({
_id: req.user._id //req.user is my test user
}, {
home: 1
})
.limit(200)
.populate('home')
.exec(function (err) {
if (!err) {
return res.json(200)
}
return res.json(500, err)
});
// output
[
{
"_id": "533af0ae994c3647c5c97337",
"name" : "Mio Nome"
"home": [
{
"_id": "533b004e6bcb9105d535597e",
"author": {
"id": "533af14b994c3647c5c97338",
"name": "AutoreDelPost"
},
"likes": [] // i can't see like and i can't count they
}
]
I tryed to use aggregate, to count etc but i can't see the posts getting populated but their _id
req.db.User.aggregate({
$match: {
_id: req.user._id
}
}, {
$project: {
home: 1
}
}, {
$unwind: "$home"
}).exec(function (err, home) {
if (!err) {
return res.json(200, home)
}
return res.json(500, err)
});
// output
[
{
"_id": "533af0ae994c3647c5c97337",
"home": "533b004e6bcb9105d535597e"
},
{
"_id": "533af0ae994c3647c5c97337",
"home": "533b004e6bcb9105d5355980"
},
{
"_id": "533af0ae994c3647c5c97337",
"home": "533b004f6bcb9105d5355982"
},
{
"_id": "533af0ae994c3647c5c97337",
"home": "533b004f6bcb9105d5355984"
},
{
"_id": "533af0ae994c3647c5c97337",
"home": "533b00506bcb9105d5355986"
}
]
QUESTION: I want to get from users the posts in home field and count how many like a user has
Perhaps you can store your data more denormalized and add a counter field which is incremented on each new "like". See http://cookbook.mongodb.org/patterns/votes/. Something like:
update = {'$push': {'voters': user_id}, '$inc': {vote_count: 1}}

Resources