Search code examples
javascriptnode.jsmongodbmongoosenodes

Return fields from different collection Mongoose


Imagine a function that finds users by their name and returns them.

 User.aggregate(
        [
            { $sort: { userFirstName: 1, userLastName: 1 } },
            {
                $addFields: {
                    firstLastName: { $concat: ['$userFirstName', ' ', '$userLastName'] },
                    lastFirstName: { $concat: ['$userLastName', ' ', '$userFirstName'] }
                }
            },
            {
                $match: $match // Set from above with match crit
            },
            {
                $group: {
                    _id: null,
                    total: { $sum: 1 },
                    data: {
                        $push: {
                            '_id': '$_id',
                            'userFirstName': '$userFirstName',
                            'userLastName': '$userLastName',
                            'userProfileImage': '$userProfileImage',
                            'userVihorCategory': '$userVihorCategory'
                        }
                    }
                }
            },
            {
                $project: {
                    total: 1,
                    data: { $slice: ['$data', start, limit] }
                }
            }
        ]
    ).exec((errAgg, results) => {...

This works, it splices them and returns them correctly. There is another collection that tracks user connections.

{
    user: { type: Schema.Types.ObjectId, ref: 'User' },
    userConnection: { type: Schema.Types.ObjectId, ref: 'User' },
    userConnectionStatus: {
        type: String,
        enum: ['following', 'blocked', 'requested']
    }
}

Eg User: me, userConnection: 'someone', userConnectionStatus: 'following'

What I am trying to achive is to return 2 more fields, 1. My userConnectionStatus to him 2. His userConnectionStatus to me

And not to return users who have blocked me. What is the best approach when it comes to this DB structure.

Thank you for your time


Solution

  • Preventing blocked users was solved by selecting all blocked users, and adding $nin in match inside aggregate.

    For connection status, I have resolved the problem by adding 2 virtual fields to User.

    UserMongoSchema.virtual('userConnectionStatus', {
      ref: 'UserConnection',
      localField: '_id',
      foreignField: 'user',
      justOne: true
    });
    UserMongoSchema.virtual('connectionStatus', {
      ref: 'UserConnection',
      localField: '_id',
      foreignField: 'userConnection',
      justOne: true
    });
    

    And populating them on results

    ...
    .exec((errAgg, results) => {
          User.populate(results[0].data, [ 
                { path: 'userConnectionStatus', match: { userConnection: req.userCode }, select: 'userConnectionStatus' },
                { path: 'connectionStatus', match: { user: req.userCode }, select: 'userConnectionStatus' },
            ], (errPop, populateResponse) => {
                if (errPop) { return next(errPop); }
    
                populateResponse = populateResponse.map((row) => {
                    row['userConnectionStatus'] = row.userConnectionStatus ? row.userConnectionStatus.userConnectionStatus : null;
                    row['connectionStatus'] = row.connectionStatus ? row.connectionStatus.userConnectionStatus : null;
                    return row;
                });
    ...
    

    Looking at the order of actions, I think this won't affect performance since I am running populate only on those matched top X (max 100) results.

    I won't mark this as Answer yet. If you have any opinion about if this is bad practice or if there is a better way of doing it, feel free to comment.