Snoopy
Snoopy

Reputation: 1357

Mongo query operation for matching two elements within same index

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

Answers (1)

mickl
mickl

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

Working example

Upvotes: 1

Related Questions