通过 mongoDB Realm webhook 删除集合并插入文档数组

Deleting collection and inserting array of docs via mongoDB Realm webhook

我有一个用例,我想在文件被修改时将 csv 文件的内容发送到 mongoDB 集合。我发现可以在 mongoDB Realm 中创建一个 webhook。下面代码的目的是做两件事。首先,将指定的集合放入指定的数据库中。第二,向指定集合插入许多(~10k+)文档。

exports = function(payload, response) {
    const {database, coll_to_update} = payload.query;
    const contentTypes = payload.headers["Content-Type"];
    const body = payload.body;

    console.log("database, coll_to_update:", database, coll_to_update);
    console.log("Content-Type:", JSON.stringify(contentTypes));
    console.log("Request body:", body);

    const coll = context.services.get("mongodb-atlas").db(database).collection(coll_to_update);
    
    coll.deleteMany({})
      .then(result => console.log(`Deleted ${result.deletedCount} item(s).`))
      .catch(err => console.error(`Delete failed with error: ${err}`))
    
    coll.insertMany(body)
      .then(result => console.log(`Successfully inserted ${result.insertedIds.length} items!`))
      .catch(err => console.error(`Failed to insert documents: ${err}`))

    return payload;
};

这是在在线领域 UI 的函数编辑器中编写的。因为找不到删除集合的方法,所以我尝试通过传递一个空查询来删除其中的所有文档。但是我收到一条错误消息 FunctionError: mongodb delete: no arguments were passed。如果我提供查询,我可以删除文档。是否有任何查询始终匹配我可以使用的所有文档,或者有更好的方法删除或删除集合中的所有文档?

第二个问题是我不确定如何解码请求正文中发送的 csv 内容。我正在使用的卷曲如下。只是为了测试,我还将它发送到 http://httpbin.org/post 并且 json 被正确解码为两个对象的数组:

