Modelling for friends schema in mongoose?

2020-02-05 11:26发布

问题:

How do I model my mongoose schema to get these three buttons when I am on the other users profile?

  1. Add Friend
  2. Requested
  3. Friends

My users schema

const schema = new Mongoose.Schema({
  firstName: { type: String, default: '', trim: true },
  lastName: { type: String, default: '', trim: true },
}, { timestamps: true })

I could not find the correct modelling for this... And also please suggest the aggregation after modelling...

回答1:

So Finally I made it and I think it is probably the best way to do it with mongodb and mongoose

1. Create a model for users.

    var Schema = mongoose.Schema
    const usersSchema = new Schema({
      firstName: { type: String, required: true },
      lastName: { type: String, required: true },
      friends: [{ type: Schema.Types.ObjectId, ref: 'Friends'}]
    }, {timestamps: true})
    module.exports = mongoose.model('Users', usersSchema)

2. Create a model for friends having enums for accepted, rejected, pending and requested.

    const friendsSchema = new Schema({
      requester: { type: Schema.Types.ObjectId, ref: 'Users'},
      recipient: { type: Schema.Types.ObjectId, ref: 'Users'},
      status: {
        type: Number,
        enums: [
            0,    //'add friend',
            1,    //'requested',
            2,    //'pending',
            3,    //'friends'
        ]
      }
    }, {timestamps: true})
    module.exports = mongoose.model('Friends', friendsSchema)

3. Now api calls --> Lets say we have two users UserA and UserB... So when UserA requestes UserB to be a friends at that time we make two documents so that UserA can see requested and UserB can see pending and at the same time we push the _id of these documents in user's friends

    const docA = await Friend.findOneAndUpdate(
        { requester: UserA, recipient: UserB },
        { $set: { status: 1 }},
        { upsert: true, new: true }
    )
    const docB = await Friend.findOneAndUpdate(
        { recipient: UserA, requester: UserB },
        { $set: { status: 2 }},
        { upsert: true, new: true }
    )
    const updateUserA = await User.findOneAndUpdate(
        { _id: UserA },
        { $push: { friends: docA._id }}
    )
    const updateUserB = await User.findOneAndUpdate(
        { _id: UserB },
        { $push: { friends: docB._id }}
    )

4. If UserB acceptes the request

    Friend.findOneAndUpdate(
        { requester: UserA, recipient: UserB },
        { $set: { status: 3 }}
    )
    Friend.findOneAndUpdate(
        { recipient: UserA requester: UserB },
        { $set: { status: 3 }}
    )

5. If UserB rejectes the request

    const docA = await Friend.findOneAndRemove(
        { requester: UserA, recipient: UserB }
    )
    const docB = await Friend.findOneAndRemove(
        { recipient: UserA, requester: UserB }
    )
    const updateUserA = await User.findOneAndUpdate(
        { _id: UserA },
        { $pull: { friends: docA._id }}
    )
    const updateUserB = await User.findOneAndUpdate(
        { _id: UserB },
        { $pull: { friends: docB._id }}
    )

6. Get all friends and check whether the logged in user is friend of that user or not

User.aggregate([
  { "$lookup": {
    "from": Friend.collection.name,
    "let": { "friends": "$friends" },
    "pipeline": [
      { "$match": {
        "recipient": ObjectId("5afaab572c4ec049aeb0bcba"),
        "$expr": { "$in": [ "$_id", "$$friends" ] }
      }},
      { "$project": { "status": 1 } }
    ],
    "as": "friends"
  }},
  { "$addFields": {
    "friendsStatus": {
      "$ifNull": [ { "$min": "$friends.status" }, 0 ]
    }
  }}
])


回答2:

A little late for this question, but here's my solution:

  1. Create a "self-referential" model for users:
const Schema = mongoose.Schema;

// Create Schema
const UserSchema = new Schema({
   firstName: { 
      type: String, 
      required: true 
   },
   lastName: { 
      type: String, 
      required: true 
   },
   friends: [
      {
         user: {
           type: Schema.Types.ObjectId,
           ref: 'users',
         },
         status: Number,
         enums: [
           0,    //'add friend',
           1,    //'requested',
           2,    //'pending',
           3,    //'friends'
         ]
      }
   ]
})

Now if you want to query for friends you can use an aggregate function and match all users that are in the friends list:

exports.getFriends = async (req, res) => {
  let {id} = req.params
  let user = await User.aggregate([
    { "$match": { "_id": ObjectId(id) } },
    { "$lookup": {
      "from": User.collection.name,
      "let": { "friends": "$friends" },
      "pipeline": [
        { "$match": {
          "friends.status": 1,
        }},
        { "$project": { 
            "name": 1, 
            "email": 1,
            "avatar": 1
          }
        }
      ],
      "as": "friends"
    }}, 
  ])

  res.json({
    user
  })
}

One of the pro's to this approach rather than creating a Friendship join table is that you can make smaller queries that can be a bit less expensive to make. Also it seemed more intuitive to me. However I'm pretty new to mongo so I'm not really sure what the best practices are.