简体   繁体   中英

How to filter a MongoDB/Mongoose query based upon referenced subdocuments values

So I am currently working on cloning some app concepts (for learning purposes) and I have built a basic social media app that has users, posts, & allows users to block users if they don't want to share data. But my question is, can I use referenced documents values/data (a populated user's array of blocked users) to filter out the parent document (the post)? Hope this makes sense.

So far, here are the schemas (trimmed down of course):

// User Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;    
const UserSchema = new Schema({
    email: {
        type: String,
        required: [true, "Please provide an email"],
        unique: true
    },
    password: {
        type: String,
        minlength: 8
    },
    blockedUsers: {
        type: [{
            type: Schema.Types.ObjectId,
            ref: 'User'
        }]
    }
});

// Post Schema
const PostSchema = new Schema({
    author: {
        type: Schema.Types.ObjectId,
        ref: 'User'
    },
    dateCreated: {
        type: Date,
        default: Date.now()
    },
    body: {
        type: String,
        required: true,
        trim: true,
        maxlength: 1200
    }
});

const User = mongoose.model('User', UserSchema);
const Post = mongoose.model('Post', PostSchema);

Then my query I have so far which only filters out the subdocument and in my tests is returning a null author to my post which is not exactly the functionality I was hoping for. To clarify, it still returns the right post that was queried, but it does not populate the 'author' field and instead sets the author field to null .

router.get('/p/:postID', catchAsyncErr(async (req, res, next) => {
    // Find post and make sure the user requesting the post is not blocked by the author
    const post = await Post.findById(req.params.postID).populate({
        path: 'author',
        select: 'blockedUsers',
        match: {
            blockedUsers: {
                $ne: req.user.id // The user ID stored in the JSON Web token
            }
        }
    });

    res.status(200).json({
        status: 'Success',
        statusCode: 200,
        message: `Post "${req.params.postID}" found`,
        data: post
    })
}));

I tried using aggregation and honestly am still trying to fully wrap my head around the $lookup concept. But I have a strange feeling that that should be where I am testing more...

Basically, I am really trying to hide the post and return a 404 if the author (user) has the requesting user on the 'blockedUsers' list. I understand I can do this using regular if/else statements but I was really hoping to be able to do this inside a query. Further more, I want to be able to implement this same concept on a users home feed if you will. If they are blocked by a user, the user who blocked them would not have their post show up in their feed.

Thank you for any of your insights on this!

You can use aggregation pipeline to do that. You need $lookup for join (populate) author and $match for filter your user.

$match is very similar to your first parameter in find() .

$lookup is similar to join in SQL if you familiar with.

For setting up $lookup you need four parameters.

from : Which is your destination collection (Here is users ) localField : Which your foreign key field name (Here author ) foreignField : Which is destination collection field (Here _id ) as : Name after join (Here I use author )

Here is the snippet:

const post = Post.aggregate([{
  $match: {
    _id: req.params.postID
  }
}, 
{
  $lookup: {
    from: 'users',
    localField: 'author',
    foreignField: '_id',
    as: 'author'
  }
}])

Then your post.author.blockedUsers should work fine.

If you want to retrieve specific blockedUsers you can use aggregation like this:

const post = Post.aggregate([{
  $match: {
    _id: req.params.postID
  }
}, 
{
  $lookup: {
    from: 'users',
    'let': {authorId: "$author"},
    pipeline: [{
      $match: {
        $expr: {
          $and: [{
            $eq: ['$_id', '$$authorId']
          }, {
            $ne: ['$blockedUsers', req.user.id]
          }]
        }
      }
    }
    as: 'author'
  }
}])

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM