Reputation: 1357
Scope of Project: I am making a newsfeed, like Facebook. And I have a like button feature that will add a like to a post item when clicked.
Problem Lets say I have two posts, post A and post B.
If I like post A and like again then my server returns "User already liked post", okay that works.
However, if I like post B then the server returns the same "User already liked post"
Query:
Feed.findOne({
owner: req.body.authorId,
$and: [
{
"posts.likes.likeList": {
$elemMatch: { user: req.user._id }
}
},
{ posts: { $elemMatch: { _id: req.body.postId } } }
]
}).then(checkedFeed => {
if (checkedFeed) {
return res.status(400).json({ Error: "User has already liked post" });
}
What I think the problem is When the user likes post B while post A is liked, the $and
operator matches the req.user._id
with the posts.likes.likeList
of post A in the first index of the $and
array. Then, it matches the _id
of posts
in the second index of the $and
array. And then returns the entire feed as a match.
So, if I am correct in this regards, how do I write a query that matches the post id (the second index of the $and
array) with the post.likes.likeList
list of users?
Schema
{
owner: {
type: Schema.Types.ObjectId,
ref: "userType"
},
posts: [
{
likes: {
totalLikes: { type: Number, default: 0 },
likeList: [
{
user: { type: Schema.Types.ObjectId, ref: "User" },
avatar: { type: String },
name: { type: String },
date: {
type: Date,
default: Date.now
}
}
]
}
});
Test Data*
{
//POST B <-------
"_id" : ObjectId("5d0a61bc5b835b2428289c1b"),
"owner" : ObjectId("5c9bf6eb1da18b038ca660b8"),
"posts" : [
{
"likes" : {
"totalLikes" : 0,
"likeList" : []
},
"_id" : ObjectId("5d0a61bc5b835b2428289c1c"),
"postBody" : "Test text only",
"author" : {
"userType" : "User",
"user" : ObjectId("5c9bf6eb1da18b038ca660b8"),
"name" : "Amari DeFrance",
"avatar" : "https://stemuli.blob.core.windows.net/stemuli/profile-picture-e1367a7a-41c2-4ab4-9cb5-621d2008260f.jpg"
}
},
{
//Post A <------
"likes" : {
"totalLikes" : 1,
"likeList" : [
{
"_id" : ObjectId("5d0a66efbac13b4ff8b3b1c8"),
"user" : ObjectId("5c9bf6eb1da18b038ca660b8"),
"avatar" : "https://stemuli.blob.core.windows.net/stemuli/profile-picture-e1367a7a-41c2-4ab4-9cb5-621d2008260f.jpg",
"name" : "Amari DeFrance",
"date" : ISODate("2019-06-19T16:46:39.177Z")
}
]
},
"postBody" : "Test photo",
"author" : {
"userType" : "User",
"user" : ObjectId("5c9bf6eb1da18b038ca660b8"),
"name" : "Amari DeFrance",
"avatar" : "https://stemuli.blob.core.windows.net/stemuli/profile-picture-e1367a7a-41c2-4ab4-9cb5-621d2008260f.jpg"
},
"date" : ISODate("2019-06-19T16:25:26.123Z")
}
],
"__v" : 3
}
New Query per suggested answer
Feed.aggregate([
{
$match: {
$expr: {
$and: [
{
$eq: ["$owner", req.body.authorId]
},
{
$anyElementTrue: {
$map: {
input: "$posts",
in: {
$and: [
{
$eq: ["$$this._id", req.body.postId]
},
{
$anyElementTrue: {
$map: {
input: "$$this.likes.likeList",
as: "like",
in: {
$eq: ["$$like.user", req.user._id]
}
}
}
}
]
}
}
}
}
]
}
}
}
]).then(checkedFeed => {
if (checkedFeed.length !== 0) {
return res.status(400).json({ Error: "User has already liked post" });
}
MongoDB query test with post B having liked post from the user
Upvotes: 1
Views: 50
Reputation: 49945
You can use $map to transform likeList
into an array of boolean values. Then you can use $anyElementTrue to check if any like belongs to particular user. Then you need to do the same trick for posts
(outer array), combining both conditions with $and
will bring you desired results, try:
Feed.aggregate([
{
$match: {
$expr: {
$and: [
{ $eq: [ "$owner", req.body.authorId ] },
{
$anyElementTrue: {
$map: {
input: "$posts",
in: {
$and: [
{ $eq: [ "$$this._id", req.body.postId ] },
{
$anyElementTrue: {
$map: {
input: "$$this.likes.likeList",
as: "like",
in: { $eq: [ "$$like.user", req.user._id ] }
}
}
}
]
}
}
}
}
]
}
}
}
])
Upvotes: 1