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'
}
}
]
}
]
Related
I have a collection in Mongoose called Points where I have a history of all points of a user
This is the point schema
const PointSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
reason: {
type: String,
required: true,
},
points: {
type: Number,
required: true,
},
time: {
type: Date,
default: Date.now,
},
});
I want a JSON output like this
{
"points": 30,
"history": [
{ Point object },
{ Point object },
{ Point object },
]
}
How can I get the sum of all the points queried by a particular user, and get the output as above if I have the particular user's id?
You could do a simple aggregation where you first $match the user-id, then $sum all the points and finally push each document to the history array:
db.collection.aggregate([
{
"$match": {
user: "<user-id-to-match>"
}
},
{
"$group": {
"_id": "$user",
"points": {
$sum: "$points"
},
history: {
"$push": "$$ROOT"
}
}
},
{
$project: {
_id: 0
}
}
])
Here's an example I've created on mongoplayground: https://mongoplayground.net/p/Q_TtcI_dkZu
I'm trying to query a MongoDB database via mongoose to updateMany the fields of my database. I suppose that the first request is correct because mongoose doesn't fire any error, but for the nested schemas, I'm getting the following error.
My goal is to delete the occurences of the userTag in friends and remove the friendRequestsSent when userTarget equals userTag, friendRequestsReceived when userRequest equals userTag and notification when data equals userTag.
Here are the schemas of my Model
const NotificationSchema = new Schema({
title: String,
type: Number,
icon: String,
data: String,
createdAt: { type: Date, default: Date.now },
})
const FriendRequestSchema = new Schema({
userRequest: { type: String, required: true },
userTarget: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
})
const UserSchema = new Schema({
tag: { type: String, required: true, unique: true },
friendRequestsSent: { type: [FriendRequestSchema] },
friendRequestsReceived: { type: [FriendRequestSchema] },
friends: { type: [String] },
notifications: { type: [NotificationSchema] },
})
The request
const updateResponse = await User.updateMany(
{
friends: { $elemMatch: { $eq: userTag } },
friendRequestsSent: {
userTarget: {
$elemMatch: { $eq: userTag },
},
},
friendRequestsReceived: {
userRequest: {
$elemMatch: { $eq: userTag },
},
},
notifications: {
data: {
$elemMatch: { $eq: userTag },
},
},
},
{
$pull: {
friends: userTag,
friendRequestsSent: { userTarget: userTag },
friendRequestsReceived: { userRequest: userTag },
notifications: { data: userTag },
},
}
)
The error
Error while deleting the user account: Cast to String failed for value "{ '$elemMatch': { '$eq': '0eQzaAwpt' } }" at path "userRequest" for model "User"
The userRequest field in friendRequestsReceived is type String, not array so $elemMatch will not work. Also, you don't need to use $elemMatch because you specify only a single condition in the $elemMatch expression as it says in the docs:
If you specify only a single condition in the $elemMatch expression, you do not need to use $elemMatch.
In your case, you just need to do something like (details here):
await User.updateMany({
friends: userTag,
"friendRequestsSent.userTarget" : userTag,
"friendRequestsReceived.userRequest": userTag,
"notifications.data": userTag
}...
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
]
}
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"
}
]
}
}
}}
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}}