Home > Blockchain >  Group by field and have sub-group-by for each result
Group by field and have sub-group-by for each result

Time:12-17

I don't really know how to title this so that it describes what I'm trying to do. Easier to just show the collections and my desired result.

There's a permissions collection that contains permissions and the users or groups that are assigned that permission for a given resource.

permissions

[
    {
      "_id": 1,
      "resource": "resource:docs/61",
      "permissions": [
        {
          "permission": "role:documentOwner",
          "users": [
            "user:abc",
            "user:def",
            "group:abc",
            "group:bff"
          ]
        },
        {
          "permission": "document.read",
          "users": [
            "user:xxx"
          ]
        },
        {
          "permission": "document.update",
          "users": [
            "user:xxx"
          ]
        }
      ]
    },
    {
      "_id": 2,
      "resource": "resource:docs/38",
      "permissions": [
        {
          "permission": "role:documentOwner",
          "users": [
            "user:abc",
            "user:def",
            "group:abc",
            "group:bff"
          ]
        },
        {
          "permission": "document.read",
          "users": [
            "user:xxx"
          ]
        },
        {
          "permission": "document.update",
          "users": [
            "user:xxx"
          ]
        }
      ]
    }
]

And a groups collection that assigns users to a group.

groups

[
    {
      "_id": 1,
      "id": "abc",
      "name": "Test Group",
      "users": [
        "cpo",
        "yyy",
        "xxx"
      ]
    },
    {
      "_id": 2,
      "id": "bff",
      "name": "Something",
      "users": [
        "xxx"
      ]
    }
 ]

I'm trying to group by users and show their associated groups and resource permissions. I'm almost there but not sure how to group permissions with their associated resource. Here's my desired result...

Desired Result

{
    "_id": "xxx",
    "groups": [
      {
        "id": "abc",
        "name": "Test Group"
      },
      {
        "id": "bff",
        "name": "Something"
      }
    ],
    "permissions": [
      {
        "permissions": ["document.update", "document.read"],
        "resource": "resource:docs/61"
      },
      {
        "permissions": ["document.update", "document.read"],
        "resource": "resource:docs/38"
      }
    ]
 }

This is my current attempt. I've setup a Mongo Playground where I've been trying to get this to work. I'm stuck on the last part of grouping the permissions with their resource.

db.permissions.aggregate([
  {
    "$unwind": "$permissions"
  },
  {
    "$unwind": "$permissions.users"
  },
  {
    $match: {
      $expr: {
        $eq: [
          0,
          {
            $indexOfCP: [
              "$permissions.users",
              "user:"
            ]
          }
        ]
      }
    }
  },
  {
    "$lookup": {
      "from": "groups",
      "let": {
        "u": {
          "$arrayElemAt": [
            {
              "$split": [
                "$permissions.users",
                ":"
              ]
            },
            1
          ]
        }
      },
      "pipeline": [
        {
          "$match": {
            "$expr": {
              "$in": [
                "$$u",
                "$users"
              ]
            }
          }
        },
        {
          "$project": {
            "_id": 0,
            "id": 1,
            "name": 1,
            
          }
        }
      ],
      "as": "groupLookup"
    }
  },
  {
    "$group": {
      "_id": {
        "$arrayElemAt": [
          {
            "$split": [
              "$permissions.users",
              ":"
            ]
          },
          1
        ]
      },
      "groups": {
        "$addToSet": "$groupLookup"
      },
      "permissions": {
        "$addToSet": {
          "permissions": "$permissions.permission",
          "resource": "$resource"
        }
      }
    }
  },
  {
    "$addFields": {
      "groups": {
        "$reduce": {
          "input": "$groups",
          "initialValue": [],
          "in": {
            "$concatArrays": [
              "$$value",
              "$$this"
            ]
          }
        }
      },
      
    }
  }
])

CodePudding user response:

I believe this will give you the desired output:

 db.foo.aggregate([
    {$unwind: "$permissions"},
    {$unwind: "$permissions.users"},

    // Only take 'user:' style permissions, not 'group:'                                     
    {$match: {$expr: {$eq:[0,{$indexOfCP:["$permissions.users","user:"]} ] } }},

    // Organize by user (after hacking off the user: prefix) and resource:                   
    {$group: {
        _id: {
            u: {$arrayElemAt:[{$split:['$permissions.users',':']},1]},
            r:"$resource"
        },
        p: {$push: "$permissions.permission"}
    }},

    // ... and reorganize.  I suspect this is the technique the OP was 
    // looking for:                                                            
    {$group: {_id: '$_id.u', p: {$push: {permissions: '$p', resource: '$_id.r'}}}},

    // Now, look for user ID in the $users array but only emit id and name:                       
    {$lookup: {"from": "foo2",
               let: { uid: "$_id" },
               pipeline: [
                   {$match: {$expr: {$in: [ '$$uid',"$users" ]} }},
                   {$project: {_id:false, id:true, name:true}}
               ],
               as: "grps"
              }}
]);
  • Related