Javascript 在mongoDB中创建firebase样式的权限层次结构

Javascript 在mongoDB中创建firebase样式的权限层次结构,javascript,node.js,mongodb,mongodb-query,aggregation-framework,Javascript,Node.js,Mongodb,Mongodb Query,Aggregation Framework,刚接触MongoDB,并使用NodeJS,我尝试根据用户的权限级别检索和更新项目,这些权限级别也存储在集合中 我了解如何获取所有项目: const collection = db.collection('events'); // Find some documents collection.find({}).toArray(function(err, docs) { assert.equal(err, null); console.log("Found the foll

刚接触MongoDB,并使用NodeJS,我尝试根据用户的权限级别检索和更新项目,这些权限级别也存储在集合中

我了解如何获取所有项目:

 const collection = db.collection('events');
  // Find some documents
  collection.find({}).toArray(function(err, docs) {
    assert.equal(err, null);
    console.log("Found the following records");
    console.log(docs)
    callback(docs);
  });
但考虑到以下数据结构,我想知道:

{
    events: {
        1: {
            id: 1,
            name: "first event",
        },
        2: {
            id: 2,
            name: "2nd event",
        },
        3: {
            id: 3,
            name: "3rd event",
        },
    },
    permissions: {
        events: {
            1: {
                user1: "read",
                user2: "write",
            },
            2: {
                user2: "write",
            },
            3: {
                user1: "readwrite",
                user2: "write",
            },
        },
    },
 }
如果您是以User1身份登录的,则上述获取代码只能获取User1根据指定权限具有读取权限的相关事件


对于更新、删除和插入等CRUD,我可以运行单独的查询,查看用户是否具有所需的访问权限。然而,get查询很常见,IMHO应该很简单,所以我想知道如何优雅地完成此过滤?

首先,我将简化您的数据模型,因为有一件事情可能会在以后的过程中对您造成不利影响。您不应该像在事件文档中那样使用值作为键。显然,这些都是数组,数组元素已经有了一个可以引用的id

此外,您的数据模型(以及随后的我的派生模型)只适用于几千个事件,因为存在一个。为了缓解这种情况,我将向您展示一个更新的数据模型以及如何处理它

一份活动文件 数据模型 这是我更新的数据模型。我们基本上消除了冒充键的不必要值

{
  "events": [{
      "id": 1,
      "name": "first event",
    },
    {
      "id": 2,
      "name": "2nd event",
    },
    {
      "id": 3,
      "name": "3rd event",
    }
  ],
  "permissions": [{
      "event": 1,
      "perms": [{
          "user": "user1",
          "permission": "read",
        },
        {
          "user": "user2",
          "permission": "write"
        }
      ]
    },
    {
      "event": 2,
      "perms": [{
        "user": "user2",
        "permission": "write"
      }]
    },
    {
      "event": 3,
      "perms": [{
          "user": "user1",
          "permission": "readwrite"
        },
        {
          "user": "user2",
          "permission": "write"
        },
      ]
    }
  ],
}
它仍然允许您通过
db.events.find({“events.id”:1},{“events.$”:1})
搜索特定事件。当然,这适用于事件的所有其他属性

权限检查 用户是否拥有对特定文档执行特定操作的权限这一基本问题可以重新表述为

获取我感兴趣的属性和用户Y有权执行Z的属性的所有文档

这转化为一个相当简单的查询:

db.events.find({
  // The event we want...
  "events.id": 1,
  // ...shall be returned provided the following conditions are met
  "permissions": {
    $elemMatch: {
      // The permissions for event with id 1...
      "event": 1,
      "perms": {
        $elemMatch: {
          // ...allow the user in question... 
          "user": "user1",
          // ... to read said event.
          "permission": "read"
        }
      }
    }
  }
}, {
  // Subsequently, we return all matching events in all documents...
  "events.$": 1
})
我们在这里使用的是查询条件作为逻辑AND进行计算。如果用户没有我们请求的权限(本例中为“读取”),则不会返回任何文档

每个活动一份文件 数据模型 但是,如上所述,MongoDB文档有16MB的大小限制。因此,如果您有很多事件,或者甚至当您不确定可能有多少事件时,我们应该从不同(甚至更简单)的角度来处理这个问题:每个事件一个文档。数据模型变得非常简单:

// db.events.insertMany([
{
  "_id": 1,
  "name": "first event",
  "permissions":[
    {
      "user":"user1",
      "permission": "read"
    },
    {
      "user":"user2",
      "permission":"write"
    }
  ]
},
{
  "_id":2,
  "name": "2nd event",
  "permissions":[
    {
      "user": "user2",
      "permission": "write"
    }
  ]
},
{
  "_id":3,
  "name": "3rd event",
  "permissions":[
    {
        "user": "user1",
        "permission": "readwrite"
      },
      {
        "user": "user2",
        "permission": "write"
      },
  ]
}
// ])
权限检查 现在,让我们假设我们想检查user1是否真的可以读取(包括readwrite)事件3,如果可以,则返回它。变得比以前更加简单:

db.events.find({
  // The event we want...
  "_id": 3,
  // ...in case...
  "permissions": {
    $elemMatch: {
      // ... user1...
      "user": "user1",
      // ..has either the permission...
      $or: [{
        // ...to read or...
        "permission": "read"
      }, {
        // ... to readwrite.
        "permission": "readwrite"
      }]
    }
  }
}, {
  // Either way, do not return the permissions.
  permissions: 0
})
结论 无论您将选择这两种数据模型中的哪一种,都可以使用上述查询作为:

db.events.update(,{[…]})
不用说,我强烈建议使用“每个事件一个文档”的方法

然而,有两件事你需要记住

如果出现问题,您需要进行额外的查询以找出问题所在。事件可能不完全存在,或者用户可能没有执行查询/更新所需的权限。不过,既然这应该是个例外,我个人可以接受


要记住的另一件事是,您需要确保用户永远不能更改权限数组。原因很明显。

这是一个很好的解决方案,马库斯,+1!极好的!我不知道16mb的限制。我深受Firebase的博客文章的影响,这篇文章批评使用数组(),但现在我了解到Firebase是一种不同的体系结构,采用不同的方法。我仍然需要将权限和事件分开,因为用户的读写权限取决于他是组成员还是团队成员,所以数据位于事件实体的外部。我可以在事件中有一个额外的perm节点,但我觉得这种双重管理可能会导致问题。@Guy,应该没有太大问题。只需使用角色而不是用户。因为在进行查询之前,您应该了解用户的角色,所以这不会有太大的区别——方法是一样的。
db.events.update(<queryFromAbove>,{[...]})