Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modelling for friends schema in mongoose?

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

enter image description here

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...

like image 617
Ashh Avatar asked May 16 '18 05:05

Ashh


2 Answers

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": mongoose.Types.ObjectId("5afaab572c4ec049aeb0bcba"),
        "$expr": { "$in": [ "$_id", "$$friends" ] }
      }},
      { "$project": { "status": 1 } }
    ],
    "as": "friends"
  }},
  { "$addFields": {
    "friendsStatus": {
      "$ifNull": [ { "$min": "$friends.status" }, 0 ]
    }
  }}
])
like image 121
Ashh Avatar answered Sep 21 '22 11:09

Ashh


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.

like image 29
Veraprot Avatar answered Sep 21 '22 11:09

Veraprot