From 9a8c01cd01bfcbd04c26557569a151be90ea37ba Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Wed, 13 Nov 2024 14:18:37 +0200 Subject: [PATCH 01/15] feat: Implemented mongo insert update connection. --- .../MongoDBUpdateInsertOne.js | 91 ++++ .../MongoDBUpdateInsertOne.test.js | 490 ++++++++++++++++++ .../MongoDBUpdateInsertOne/schema.js | 52 ++ 3 files changed, 633 insertions(+) create mode 100644 plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js create mode 100644 plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js create mode 100644 plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js new file mode 100644 index 0000000..3d5cc61 --- /dev/null +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js @@ -0,0 +1,91 @@ +/* + Copyright 2020-2023 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import getCollection from '../getCollection.js'; +import { serialize, deserialize } from '../serialize.js'; +import schema from './schema.js'; + +async function MongoDBUpdateInsertOne({ + blockId, + connection, + connectionId, + pageId, + request, + requestId, + payload, +}) { + const deserializedRequest = deserialize(request); + const { filter, update, options, disableNoMatchError } = deserializedRequest; + const { collection, client, logCollection } = await getCollection({ connection }); + let response; + try { + if (logCollection) { + const document = await collection.findOne(filter, options); + var insertedDocument; + if (document) { + delete document._id; + insertedDocument = await collection.insertOne(document); + } + + const { value, ...responseWithoutValue } = await collection.findOneAndUpdate( + insertedDocument ? { _id: insertedDocument.insertedId } : filter, + update, + { + ...options, + includeResultMetadata: true, + } + ); + response = responseWithoutValue; + const after = await collection.findOne({ + _id: value ? value._id : response.lastErrorObject?.upserted, + }); + await logCollection.insertOne({ + args: { filter, update, options }, + blockId, + connectionId, + pageId, + payload, + requestId, + before: value, + after, + timestamp: new Date(), + type: 'MongoDBUpdateInsertOne', + meta: connection.changeLog?.meta, + }); + if (!disableNoMatchError && !options?.upsert && !response.lastErrorObject.updatedExisting) { + throw new Error('No matching record to update.'); + } + } else { + response = await collection.updateOne(filter, update, options); + if (!disableNoMatchError && !options?.upsert && response.matchedCount === 0) { + throw new Error('No matching record to update.'); + } + } + } catch (error) { + await client.close(); + throw error; + } + await client.close(); + return serialize(response); +} + +MongoDBUpdateInsertOne.schema = schema; +MongoDBUpdateInsertOne.meta = { + checkRead: false, + checkWrite: true, +}; + +export default MongoDBUpdateInsertOne; diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js new file mode 100644 index 0000000..c57e962 --- /dev/null +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js @@ -0,0 +1,490 @@ +/* + Copyright 2020-2023 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import { validate } from '@lowdefy/ajv'; +import MongoDBUpdateInsertOne from './MongoDBUpdateInsertOne.js'; +import findLogCollectionRecordTestMongoDb from '../../../test/findLogCollectionRecordTestMongoDb.js'; +import populateTestMongoDb from '../../../test/populateTestMongoDb.js'; + +const { checkRead, checkWrite } = MongoDBUpdateInsertOne.meta; +const schema = MongoDBUpdateInsertOne.schema; + +const databaseUri = process.env.MONGO_URL; +const databaseName = 'test'; +const collection = 'updateInsertOne'; +const logCollection = 'logCollection'; +const documents = [{ _id: 'uniqueId', v: 'before', doc_id: 'updateInsertOne' }]; + +beforeAll(() => { + return populateTestMongoDb({ collection, documents }); +}); + +test('updateInsertOne', async () => { + const request = { + filter: { doc_id: 'updateInsertOne' }, + update: { $set: { v: 'after' } }, + }; + const connection = { + databaseUri, + databaseName, + collection, + write: true, + }; + const res = await MongoDBUpdateInsertOne({ request, connection }); + expect(res).toEqual({ + acknowledged: true, + modifiedCount: 1, + upsertedId: null, + upsertedCount: 0, + matchedCount: 1, + }); +}); + +test('updateInsertOne logCollection', async () => { + const request = { + filter: { doc_id: 'updateInsertOne' }, + update: { $set: { v: 'afterLog' } }, + }; + const connection = { + databaseUri, + databaseName, + collection, + changeLog: { collection: logCollection, meta: { meta: true } }, + write: true, + }; + const res = await MongoDBUpdateInsertOne({ + request, + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne', + connection, + }); + expect(res).toEqual({ + lastErrorObject: { + n: 1, + updatedExisting: true, + }, + ok: 1, + }); + const logged = await findLogCollectionRecordTestMongoDb({ + logCollection, + requestId: 'updateInsertOne', + }); + expect(logged).toMatchObject({ + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne', + before: { doc_id: 'updateInsertOne', v: 'after' }, + after: { doc_id: 'updateInsertOne', v: 'afterLog' }, + type: 'MongoDBUpdateInsertOne', + meta: { meta: true }, + }); +}); + +test('updateInsertOne upsert', async () => { + const request = { + filter: { _id: 'uniqueId_upsert', doc_id: 'updateInsertOne' }, + update: { $set: { v: 'after' } }, + options: { upsert: true }, + }; + const connection = { + databaseUri, + databaseName, + collection, + write: true, + }; + const res = await MongoDBUpdateInsertOne({ request, connection }); + expect(res).toEqual({ + acknowledged: true, + modifiedCount: 0, + upsertedId: 'uniqueId_upsert', + upsertedCount: 1, + matchedCount: 0, + }); +}); + +test('updateInsertOne upsert logCollection', async () => { + const request = { + filter: { _id: 'uniqueId_upsert_log', doc_id: 'updateInsertOne' }, + update: { $set: { v: 'after' } }, + options: { upsert: true }, + }; + const connection = { + databaseUri, + databaseName, + collection, + changeLog: { collection: logCollection, meta: { meta: true } }, + write: true, + }; + const res = await MongoDBUpdateInsertOne({ + request, + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'uniqueId_upsert_log', + connection, + }); + expect(res).toEqual({ + lastErrorObject: { + n: 1, + updatedExisting: false, + upserted: 'uniqueId_upsert_log', + }, + ok: 1, + }); + const logged = await findLogCollectionRecordTestMongoDb({ + logCollection, + requestId: 'uniqueId_upsert_log', + }); + expect(logged).toMatchObject({ + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'uniqueId_upsert_log', + before: null, + after: { _id: 'uniqueId_upsert_log', v: 'after', doc_id: 'updateInsertOne' }, + type: 'MongoDBUpdateInsertOne', + meta: { meta: true }, + }); +}); + +test('updateInsertOne upsert false', async () => { + const request = { + filter: { doc_id: 'uniqueId_upsert_false' }, + update: { $set: { v: 'after' } }, + options: { upsert: false }, + }; + const connection = { + databaseUri, + databaseName, + collection, + write: true, + }; + expect(async () => { + await MongoDBUpdateInsertOne({ request, connection }); + }).rejects.toThrow('No matching record to update.'); +}); + +test('updateInsertOne upsert false logCollection', async () => { + const request = { + filter: { doc_id: 'updateInsertOne_upsert_false' }, + update: { $set: { v: 'after' } }, + options: { upsert: false }, + }; + const connection = { + databaseUri, + databaseName, + collection, + changeLog: { collection: logCollection, meta: { meta: true } }, + write: true, + }; + await expect(async () => { + await MongoDBUpdateInsertOne({ + request, + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'uniqueId_upsert_false', + connection, + }); + }).rejects.toThrow('No matching record to update.'); + const logged = await findLogCollectionRecordTestMongoDb({ + logCollection, + requestId: 'uniqueId_upsert_false', + }); + expect(logged).toMatchObject({ + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'uniqueId_upsert_false', + before: null, + after: null, + type: 'MongoDBUpdateInsertOne', + meta: { meta: true }, + }); +}); + +test('updateInsertOne upsert default false', async () => { + const request = { + filter: { doc_id: 'updateInsertOne_upsert_default_false' }, + update: { $set: { v: 'after' } }, + }; + const connection = { + databaseUri, + databaseName, + collection, + write: true, + }; + expect(async () => { + await MongoDBUpdateInsertOne({ request, connection }); + }).rejects.toThrow('No matching record to update.'); +}); + +test('updateInsertOne upsert default false logCollection', async () => { + const request = { + filter: { doc_id: 'updateInsertOne_upsert_default_false' }, + update: { $set: { v: 'after' } }, + }; + const connection = { + databaseUri, + databaseName, + collection, + changeLog: { collection: logCollection, meta: { meta: true } }, + write: true, + }; + await expect(async () => { + await MongoDBUpdateInsertOne({ + request, + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne_upsert_default_false', + connection, + }); + }).rejects.toThrow('No matching record to update.'); + const logged = await findLogCollectionRecordTestMongoDb({ + logCollection, + requestId: 'updateInsertOne_upsert_default_false', + }); + expect(logged).toMatchObject({ + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne_upsert_default_false', + before: null, + after: null, + type: 'MongoDBUpdateInsertOne', + meta: { meta: true }, + }); +}); + +test('updateInsertOne disableNoMatchError', async () => { + const request = { + filter: { doc_id: 'updateInsertOne_disable_no_match_error' }, + update: { $set: { v: 'after' } }, + disableNoMatchError: true, + }; + const connection = { + databaseUri, + databaseName, + collection, + write: true, + }; + const res = await MongoDBUpdateInsertOne({ request, connection }); + expect(res).toEqual({ + acknowledged: true, + modifiedCount: 0, + upsertedId: null, + upsertedCount: 0, + matchedCount: 0, + }); +}); + +test('updateInsertOne disableNoMatchError logCollection', async () => { + const request = { + filter: { doc_id: 'updateInsertOne_disable_no_match_error' }, + update: { $set: { v: 'after' } }, + disableNoMatchError: true, + }; + const connection = { + databaseUri, + databaseName, + collection, + changeLog: { collection: logCollection, meta: { meta: true } }, + write: true, + }; + const res = await MongoDBUpdateInsertOne({ + request, + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne_disable_no_match_error', + connection, + }); + expect(res).toEqual({ + lastErrorObject: { + n: 0, + updatedExisting: false, + }, + ok: 1, + }); + const logged = await findLogCollectionRecordTestMongoDb({ + logCollection, + requestId: 'updateInsertOne_disable_no_match_error', + }); + expect(logged).toMatchObject({ + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne_disable_no_match_error', + before: null, + after: null, + type: 'MongoDBUpdateInsertOne', + meta: { meta: true }, + }); +}); + +test('updateInsertOne disableNoMatchError false', async () => { + const request = { + filter: { doc_id: 'updateInsertOne_disable_no_match_error_false' }, + update: { $set: { v: 'after' } }, + disableNoMatchError: false, + }; + const connection = { + databaseUri, + databaseName, + collection, + write: true, + }; + expect(async () => { + await MongoDBUpdateInsertOne({ request, connection }); + }).rejects.toThrow('No matching record to update.'); +}); + +test('updateInsertOne disableNoMatchError false logCollection', async () => { + const request = { + filter: { doc_id: 'updateInsertOne_disable_no_match_error_false' }, + update: { $set: { v: 'after' } }, + disableNoMatchError: false, + }; + const connection = { + databaseUri, + databaseName, + collection, + changeLog: { collection: logCollection, meta: { meta: true } }, + write: true, + }; + await expect(async () => { + await MongoDBUpdateInsertOne({ + request, + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne_disable_no_match_error_false', + connection, + }); + }).rejects.toThrow('No matching record to update.'); + const logged = await findLogCollectionRecordTestMongoDb({ + logCollection, + requestId: 'updateInsertOne_disable_no_match_error_false', + }); + expect(logged).toMatchObject({ + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne_disable_no_match_error_false', + before: null, + after: null, + type: 'MongoDBUpdateInsertOne', + meta: { meta: true }, + }); +}); + +test('updateInsertOne connection error', async () => { + const request = { + filter: { doc_id: 'updateInsertOne_connection_error' }, + update: { $set: { v: 'after' } }, + }; + const connection = { + databaseUri: 'bad_uri', + databaseName, + collection, + write: true, + }; + await expect(MongoDBUpdateInsertOne({ request, connection })).rejects.toThrow( + 'Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"' + ); +}); + +test('updateInsertOne mongodb error', async () => { + const request = { + filter: { doc_id: 'updateInsertOne_mongodb_error' }, + update: { $badOp: { v: 'after' } }, + }; + const connection = { + databaseUri, + databaseName, + collection, + write: true, + }; + await expect(MongoDBUpdateInsertOne({ request, connection })).rejects.toThrow( + 'Unknown modifier: $badOp' + ); +}); + +test('checkRead should be false', async () => { + expect(checkRead).toBe(false); +}); + +test('checkWrite should be true', async () => { + expect(checkWrite).toBe(true); +}); + +test('request not an object', async () => { + const request = 'request'; + expect(() => validate({ schema, data: request })).toThrow( + 'MongoDBUpdateInsertOne request properties should be an object.' + ); +}); + +test('request no filter', async () => { + const request = { update: {} }; + expect(() => validate({ schema, data: request })).toThrow( + 'MongoDBUpdateInsertOne request should have required property "filter".' + ); +}); + +test('request no update', async () => { + const request = { filter: {} }; + expect(() => validate({ schema, data: request })).toThrow( + 'MongoDBUpdateInsertOne request should have required property "update".' + ); +}); + +test('request update not an object', async () => { + const request = { update: 'update', filter: {} }; + expect(() => validate({ schema, data: request })).toThrow( + 'MongoDBUpdateInsertOne request property "update" should be an object.' + ); +}); + +test('request filter not an object', async () => { + const request = { update: {}, filter: 'filter' }; + expect(() => validate({ schema, data: request })).toThrow( + 'MongoDBUpdateInsertOne request property "filter" should be an object.' + ); +}); + +test('request options not an object', async () => { + const request = { update: {}, filter: {}, options: 'options' }; + expect(() => validate({ schema, data: request })).toThrow( + 'MongoDBUpdateInsertOne request property "options" should be an object.' + ); +}); diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js new file mode 100644 index 0000000..a8456ad --- /dev/null +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js @@ -0,0 +1,52 @@ +/* + Copyright 2020-2023 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +export default { + $schema: 'http://json-schema.org/draft-07/schema#', + title: 'Lowdefy Request Schema - MongoDBUpdateInsertOne', + type: 'object', + required: ['filter', 'update'], + properties: { + filter: { + type: 'object', + description: 'The filter used to select the document to find and insert.', + errorMessage: { + type: 'MongoDBUpdateInsertOne request property "filter" should be an object.', + }, + }, + update: { + type: ['object', 'array'], + description: 'The update operations to be applied to the new inserted document.', + errorMessage: { + type: 'MongoDBUpdateInsertOne request property "update" should be an object.', + }, + }, + options: { + type: 'object', + description: 'Optional settings.', + errorMessage: { + type: 'MongoDBUpdateInsertOne request property "options" should be an object.', + }, + }, + }, + errorMessage: { + type: 'MongoDBUpdateInsertOne request properties should be an object.', + required: { + filter: 'MongoDBUpdateInsertOne request should have required property "filter".', + update: 'MongoDBUpdateInsertOne request should have required property "update".', + }, + }, +}; From 2c6315b4cfb2206669ab5b2c16e7c47145ca97d3 Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Wed, 13 Nov 2024 16:31:20 +0200 Subject: [PATCH 02/15] fix: Added options for each request. --- .../MongoDBUpdateInsertOne.js | 30 +++++++++++-------- .../MongoDBUpdateInsertOne.test.js | 8 ++--- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js index 3d5cc61..dfeeeb0 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js @@ -30,28 +30,30 @@ async function MongoDBUpdateInsertOne({ const deserializedRequest = deserialize(request); const { filter, update, options, disableNoMatchError } = deserializedRequest; const { collection, client, logCollection } = await getCollection({ connection }); + const findOptions = options?.find; + const insertOptions = options?.insert; + const updateOptions = options?.update; let response; + try { if (logCollection) { - const document = await collection.findOne(filter, options); + const document = await collection.findOne(filter, { ...findOptions }); var insertedDocument; if (document) { delete document._id; - insertedDocument = await collection.insertOne(document); + insertedDocument = await collection.insertOne(document, { ...insertOptions }); } const { value, ...responseWithoutValue } = await collection.findOneAndUpdate( insertedDocument ? { _id: insertedDocument.insertedId } : filter, update, { - ...options, - includeResultMetadata: true, + ...updateOptions, + includeResultMetadata: true, //TODO: Use document after + returnDocument: 'after', } ); response = responseWithoutValue; - const after = await collection.findOne({ - _id: value ? value._id : response.lastErrorObject?.upserted, - }); await logCollection.insertOne({ args: { filter, update, options }, blockId, @@ -59,18 +61,22 @@ async function MongoDBUpdateInsertOne({ pageId, payload, requestId, - before: value, - after, + before: document, + after: value, timestamp: new Date(), type: 'MongoDBUpdateInsertOne', meta: connection.changeLog?.meta, }); - if (!disableNoMatchError && !options?.upsert && !response.lastErrorObject.updatedExisting) { + if ( + !disableNoMatchError && + !updateOptions?.upsert && + !response.lastErrorObject.updatedExisting + ) { throw new Error('No matching record to update.'); } } else { - response = await collection.updateOne(filter, update, options); - if (!disableNoMatchError && !options?.upsert && response.matchedCount === 0) { + response = await collection.updateOne(filter, update, { ...updateOptions }); + if (!disableNoMatchError && !updateOptions?.upsert && response.matchedCount === 0) { throw new Error('No matching record to update.'); } } diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js index c57e962..c242939 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js @@ -102,7 +102,7 @@ test('updateInsertOne upsert', async () => { const request = { filter: { _id: 'uniqueId_upsert', doc_id: 'updateInsertOne' }, update: { $set: { v: 'after' } }, - options: { upsert: true }, + options: { update: { upsert: true } }, }; const connection = { databaseUri, @@ -124,7 +124,7 @@ test('updateInsertOne upsert logCollection', async () => { const request = { filter: { _id: 'uniqueId_upsert_log', doc_id: 'updateInsertOne' }, update: { $set: { v: 'after' } }, - options: { upsert: true }, + options: { update: { upsert: true } }, }; const connection = { databaseUri, @@ -171,7 +171,7 @@ test('updateInsertOne upsert false', async () => { const request = { filter: { doc_id: 'uniqueId_upsert_false' }, update: { $set: { v: 'after' } }, - options: { upsert: false }, + options: { update: { upsert: false } }, }; const connection = { databaseUri, @@ -188,7 +188,7 @@ test('updateInsertOne upsert false logCollection', async () => { const request = { filter: { doc_id: 'updateInsertOne_upsert_false' }, update: { $set: { v: 'after' } }, - options: { upsert: false }, + options: { update: { upsert: false } }, }; const connection = { databaseUri, From 6c001b7ac812864ec81a9237d4a2e64137e8fbf3 Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Thu, 14 Nov 2024 10:34:55 +0200 Subject: [PATCH 03/15] chore: Added changeset. --- .changeset/hip-seas-camp.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hip-seas-camp.md diff --git a/.changeset/hip-seas-camp.md b/.changeset/hip-seas-camp.md new file mode 100644 index 0000000..2ffc43e --- /dev/null +++ b/.changeset/hip-seas-camp.md @@ -0,0 +1,5 @@ +--- +'@lowdefy/community-plugin-mongodb': minor +--- + +Added MongoDBUpdateInsertOne request. From 9208f2b1637ec9e61588561ef7c260455efe8996 Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Thu, 14 Nov 2024 11:11:30 +0200 Subject: [PATCH 04/15] fix: Updated no log collection logic. --- .../MongoDBUpdateInsertOne.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js index dfeeeb0..a08d28a 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js @@ -33,12 +33,11 @@ async function MongoDBUpdateInsertOne({ const findOptions = options?.find; const insertOptions = options?.insert; const updateOptions = options?.update; - let response; + let response, insertedDocument; try { if (logCollection) { const document = await collection.findOne(filter, { ...findOptions }); - var insertedDocument; if (document) { delete document._id; insertedDocument = await collection.insertOne(document, { ...insertOptions }); @@ -49,7 +48,7 @@ async function MongoDBUpdateInsertOne({ update, { ...updateOptions, - includeResultMetadata: true, //TODO: Use document after + includeResultMetadata: true, returnDocument: 'after', } ); @@ -75,7 +74,17 @@ async function MongoDBUpdateInsertOne({ throw new Error('No matching record to update.'); } } else { - response = await collection.updateOne(filter, update, { ...updateOptions }); + const document = await collection.findOne(filter, { ...findOptions }); + if (document) { + delete document._id; + insertedDocument = await collection.insertOne(document, { ...insertOptions }); + } + + response = await collection.updateOne( + insertedDocument ? { _id: insertedDocument.insertedId } : filter, + update, + { ...updateOptions } + ); if (!disableNoMatchError && !updateOptions?.upsert && response.matchedCount === 0) { throw new Error('No matching record to update.'); } From ab0b379bf144361dd72f81f5098065f3ac43623b Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Thu, 14 Nov 2024 11:33:02 +0200 Subject: [PATCH 05/15] fix: Added test case. --- .../MongoDBUpdateInsertOne.test.js | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js index c242939..ce6b930 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js @@ -91,13 +91,59 @@ test('updateInsertOne logCollection', async () => { pageId: 'pageId', payload: { payload: true }, requestId: 'updateInsertOne', - before: { doc_id: 'updateInsertOne', v: 'after' }, + before: { doc_id: 'updateInsertOne', v: 'before' }, after: { doc_id: 'updateInsertOne', v: 'afterLog' }, type: 'MongoDBUpdateInsertOne', meta: { meta: true }, }); }); +test('updateInsertOne logCollection with find options', async () => { + const request = { + filter: { doc_id: 'updateInsertOne' }, + update: { $set: { v: 'afterLog' } }, + options: { find: { projection: { _id: 1, v: 1 } } }, + }; + const connection = { + databaseUri, + databaseName, + collection, + changeLog: { collection: logCollection, meta: { meta: true } }, + write: true, + }; + const res = await MongoDBUpdateInsertOne({ + request, + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne', + connection, + }); + expect(res).toEqual({ + lastErrorObject: { + n: 1, + updatedExisting: true, + }, + ok: 1, + }); + const logged = await findLogCollectionRecordTestMongoDb({ + logCollection, + requestId: 'updateInsertOne', + }); + expect(logged).toMatchObject({ + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne', + before: { v: 'before' }, + after: { v: 'afterLog' }, + type: 'MongoDBUpdateInsertOne', + meta: { meta: true }, + }); +}); + test('updateInsertOne upsert', async () => { const request = { filter: { _id: 'uniqueId_upsert', doc_id: 'updateInsertOne' }, From ce1720a4e23c1cf8a0eff35ce6ab88eb4d5ced47 Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Thu, 14 Nov 2024 11:52:56 +0200 Subject: [PATCH 06/15] fix: Fixed test new test case. --- .../MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js index ce6b930..5935761 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js @@ -102,7 +102,7 @@ test('updateInsertOne logCollection with find options', async () => { const request = { filter: { doc_id: 'updateInsertOne' }, update: { $set: { v: 'afterLog' } }, - options: { find: { projection: { _id: 1, v: 1 } } }, + options: { find: { projection: { doc_id: 0 } } }, }; const connection = { databaseUri, @@ -117,7 +117,7 @@ test('updateInsertOne logCollection with find options', async () => { connectionId: 'connectionId', pageId: 'pageId', payload: { payload: true }, - requestId: 'updateInsertOne', + requestId: 'updateInsertOneFindOptions', connection, }); expect(res).toEqual({ @@ -129,14 +129,14 @@ test('updateInsertOne logCollection with find options', async () => { }); const logged = await findLogCollectionRecordTestMongoDb({ logCollection, - requestId: 'updateInsertOne', + requestId: 'updateInsertOneFindOptions', }); expect(logged).toMatchObject({ blockId: 'blockId', connectionId: 'connectionId', pageId: 'pageId', payload: { payload: true }, - requestId: 'updateInsertOne', + requestId: 'updateInsertOneFindOptions', before: { v: 'before' }, after: { v: 'afterLog' }, type: 'MongoDBUpdateInsertOne', From b73334c1e24c09652470597581436709fca92b87 Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Thu, 14 Nov 2024 11:55:21 +0200 Subject: [PATCH 07/15] fix: Move statement outside of condition. --- .../MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js index a08d28a..6044ee9 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js @@ -36,8 +36,8 @@ async function MongoDBUpdateInsertOne({ let response, insertedDocument; try { + const document = await collection.findOne(filter, { ...findOptions }); if (logCollection) { - const document = await collection.findOne(filter, { ...findOptions }); if (document) { delete document._id; insertedDocument = await collection.insertOne(document, { ...insertOptions }); @@ -74,7 +74,6 @@ async function MongoDBUpdateInsertOne({ throw new Error('No matching record to update.'); } } else { - const document = await collection.findOne(filter, { ...findOptions }); if (document) { delete document._id; insertedDocument = await collection.insertOne(document, { ...insertOptions }); From 9a07a0d2e3e3f6911fbc4eb55fae4b9455418f82 Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Thu, 14 Nov 2024 12:30:31 +0200 Subject: [PATCH 08/15] feat: Added test case. --- .../MongoDBUpdateInsertOne.test.js | 43 ++++++++++++++++++- .../src/test/getTestCollection.js | 39 +++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 plugins/community-plugin-mongodb/src/test/getTestCollection.js diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js index 5935761..b08e5f6 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js @@ -18,6 +18,7 @@ import { validate } from '@lowdefy/ajv'; import MongoDBUpdateInsertOne from './MongoDBUpdateInsertOne.js'; import findLogCollectionRecordTestMongoDb from '../../../test/findLogCollectionRecordTestMongoDb.js'; import populateTestMongoDb from '../../../test/populateTestMongoDb.js'; +import getTestCollection from '../../../test/getTestCollection.js'; const { checkRead, checkWrite } = MongoDBUpdateInsertOne.meta; const schema = MongoDBUpdateInsertOne.schema; @@ -28,7 +29,7 @@ const collection = 'updateInsertOne'; const logCollection = 'logCollection'; const documents = [{ _id: 'uniqueId', v: 'before', doc_id: 'updateInsertOne' }]; -beforeAll(() => { +beforeEach(() => { return populateTestMongoDb({ collection, documents }); }); @@ -485,6 +486,46 @@ test('updateInsertOne mongodb error', async () => { ); }); +test('updateInsertOne validate write', async () => { + const request = { + filter: { doc_id: 'updateInsertOne' }, + update: { $set: { v: 'after' } }, + }; + const connection = { + databaseUri, + databaseName, + collection, + write: true, + }; + const res = await MongoDBUpdateInsertOne({ + request, + blockId: 'blockId', + connectionId: 'connectionId', + pageId: 'pageId', + payload: { payload: true }, + requestId: 'updateInsertOne_validate_write', + connection, + }); + expect(res).toEqual({ + acknowledged: true, + modifiedCount: 1, + upsertedId: null, + upsertedCount: 0, + matchedCount: 1, + }); + const { collection: testCollection, client } = await getTestCollection(); + const cursor = await testCollection.find( + { doc_id: 'updateInsertOne' }, + { projection: { _id: 0 } } + ); + const records = await cursor.toArray(); + expect(records).toEqual([ + { doc_id: 'updateInsertOne', v: 'before' }, + { doc_id: 'updateInsertOne', v: 'after' }, + ]); + await client.close(); +}); + test('checkRead should be false', async () => { expect(checkRead).toBe(false); }); diff --git a/plugins/community-plugin-mongodb/src/test/getTestCollection.js b/plugins/community-plugin-mongodb/src/test/getTestCollection.js new file mode 100644 index 0000000..2d3b23e --- /dev/null +++ b/plugins/community-plugin-mongodb/src/test/getTestCollection.js @@ -0,0 +1,39 @@ +/* + Copyright 2020-2023 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import { MongoClient } from 'mongodb'; + +async function getTestCollection() { + let client; + const databaseUri = process.env.MONGO_URL; + const databaseName = 'test'; + const collection = 'updateInsertOne'; + + client = new MongoClient(databaseUri); + await client.connect(); + try { + const db = client.db(databaseName); + return { + client, + collection: db.collection(collection), + }; + } catch (error) { + await client.close(); + throw error; + } +} + +export default getTestCollection; From a597917e0668157db7b2c16adb5971c7b3cda4ba Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Thu, 14 Nov 2024 12:49:30 +0200 Subject: [PATCH 09/15] fix: Updated schema and test cases. --- .../MongoDBUpdateInsertOne.test.js | 21 ++++++++++++++++ .../MongoDBUpdateInsertOne/schema.js | 25 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js index b08e5f6..d220846 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js @@ -575,3 +575,24 @@ test('request options not an object', async () => { 'MongoDBUpdateInsertOne request property "options" should be an object.' ); }); + +test('request update options not an object', async () => { + const request = { update: {}, filter: {}, options: { update: 'update' } }; + expect(() => validate({ schema, data: request })).toThrow( + 'MongoDBUpdateInsertOne request property option "update" should be an object.' + ); +}); + +test('request find options not an object', async () => { + const request = { update: {}, filter: {}, options: { find: 'find' } }; + expect(() => validate({ schema, data: request })).toThrow( + 'MongoDBUpdateInsertOne request property option "find" should be an object.' + ); +}); + +test('request insert options not an object', async () => { + const request = { update: {}, filter: {}, options: { insert: 'insert' } }; + expect(() => validate({ schema, data: request })).toThrow( + 'MongoDBUpdateInsertOne request property option "insert" should be an object.' + ); +}); diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js index a8456ad..33ec53b 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js @@ -36,10 +36,33 @@ export default { }, options: { type: 'object', - description: 'Optional settings.', + description: 'Optional settings for each mongodb operation.', errorMessage: { type: 'MongoDBUpdateInsertOne request property "options" should be an object.', }, + properties: { + find: { + type: 'object', + description: 'Find options for mongodb find one.', + errorMessage: { + type: 'MongoDBUpdateInsertOne request property option "find" should be an object.', + }, + }, + insert: { + type: 'object', + description: 'Insert options for mongodb insert one.', + errorMessage: { + type: 'MongoDBUpdateInsertOne request property option "insert" should be an object.', + }, + }, + update: { + type: 'object', + description: 'Update options for mongodb find one and update.', + errorMessage: { + type: 'MongoDBUpdateInsertOne request property option "update" should be an object.', + }, + }, + }, }, }, errorMessage: { From eb568656915bad4bf279866ea7424a150d85773c Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Tue, 19 Nov 2024 12:16:55 +0200 Subject: [PATCH 10/15] chore: Rename to MongoDBVersionedUpdateOne. --- .../MongoDBVersionedUpdateOne.js} | 10 +-- .../MongoDBVersionedUpdateOne.test.js} | 70 +++++++++---------- .../schema.js | 20 +++--- 3 files changed, 50 insertions(+), 50 deletions(-) rename plugins/community-plugin-mongodb/src/connections/MongoDBCollection/{MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js => MongoDBVersionedUpdateOne/MongoDBVersionedUpdateOne.js} (93%) rename plugins/community-plugin-mongodb/src/connections/MongoDBCollection/{MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js => MongoDBVersionedUpdateOne/MongoDBVersionedUpdateOne.test.js} (87%) rename plugins/community-plugin-mongodb/src/connections/MongoDBCollection/{MongoDBUpdateInsertOne => MongoDBVersionedUpdateOne}/schema.js (66%) diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBVersionedUpdateOne/MongoDBVersionedUpdateOne.js similarity index 93% rename from plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js rename to plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBVersionedUpdateOne/MongoDBVersionedUpdateOne.js index 6044ee9..7fc33fd 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBVersionedUpdateOne/MongoDBVersionedUpdateOne.js @@ -18,7 +18,7 @@ import getCollection from '../getCollection.js'; import { serialize, deserialize } from '../serialize.js'; import schema from './schema.js'; -async function MongoDBUpdateInsertOne({ +async function MongoDBVersionedUpdateOne({ blockId, connection, connectionId, @@ -63,7 +63,7 @@ async function MongoDBUpdateInsertOne({ before: document, after: value, timestamp: new Date(), - type: 'MongoDBUpdateInsertOne', + type: 'MongoDBVersionedUpdateOne', meta: connection.changeLog?.meta, }); if ( @@ -96,10 +96,10 @@ async function MongoDBUpdateInsertOne({ return serialize(response); } -MongoDBUpdateInsertOne.schema = schema; -MongoDBUpdateInsertOne.meta = { +MongoDBVersionedUpdateOne.schema = schema; +MongoDBVersionedUpdateOne.meta = { checkRead: false, checkWrite: true, }; -export default MongoDBUpdateInsertOne; +export default MongoDBVersionedUpdateOne; diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBVersionedUpdateOne/MongoDBVersionedUpdateOne.test.js similarity index 87% rename from plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js rename to plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBVersionedUpdateOne/MongoDBVersionedUpdateOne.test.js index d220846..b0f88fb 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/MongoDBUpdateInsertOne.test.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBVersionedUpdateOne/MongoDBVersionedUpdateOne.test.js @@ -15,13 +15,13 @@ */ import { validate } from '@lowdefy/ajv'; -import MongoDBUpdateInsertOne from './MongoDBUpdateInsertOne.js'; +import MongoDBVersionedUpdateOne from './MongoDBVersionedUpdateOne.js'; import findLogCollectionRecordTestMongoDb from '../../../test/findLogCollectionRecordTestMongoDb.js'; import populateTestMongoDb from '../../../test/populateTestMongoDb.js'; import getTestCollection from '../../../test/getTestCollection.js'; -const { checkRead, checkWrite } = MongoDBUpdateInsertOne.meta; -const schema = MongoDBUpdateInsertOne.schema; +const { checkRead, checkWrite } = MongoDBVersionedUpdateOne.meta; +const schema = MongoDBVersionedUpdateOne.schema; const databaseUri = process.env.MONGO_URL; const databaseName = 'test'; @@ -44,7 +44,7 @@ test('updateInsertOne', async () => { collection, write: true, }; - const res = await MongoDBUpdateInsertOne({ request, connection }); + const res = await MongoDBVersionedUpdateOne({ request, connection }); expect(res).toEqual({ acknowledged: true, modifiedCount: 1, @@ -66,7 +66,7 @@ test('updateInsertOne logCollection', async () => { changeLog: { collection: logCollection, meta: { meta: true } }, write: true, }; - const res = await MongoDBUpdateInsertOne({ + const res = await MongoDBVersionedUpdateOne({ request, blockId: 'blockId', connectionId: 'connectionId', @@ -94,7 +94,7 @@ test('updateInsertOne logCollection', async () => { requestId: 'updateInsertOne', before: { doc_id: 'updateInsertOne', v: 'before' }, after: { doc_id: 'updateInsertOne', v: 'afterLog' }, - type: 'MongoDBUpdateInsertOne', + type: 'MongoDBVersionedUpdateOne', meta: { meta: true }, }); }); @@ -112,7 +112,7 @@ test('updateInsertOne logCollection with find options', async () => { changeLog: { collection: logCollection, meta: { meta: true } }, write: true, }; - const res = await MongoDBUpdateInsertOne({ + const res = await MongoDBVersionedUpdateOne({ request, blockId: 'blockId', connectionId: 'connectionId', @@ -140,7 +140,7 @@ test('updateInsertOne logCollection with find options', async () => { requestId: 'updateInsertOneFindOptions', before: { v: 'before' }, after: { v: 'afterLog' }, - type: 'MongoDBUpdateInsertOne', + type: 'MongoDBVersionedUpdateOne', meta: { meta: true }, }); }); @@ -157,7 +157,7 @@ test('updateInsertOne upsert', async () => { collection, write: true, }; - const res = await MongoDBUpdateInsertOne({ request, connection }); + const res = await MongoDBVersionedUpdateOne({ request, connection }); expect(res).toEqual({ acknowledged: true, modifiedCount: 0, @@ -180,7 +180,7 @@ test('updateInsertOne upsert logCollection', async () => { changeLog: { collection: logCollection, meta: { meta: true } }, write: true, }; - const res = await MongoDBUpdateInsertOne({ + const res = await MongoDBVersionedUpdateOne({ request, blockId: 'blockId', connectionId: 'connectionId', @@ -209,7 +209,7 @@ test('updateInsertOne upsert logCollection', async () => { requestId: 'uniqueId_upsert_log', before: null, after: { _id: 'uniqueId_upsert_log', v: 'after', doc_id: 'updateInsertOne' }, - type: 'MongoDBUpdateInsertOne', + type: 'MongoDBVersionedUpdateOne', meta: { meta: true }, }); }); @@ -227,7 +227,7 @@ test('updateInsertOne upsert false', async () => { write: true, }; expect(async () => { - await MongoDBUpdateInsertOne({ request, connection }); + await MongoDBVersionedUpdateOne({ request, connection }); }).rejects.toThrow('No matching record to update.'); }); @@ -245,7 +245,7 @@ test('updateInsertOne upsert false logCollection', async () => { write: true, }; await expect(async () => { - await MongoDBUpdateInsertOne({ + await MongoDBVersionedUpdateOne({ request, blockId: 'blockId', connectionId: 'connectionId', @@ -267,7 +267,7 @@ test('updateInsertOne upsert false logCollection', async () => { requestId: 'uniqueId_upsert_false', before: null, after: null, - type: 'MongoDBUpdateInsertOne', + type: 'MongoDBVersionedUpdateOne', meta: { meta: true }, }); }); @@ -284,7 +284,7 @@ test('updateInsertOne upsert default false', async () => { write: true, }; expect(async () => { - await MongoDBUpdateInsertOne({ request, connection }); + await MongoDBVersionedUpdateOne({ request, connection }); }).rejects.toThrow('No matching record to update.'); }); @@ -301,7 +301,7 @@ test('updateInsertOne upsert default false logCollection', async () => { write: true, }; await expect(async () => { - await MongoDBUpdateInsertOne({ + await MongoDBVersionedUpdateOne({ request, blockId: 'blockId', connectionId: 'connectionId', @@ -323,7 +323,7 @@ test('updateInsertOne upsert default false logCollection', async () => { requestId: 'updateInsertOne_upsert_default_false', before: null, after: null, - type: 'MongoDBUpdateInsertOne', + type: 'MongoDBVersionedUpdateOne', meta: { meta: true }, }); }); @@ -340,7 +340,7 @@ test('updateInsertOne disableNoMatchError', async () => { collection, write: true, }; - const res = await MongoDBUpdateInsertOne({ request, connection }); + const res = await MongoDBVersionedUpdateOne({ request, connection }); expect(res).toEqual({ acknowledged: true, modifiedCount: 0, @@ -363,7 +363,7 @@ test('updateInsertOne disableNoMatchError logCollection', async () => { changeLog: { collection: logCollection, meta: { meta: true } }, write: true, }; - const res = await MongoDBUpdateInsertOne({ + const res = await MongoDBVersionedUpdateOne({ request, blockId: 'blockId', connectionId: 'connectionId', @@ -391,7 +391,7 @@ test('updateInsertOne disableNoMatchError logCollection', async () => { requestId: 'updateInsertOne_disable_no_match_error', before: null, after: null, - type: 'MongoDBUpdateInsertOne', + type: 'MongoDBVersionedUpdateOne', meta: { meta: true }, }); }); @@ -409,7 +409,7 @@ test('updateInsertOne disableNoMatchError false', async () => { write: true, }; expect(async () => { - await MongoDBUpdateInsertOne({ request, connection }); + await MongoDBVersionedUpdateOne({ request, connection }); }).rejects.toThrow('No matching record to update.'); }); @@ -427,7 +427,7 @@ test('updateInsertOne disableNoMatchError false logCollection', async () => { write: true, }; await expect(async () => { - await MongoDBUpdateInsertOne({ + await MongoDBVersionedUpdateOne({ request, blockId: 'blockId', connectionId: 'connectionId', @@ -449,7 +449,7 @@ test('updateInsertOne disableNoMatchError false logCollection', async () => { requestId: 'updateInsertOne_disable_no_match_error_false', before: null, after: null, - type: 'MongoDBUpdateInsertOne', + type: 'MongoDBVersionedUpdateOne', meta: { meta: true }, }); }); @@ -465,7 +465,7 @@ test('updateInsertOne connection error', async () => { collection, write: true, }; - await expect(MongoDBUpdateInsertOne({ request, connection })).rejects.toThrow( + await expect(MongoDBVersionedUpdateOne({ request, connection })).rejects.toThrow( 'Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"' ); }); @@ -481,7 +481,7 @@ test('updateInsertOne mongodb error', async () => { collection, write: true, }; - await expect(MongoDBUpdateInsertOne({ request, connection })).rejects.toThrow( + await expect(MongoDBVersionedUpdateOne({ request, connection })).rejects.toThrow( 'Unknown modifier: $badOp' ); }); @@ -497,7 +497,7 @@ test('updateInsertOne validate write', async () => { collection, write: true, }; - const res = await MongoDBUpdateInsertOne({ + const res = await MongoDBVersionedUpdateOne({ request, blockId: 'blockId', connectionId: 'connectionId', @@ -537,62 +537,62 @@ test('checkWrite should be true', async () => { test('request not an object', async () => { const request = 'request'; expect(() => validate({ schema, data: request })).toThrow( - 'MongoDBUpdateInsertOne request properties should be an object.' + 'MongoDBVersionedUpdateOne request properties should be an object.' ); }); test('request no filter', async () => { const request = { update: {} }; expect(() => validate({ schema, data: request })).toThrow( - 'MongoDBUpdateInsertOne request should have required property "filter".' + 'MongoDBVersionedUpdateOne request should have required property "filter".' ); }); test('request no update', async () => { const request = { filter: {} }; expect(() => validate({ schema, data: request })).toThrow( - 'MongoDBUpdateInsertOne request should have required property "update".' + 'MongoDBVersionedUpdateOne request should have required property "update".' ); }); test('request update not an object', async () => { const request = { update: 'update', filter: {} }; expect(() => validate({ schema, data: request })).toThrow( - 'MongoDBUpdateInsertOne request property "update" should be an object.' + 'MongoDBVersionedUpdateOne request property "update" should be an object.' ); }); test('request filter not an object', async () => { const request = { update: {}, filter: 'filter' }; expect(() => validate({ schema, data: request })).toThrow( - 'MongoDBUpdateInsertOne request property "filter" should be an object.' + 'MongoDBVersionedUpdateOne request property "filter" should be an object.' ); }); test('request options not an object', async () => { const request = { update: {}, filter: {}, options: 'options' }; expect(() => validate({ schema, data: request })).toThrow( - 'MongoDBUpdateInsertOne request property "options" should be an object.' + 'MongoDBVersionedUpdateOne request property "options" should be an object.' ); }); test('request update options not an object', async () => { const request = { update: {}, filter: {}, options: { update: 'update' } }; expect(() => validate({ schema, data: request })).toThrow( - 'MongoDBUpdateInsertOne request property option "update" should be an object.' + 'MongoDBVersionedUpdateOne request property option "update" should be an object.' ); }); test('request find options not an object', async () => { const request = { update: {}, filter: {}, options: { find: 'find' } }; expect(() => validate({ schema, data: request })).toThrow( - 'MongoDBUpdateInsertOne request property option "find" should be an object.' + 'MongoDBVersionedUpdateOne request property option "find" should be an object.' ); }); test('request insert options not an object', async () => { const request = { update: {}, filter: {}, options: { insert: 'insert' } }; expect(() => validate({ schema, data: request })).toThrow( - 'MongoDBUpdateInsertOne request property option "insert" should be an object.' + 'MongoDBVersionedUpdateOne request property option "insert" should be an object.' ); }); diff --git a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBVersionedUpdateOne/schema.js similarity index 66% rename from plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js rename to plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBVersionedUpdateOne/schema.js index 33ec53b..5ef5f07 100644 --- a/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBUpdateInsertOne/schema.js +++ b/plugins/community-plugin-mongodb/src/connections/MongoDBCollection/MongoDBVersionedUpdateOne/schema.js @@ -16,7 +16,7 @@ export default { $schema: 'http://json-schema.org/draft-07/schema#', - title: 'Lowdefy Request Schema - MongoDBUpdateInsertOne', + title: 'Lowdefy Request Schema - MongoDBVersionedUpdateOne', type: 'object', required: ['filter', 'update'], properties: { @@ -24,52 +24,52 @@ export default { type: 'object', description: 'The filter used to select the document to find and insert.', errorMessage: { - type: 'MongoDBUpdateInsertOne request property "filter" should be an object.', + type: 'MongoDBVersionedUpdateOne request property "filter" should be an object.', }, }, update: { type: ['object', 'array'], description: 'The update operations to be applied to the new inserted document.', errorMessage: { - type: 'MongoDBUpdateInsertOne request property "update" should be an object.', + type: 'MongoDBVersionedUpdateOne request property "update" should be an object.', }, }, options: { type: 'object', description: 'Optional settings for each mongodb operation.', errorMessage: { - type: 'MongoDBUpdateInsertOne request property "options" should be an object.', + type: 'MongoDBVersionedUpdateOne request property "options" should be an object.', }, properties: { find: { type: 'object', description: 'Find options for mongodb find one.', errorMessage: { - type: 'MongoDBUpdateInsertOne request property option "find" should be an object.', + type: 'MongoDBVersionedUpdateOne request property option "find" should be an object.', }, }, insert: { type: 'object', description: 'Insert options for mongodb insert one.', errorMessage: { - type: 'MongoDBUpdateInsertOne request property option "insert" should be an object.', + type: 'MongoDBVersionedUpdateOne request property option "insert" should be an object.', }, }, update: { type: 'object', description: 'Update options for mongodb find one and update.', errorMessage: { - type: 'MongoDBUpdateInsertOne request property option "update" should be an object.', + type: 'MongoDBVersionedUpdateOne request property option "update" should be an object.', }, }, }, }, }, errorMessage: { - type: 'MongoDBUpdateInsertOne request properties should be an object.', + type: 'MongoDBVersionedUpdateOne request properties should be an object.', required: { - filter: 'MongoDBUpdateInsertOne request should have required property "filter".', - update: 'MongoDBUpdateInsertOne request should have required property "update".', + filter: 'MongoDBVersionedUpdateOne request should have required property "filter".', + update: 'MongoDBVersionedUpdateOne request should have required property "update".', }, }, }; From f105876a8caa1ee74ff013dd4afe607500a0e23c Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Tue, 19 Nov 2024 12:22:34 +0200 Subject: [PATCH 11/15] fix: Updated changeset. --- .changeset/hip-seas-camp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/hip-seas-camp.md b/.changeset/hip-seas-camp.md index 2ffc43e..09b7d1a 100644 --- a/.changeset/hip-seas-camp.md +++ b/.changeset/hip-seas-camp.md @@ -2,4 +2,4 @@ '@lowdefy/community-plugin-mongodb': minor --- -Added MongoDBUpdateInsertOne request. +Added MongoDBVersionedUpdateOne request. From 26146b970a28af12f0f9a59b09c3e39dfe9a1687 Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Tue, 19 Nov 2024 17:15:47 +0200 Subject: [PATCH 12/15] feat: Added documentation. --- .../community-plugin-mongodb/MongoDB.yaml | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/apps/docs/community-plugin-mongodb/MongoDB.yaml b/apps/docs/community-plugin-mongodb/MongoDB.yaml index d3de46c..6e5bb18 100644 --- a/apps/docs/community-plugin-mongodb/MongoDB.yaml +++ b/apps/docs/community-plugin-mongodb/MongoDB.yaml @@ -44,6 +44,7 @@ _ref: - MongoDBUpdateOne - MongoDBInsertConsecutiveId - MongoDBInsertManyConsecutiveIds + - MongoDBVersionedUpdateOne ### MongoDBUpdateOne @@ -200,3 +201,70 @@ _ref: ..., } ``` + + ### MongoDBVersionedUpdateOne + + #### Properties + + The `MongoDBVersionedUpdateOne` request uses the standard [MongoDBUpdateOne](https://docs.lowdefy.com/MongoDB) syntax to insert an identical document to the one found with only the updated field(s) changed. + The `MongoDBVersionedUpdateOne` request consists of __three__ stages; a `find` to locate the document, an `insert` to insert an identical document, and an `update` to apply the changes. + + - `options: object` - An object containing options for each stage of the request. + + The request returns the following: + + If a log collection is not set on the connection + + - `acknowledged: boolean` - Acknowledgement of the insertion. + - `matchedCount: number` - The number of matched documents. + - `modifiedCount: number` - The number of modified documents. + - `upsertedId: string` - The ID of the upserted document. + - `upsertedCount: number` - The number of upserts. + + If a log collection is set on the connection + + - `lastErrorObject: object` - An object containing data on whether an existing document was updated or not. + - `ok: number` - Status of the request, 1 if the request was successful and 0 otherwise. + - `'$clusterTime': object` - An object containing data on the cluster time and signature. + - `operationTime: date` - Timestamp object of the operation time. + + #### Example + + ###### Find the document with the highest item number and insert an identical document with a new name. A new document is inserted if none is found. + ```yaml + id: versioned_update + type: MongoDBVersionedUpdateOne + connectionId: items + payload: + id: + _state: item._id + properties: + options: + find: + sort: + item_number: -1 + update: + upsert: true + filter: + name: 'old_item' + update: + $set: + name: 'new_item' + ``` + + ###### Before + ```json + [ + {"_id": ObjectId("..."), "name": "old_item", item_number: 2, "item_id": "old_item_2"}, + {"_id": ObjectId("..."), "name": "old_item", item_number: 1, "item_id": "old_item_1"}, + ] + ``` + + ###### After + ```json + [ + {"_id": ObjectId("..."), "name": "old_item", item_number: 2, "item_id": "old_item_2"}, + {"_id": ObjectId("..."), "name": "old_item", item_number: 1, "item_id": "old_item_1"}, + {"_id": ObjectId("..."), "name": "new_item", item_number: 2, "item_id": "old_item_2"}, + ] + ``` From d03343cd5b2463f07f261ebdabd61de5933a3d41 Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Tue, 19 Nov 2024 17:18:44 +0200 Subject: [PATCH 13/15] fix: Remove payload docs. --- apps/docs/community-plugin-mongodb/MongoDB.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/docs/community-plugin-mongodb/MongoDB.yaml b/apps/docs/community-plugin-mongodb/MongoDB.yaml index 6e5bb18..cc3279f 100644 --- a/apps/docs/community-plugin-mongodb/MongoDB.yaml +++ b/apps/docs/community-plugin-mongodb/MongoDB.yaml @@ -235,9 +235,6 @@ _ref: id: versioned_update type: MongoDBVersionedUpdateOne connectionId: items - payload: - id: - _state: item._id properties: options: find: From 3c87146acaec9a491a399a618c2e1abee1adb3aa Mon Sep 17 00:00:00 2001 From: Salahuddin Saiet Date: Wed, 20 Nov 2024 10:35:21 +0200 Subject: [PATCH 14/15] fix: Updated documentation. --- apps/docs/community-plugin-mongodb/MongoDB.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/docs/community-plugin-mongodb/MongoDB.yaml b/apps/docs/community-plugin-mongodb/MongoDB.yaml index cc3279f..71c8a57 100644 --- a/apps/docs/community-plugin-mongodb/MongoDB.yaml +++ b/apps/docs/community-plugin-mongodb/MongoDB.yaml @@ -207,9 +207,16 @@ _ref: #### Properties The `MongoDBVersionedUpdateOne` request uses the standard [MongoDBUpdateOne](https://docs.lowdefy.com/MongoDB) syntax to insert an identical document to the one found with only the updated field(s) changed. - The `MongoDBVersionedUpdateOne` request consists of __three__ stages; a `find` to locate the document, an `insert` to insert an identical document, and an `update` to apply the changes. - - `options: object` - An object containing options for each stage of the request. + The `MongoDBVersionedUpdateOne` request consists of __three stages__: + - `Find`: locates the document. + - `Insert`: creates and inserts the document identical to the one found. + - `Update`: applies the specified changes to the inserted document. + + The `options` property: + - `find: object` - An object containing options for the `find` stage of the request. + - `insert: object` - An object containing options for the `insert` stage of the request. + - `update: object` - An object containing options for the `update` stage of the request. The request returns the following: From cf6d0e06b6f3b9aa8f441b4def5d742e2e4b5745 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 20 Nov 2024 15:03:05 +0200 Subject: [PATCH 15/15] fix: Update MongoDBVersionedUpdateOne docs. --- .../community-plugin-mongodb/MongoDB.yaml | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/apps/docs/community-plugin-mongodb/MongoDB.yaml b/apps/docs/community-plugin-mongodb/MongoDB.yaml index 71c8a57..4322334 100644 --- a/apps/docs/community-plugin-mongodb/MongoDB.yaml +++ b/apps/docs/community-plugin-mongodb/MongoDB.yaml @@ -235,6 +235,8 @@ _ref: - `'$clusterTime': object` - An object containing data on the cluster time and signature. - `operationTime: date` - Timestamp object of the operation time. + > To ensure that the most recent document is updated, a `sort` specification should be given in the find options. + #### Example ###### Find the document with the highest item number and insert an identical document with a new name. A new document is inserted if none is found. @@ -243,32 +245,34 @@ _ref: type: MongoDBVersionedUpdateOne connectionId: items properties: + filter: + entity_id: 123 + update: + $set: + name: New Name + updated_at: + _date: now options: find: sort: - item_number: -1 + updated_at: -1 update: upsert: true - filter: - name: 'old_item' - update: - $set: - name: 'new_item' ``` ###### Before ```json [ - {"_id": ObjectId("..."), "name": "old_item", item_number: 2, "item_id": "old_item_2"}, - {"_id": ObjectId("..."), "name": "old_item", item_number: 1, "item_id": "old_item_1"}, + {"_id": ObjectId("..."), "entity_id": 123, "name": "Name", "value": 23,"updated_at": ISODate("2024-01-01") }, + {"_id": ObjectId("..."), "entity_id": 123, "name": "Name", "value": 33, "updated_at": ISODate("2024-03-01")}, ] ``` ###### After ```json - [ - {"_id": ObjectId("..."), "name": "old_item", item_number: 2, "item_id": "old_item_2"}, - {"_id": ObjectId("..."), "name": "old_item", item_number: 1, "item_id": "old_item_1"}, - {"_id": ObjectId("..."), "name": "new_item", item_number: 2, "item_id": "old_item_2"}, + [ + {"_id": ObjectId("..."), "entity_id": 123, "name": "Name", "value": 23,"updated_at": ISODate("2024-01-01") }, + {"_id": ObjectId("..."), "entity_id": 123, "name": "Name", "value": 33, "updated_at": ISODate("2024-03-01")}, + {"_id": ObjectId("..."), "entity_id": 123, "name": "New Name", "value": 33, "updated_at": ISODate("2024-11-01")}, ] ```