curl -H "Content-Type: application/json" -d [{\"foo\":\"bar\"},{\"baz\":\"zap\"}] \
"https://eu-west-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/application-0- \
abcdef/service/mongo_doodah/incoming_webhook/webhook0? \
database=FIC&coll_to_update=FIC_data&secret=not_this_one"

然而,当发送到 Realm 端点时,我收到错误 FunctionError: mongodb insert: argument must be an array。检查我看到的日志:

Logs:
[
  "database, coll_to_update: FIC FIC_data",
  "Content-Type: [\"application/json\"]",
  "Request body: [object Binary]"
]

Body:
{
  "$binary": {
    "base64": "W3siZm9vIjoiYmFyIn0seyJiYXoiOiJ6YXAifV0=",
    "subType": "00"
  }
}

所以 Realm 在处理我发送的 json 方面的工作方式与 pastebin 不同。我无法弄清楚如何在 Realm webhook 编辑器中获取我从这个二进制对象发送的 json。

我发现了一些可能对您的事业有所帮助的兴趣点。首先,让我提供我的解决方案。我使用了具有以下属性的 Atlas Realm webhook:

  • 身份验证:系统
  • 日志函数参数:ON
  • HTTP 方法:POST
  • 响应结果:ON
  • 可以评估:
  • 请求验证:无额外授权

在这些项目中,我认为 HTTP 方法最相关。由于您使用 -d 运算符在 CURL 命令中传递数据,因此 GET 方法是不够的。您的 CURL 命令没有指定 HTTP 谓词,因此它假定为 GET。第二项是请求验证。我看到您的 URL 包含特殊的 secret 关键项。我没有使用任何验证,因为我专注于让功能按预期工作,然后我会应用安全性。

网络钩子函数

exports = function(payload, response) {
  
    const {database, coll_to_update} = payload.query;
    const contentTypes = payload.headers["Content-Type"];
    const body = JSON.parse(payload.body.text());
    
    // console.log("database, coll_to_update:", database, coll_to_update);
    // console.log("Content-Type:", JSON.stringify(contentTypes));
    // console.log("Request body:", body);

    const coll = context.services.get("mongodb-atlas").db(database).collection(coll_to_update);
    
    coll.deleteMany({})
     .then(result => { 
        console.log(`Deleted ${result.deletedCount} item(s).`)
       
        coll.insertMany(body)
          .then(result => console.log(`Successfully inserted ${result.insertedIds.length} items!`))
          .catch(err => console.error(`Failed to insert documents: ${err}`))
     })
     .catch(err => console.error(`Delete failed with error: ${err}`))
    
     return payload;
};

注意到常量变量 body 被分配了 JSON 二进制有效载荷表示的 .text() 解析版本?这会将 $binary 转换为 JSON object.

要提到的第二项是操作顺序。您原来的 post 按顺序调用数据库 - 一个接一个。您先调用 deleteMany(),然后调用 insertMany()。但是由于代码是异步的,所以对 insertMany 的调用发生在 deleteMany 之前,因此插入的记录永远不会显示。相反,将插入物放在 deleteMany().

.then()

这是我的 CURL 命令。您必须确保 HTTP 谓词与 Web 挂钩方法匹配。就我而言,我选择在两者中都使用 POST。

示例 CURL 命令

curl --verbose \
  --header "Content-Type: application/json" \
  --request POST "https://us-east-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/barryapp-pzhuy/service/barryservice/incoming_webhook/barrywebhook?database=FIC&coll_to_update=FIC_data&secret=not_this_one" \
  --data '[ { "foo": "bar" }, { "baz": "zap" } ]'

示例 mongoshell 文档

Enterprise atlas-7aocnr-shard-0 [primary]> db.FIC_data.find()
[
  { _id: ObjectId("614b649faed0b6812c95e976"), foo: 'bar' },
  { _id: ObjectId("614b649faed0b6812c95e977"), baz: 'zap' }
]

为了对此进行测试,我需要传递一个不同的有效负载并在数据库中验证所有记录都已更改...

通过发出第二个 CURL 命令进行测试

curl --verbose \
  --header "Content-Type: application/json" \
  --request POST "https://us-east-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/barryapp-pzhuy/service/barryservice/incoming_webhook/barrywebhook?database=FIC&coll_to_update=FIC_data&secret=not_this_one" \
  --data '[ { "foo": "abc" }, { "baz": "xyz" } ]'

mongoshell 中的结果

Enterprise atlas-7aocnr-shard-0 [primary]> db.FIC_data.find()
[
  { _id: ObjectId("614b6537b435654ce5212d90"), foo: 'abc' },
  { _id: ObjectId("614b6537b435654ce5212d91"), baz: 'xyz' }
]

哦,顺便说一下,我用 MongoDB 确认了 - 没有 drop() 方法允许 collection 使用 MongoDB Atlas Realm 函数。要清除 collection,您必须像在代码中所做的那样删除所有记录。这不是一个很好的解决方案。另一种解决方案可能是简单地放弃 collection 以支持新的 collection 名称,也许名称中带有日期。您可以使用 collection.drop() 命令让 cron 作业清除废弃的 collections 以避免一次删除一条记录的开销(如果 collection 很大副本集拓扑,其中删除是 OpLog 上的幂等命令。

一般来说数据库操作是不允许的。我不清楚围绕这个概念的所有限制,但如果不允许 collection 删除,我怀疑索引主题也不允许。

我设法解决了,请参阅下面的代码。这还不包括删除目标集合中的现有文档。

exports = function(payload, response) {
    const {database, coll_to_update} = payload.query;        
    const coll = context.services.get("mongodb-atlas").db(database).collection(coll_to_update);

    // Payload body is a JSON string, convert into a JavaScript Object
    const data = JSON.parse(payload.body.text());

    // Perform operations as a bulk
    const bulkOp = coll.initializeOrderedBulkOp();
    data.forEach((document) => {
        bulkOp.insert(document);
    });
    response.addHeader(
        "Content-Type",
        "application/json"
    );
    bulkOp.execute().then(() => {
        // All operations completed successfully
        response.setStatusCode(200);
        response.setBody(JSON.stringify({
            timestamp: (new Date()).getTime()
        }));
        return;
    }).catch((error) => {
        // Catch any error with execution and return a 500
        response.setStatusCode(500);
        response.setBody(JSON.stringify({
            timestamp: (new Date()).getTime(),
            errorMessage: error
        }));
        return;
    });