From c6aa73cc2f4598737e332f489db1a3a6abbbb81e Mon Sep 17 00:00:00 2001 From: Chris Lonardo Date: Sun, 5 Apr 2020 21:06:00 -0400 Subject: [PATCH] upgrade mongodb client to support mongodb 4.2 and allow conn string override --- package.json | 38 +-- packages/cli/package.json | 2 +- packages/editor-ui/src/main.ts | 2 +- .../nodes-base/nodes/MongoDb/MongoDb.node.ts | 245 ++++-------------- .../nodes/MongoDb/mongo.node.options.ts | 156 +++++++++++ .../nodes/MongoDb/mongo.node.utils.ts | 47 ++++ packages/nodes-base/package.json | 6 +- 7 files changed, 279 insertions(+), 217 deletions(-) create mode 100644 packages/nodes-base/nodes/MongoDb/mongo.node.options.ts create mode 100644 packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts diff --git a/package.json b/package.json index b045830f51..e14010e7a8 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,22 @@ { - "name": "n8n", - "private": true, - "homepage": "https://n8n.io", - "scripts": { - "bootstrap": "lerna bootstrap --hoist --no-ci", - "build": "lerna exec npm run build", - "dev": "lerna exec npm run dev --parallel", - "start": "run-script-os", - "start:default": "cd packages/cli/bin && ./n8n", - "start:windows": "cd packages/cli/bin && n8n", - "test": "lerna run test", - "watch": "lerna run --parallel watch" - }, - "devDependencies": { - "lerna": "^3.13.1", - "run-script-os": "^1.0.7" - }, - "postcss": {} + "name": "n8n", + "private": true, + "homepage": "https://n8n.io", + "scripts": { + "bootstrap": "lerna bootstrap --hoist --no-ci", + "build": "lerna exec npm run build", + "dev": "lerna exec npm run dev --parallel", + "clean:dist": "lerna exec -- rimraf ./dist", + "start": "run-script-os", + "start:default": "cd packages/cli/bin && ./n8n", + "start:windows": "cd packages/cli/bin && n8n", + "test": "lerna run test", + "watch": "lerna run --parallel watch" + }, + "devDependencies": { + "lerna": "^3.13.1", + "rimraf": "^3.0.2", + "run-script-os": "^1.0.7" + }, + "postcss": {} } diff --git a/packages/cli/package.json b/packages/cli/package.json index aabfbb7f7a..386fce5dc1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -93,7 +93,7 @@ "jwks-rsa": "^1.6.0", "localtunnel": "^2.0.0", "lodash.get": "^4.4.2", - "mongodb": "^3.2.3", + "mongodb": "^3.5.5", "mysql2": "^2.0.1", "n8n-core": "~0.29.0", "n8n-editor-ui": "~0.40.0", diff --git a/packages/editor-ui/src/main.ts b/packages/editor-ui/src/main.ts index f9a7f879a0..e1b30cd166 100644 --- a/packages/editor-ui/src/main.ts +++ b/packages/editor-ui/src/main.ts @@ -179,7 +179,7 @@ if (process.env.NODE_ENV !== 'production') { // not do anything about it anyway return; } - console.error('error cought in main.ts'); // eslint-disable-line no-console + console.error('error caught in main.ts'); // eslint-disable-line no-console console.error(message); // eslint-disable-line no-console console.error(error); // eslint-disable-line no-console }; diff --git a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts index 0aa6d603ac..d15fd0112e 100644 --- a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts +++ b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts @@ -1,227 +1,85 @@ -import { IExecuteFunctions } from 'n8n-core'; +import { IExecuteFunctions } from "n8n-core"; import { IDataObject, INodeExecutionData, INodeType, INodeTypeDescription, -} from 'n8n-workflow'; - -import { MongoClient } from 'mongodb'; - - -/** - * Returns of copy of the items which only contains the json data and - * of that only the define properties - * - * @param {INodeExecutionData[]} items The items to copy - * @param {string[]} properties The properties it should include - * @returns - */ -function getItemCopy(items: INodeExecutionData[], properties: string[]): IDataObject[] { - // Prepare the data to insert and copy it to be returned - let newItem: IDataObject; - return items.map((item) => { - newItem = {}; - for (const property of properties) { - if (item.json[property] === undefined) { - newItem[property] = null; - } else { - newItem[property] = JSON.parse(JSON.stringify(item.json[property])); - } - } - return newItem; - }); -} - +} from "n8n-workflow"; +import { nodeDescription } from "./mongo.node.options"; +import { MongoClient } from "mongodb"; +import { getItemCopy, buildParameterizedConnString } from "./mongo.node.utils"; export class MongoDb implements INodeType { - description: INodeTypeDescription = { - displayName: 'MongoDB', - name: 'mongoDb', - icon: 'file:mongoDb.png', - group: ['input'], - version: 1, - description: 'Find, insert and update documents in MongoDB.', - defaults: { - name: 'MongoDB', - color: '#13AA52', - }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'mongoDb', - required: true, - } - ], - properties: [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - options: [ - { - name: 'Find', - value: 'find', - description: 'Find documents.', - }, - { - name: 'Insert', - value: 'insert', - description: 'Insert documents.', - }, - { - name: 'Update', - value: 'update', - description: 'Updates documents.', - }, - ], - default: 'find', - description: 'The operation to perform.', - }, - - { - displayName: 'Collection', - name: 'collection', - type: 'string', - required: true, - default: '', - description: 'MongoDB Collection' - }, - - // ---------------------------------- - // find - // ---------------------------------- - { - displayName: 'Query (JSON format)', - name: 'query', - type: 'string', - typeOptions: { - rows: 5, - }, - displayOptions: { - show: { - operation: [ - 'find' - ], - }, - }, - default: '{}', - placeholder: `{ "birth": { "$gt": "1950-01-01" } }`, - required: true, - description: 'MongoDB Find query.', - }, - - - // ---------------------------------- - // insert - // ---------------------------------- - { - displayName: 'Fields', - name: 'fields', - type: 'string', - displayOptions: { - show: { - operation: [ - 'insert' - ], - }, - }, - default: '', - placeholder: 'name,description', - description: 'Comma separated list of the fields to be included into the new document.', - }, - - - // ---------------------------------- - // update - // ---------------------------------- - { - displayName: 'Update Key', - name: 'updateKey', - type: 'string', - displayOptions: { - show: { - operation: [ - 'update' - ], - }, - }, - default: 'id', - required: true, - description: 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".', - }, - { - displayName: 'Fields', - name: 'fields', - type: 'string', - displayOptions: { - show: { - operation: [ - 'update' - ], - }, - }, - default: '', - placeholder: 'name,description', - description: 'Comma separated list of the fields to be included into the new document.', - }, - - ] - }; - + description: INodeTypeDescription = nodeDescription; async execute(this: IExecuteFunctions): Promise { + const credentials = this.getCredentials("mongoDb"); - const credentials = this.getCredentials('mongoDb'); + // user can optionally override connection string + const useOverriddenConnString = this.getNodeParameter( + "shouldOverrideConnString", + 0 + ) as boolean; if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new Error("No credentials got returned!"); } - let connectionUri = ''; - - if (credentials.port) { - connectionUri = `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`; + let connectionUri = ""; + if (useOverriddenConnString === true) { + const connStringInput = this.getNodeParameter( + "connStringOverrideVal", + 0 + ) as string; + if (connStringInput && connStringInput.length > 0) { + connectionUri = connStringInput; + } else { + throw new Error( + "Cannot override credentials: valid MongoDB connection string not provided " + ); + } } else { - connectionUri = `mongodb+srv://${credentials.user}:${credentials.password}@${credentials.host}`; + connectionUri = buildParameterizedConnString(credentials); } - const client = await MongoClient.connect(connectionUri, { useNewUrlParser: true, useUnifiedTopology: true }); + const client: MongoClient = await MongoClient.connect(connectionUri, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + const mdb = client.db(credentials.database as string); let returnItems = []; const items = this.getInputData(); - const operation = this.getNodeParameter('operation', 0) as string; + const operation = this.getNodeParameter("operation", 0) as string; - if (operation === 'find') { + if (operation === "find") { // ---------------------------------- // find // ---------------------------------- const queryResult = await mdb - .collection(this.getNodeParameter('collection', 0) as string) - .find(JSON.parse(this.getNodeParameter('query', 0) as string)) + .collection(this.getNodeParameter("collection", 0) as string) + .find(JSON.parse(this.getNodeParameter("query", 0) as string)) .toArray(); returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]); - - } else if (operation === 'insert') { + } else if (operation === "insert") { // ---------------------------------- // insert // ---------------------------------- // Prepare the data to insert and copy it to be returned - const fields = (this.getNodeParameter('fields', 0) as string) - .split(',') - .map(f => f.trim()) - .filter(f => !!f); + const fields = (this.getNodeParameter("fields", 0) as string) + .split(",") + .map((f) => f.trim()) + .filter((f) => !!f); const insertItems = getItemCopy(items, fields); const { insertedIds } = await mdb - .collection(this.getNodeParameter('collection', 0) as string) + .collection(this.getNodeParameter("collection", 0) as string) .insertMany(insertItems); // Add the id to the data @@ -230,20 +88,20 @@ export class MongoDb implements INodeType { json: { ...insertItems[parseInt(i, 10)], id: insertedIds[parseInt(i, 10)] as string, - } + }, }); } - } else if (operation === 'update') { + } else if (operation === "update") { // ---------------------------------- // update // ---------------------------------- - const fields = (this.getNodeParameter('fields', 0) as string) - .split(',') - .map(f => f.trim()) - .filter(f => !!f); + const fields = (this.getNodeParameter("fields", 0) as string) + .split(",") + .map((f) => f.trim()) + .filter((f) => !!f); - let updateKey = this.getNodeParameter('updateKey', 0) as string; + let updateKey = this.getNodeParameter("updateKey", 0) as string; updateKey = updateKey.trim(); if (!fields.includes(updateKey)) { @@ -258,16 +116,15 @@ export class MongoDb implements INodeType { continue; } - const filter: { [key: string] :string } = {}; + const filter: { [key: string]: string } = {}; filter[updateKey] = item[updateKey] as string; await mdb - .collection(this.getNodeParameter('collection', 0) as string) + .collection(this.getNodeParameter("collection", 0) as string) .updateOne(filter, { $set: item }); } returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); - } else { throw new Error(`The operation "${operation}" is not supported!`); } diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts new file mode 100644 index 0000000000..19f18f4ab7 --- /dev/null +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts @@ -0,0 +1,156 @@ +import { INodeTypeDescription } from "n8n-workflow"; + +/** + * Options to be displayed + */ +export const nodeDescription: INodeTypeDescription = { + displayName: "MongoDB", + name: "mongoDb", + icon: "file:mongoDb.png", + group: ["input"], + version: 1, + description: "Find, insert and update documents in MongoDB.", + defaults: { + name: "MongoDB", + color: "#13AA52", + }, + inputs: ["main"], + outputs: ["main"], + credentials: [ + { + name: "mongoDb", + required: true, + }, + ], + properties: [ + { + displayName: "Override conn string", + name: "shouldOverrideConnString", + type: "boolean", + default: false, + description: + "Whether to override the generated connection string. Credentials will also be ignored in this case.", + }, + { + displayName: "Conn string", + name: "connStringOverrideVal", + type: "string", + typeOptions: { + rows: 1, + }, + displayOptions: { + show: { + shouldOverrideConnString: [true], + }, + }, + default: "", + placeholder: `mongodb://USERNAMEHERE:PASSWORDHERE@localhost:27017/?authSource=admin&readPreference=primary&appname=n8n&ssl=false`, + required: false, + description: `If "Override conn string" is checked, the value here will be used as a MongoDB connection string, and the MongoDB credentials will be ignored`, + }, + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "Find", + value: "find", + description: "Find documents.", + }, + { + name: "Insert", + value: "insert", + description: "Insert documents.", + }, + { + name: "Update", + value: "update", + description: "Updates documents.", + }, + ], + default: "find", + description: "The operation to perform.", + }, + + { + displayName: "Collection", + name: "collection", + type: "string", + required: true, + default: "", + description: "MongoDB Collection", + }, + + // ---------------------------------- + // find + // ---------------------------------- + { + displayName: "Query (JSON format)", + name: "query", + type: "string", + typeOptions: { + rows: 5, + }, + displayOptions: { + show: { + operation: ["find"], + }, + }, + default: "{}", + placeholder: `{ "birth": { "$gt": "1950-01-01" } }`, + required: true, + description: "MongoDB Find query.", + }, + + // ---------------------------------- + // insert + // ---------------------------------- + { + displayName: "Fields", + name: "fields", + type: "string", + displayOptions: { + show: { + operation: ["insert"], + }, + }, + default: "", + placeholder: "name,description", + description: + "Comma separated list of the fields to be included into the new document.", + }, + + // ---------------------------------- + // update + // ---------------------------------- + { + displayName: "Update Key", + name: "updateKey", + type: "string", + displayOptions: { + show: { + operation: ["update"], + }, + }, + default: "id", + required: true, + description: + 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".', + }, + { + displayName: "Fields", + name: "fields", + type: "string", + displayOptions: { + show: { + operation: ["update"], + }, + }, + default: "", + placeholder: "name,description", + description: + "Comma separated list of the fields to be included into the new document.", + }, + ], +}; diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts new file mode 100644 index 0000000000..7de725f08a --- /dev/null +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts @@ -0,0 +1,47 @@ +import { + IDataObject, + INodeExecutionData, + ICredentialDataDecryptedObject, +} from "n8n-workflow"; + +/** + * Standard way of building the MongoDB connection string, unless overridden with a provided string + * + * @param {ICredentialDataDecryptedObject} credentials MongoDB credentials to use, unless conn string is overridden + */ +export function buildParameterizedConnString( + credentials: ICredentialDataDecryptedObject +): string { + if (credentials.port) { + return `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`; + } else { + return `mongodb+srv://${credentials.user}:${credentials.password}@${credentials.host}`; + } +} + +/** + * Returns of copy of the items which only contains the json data and + * of that only the define properties + * + * @param {INodeExecutionData[]} items The items to copy + * @param {string[]} properties The properties it should include + * @returns + */ +export function getItemCopy( + items: INodeExecutionData[], + properties: string[] +): IDataObject[] { + // Prepare the data to insert and copy it to be returned + let newItem: IDataObject; + return items.map((item) => { + newItem = {}; + for (const property of properties) { + if (item.json[property] === undefined) { + newItem[property] = null; + } else { + newItem[property] = JSON.parse(JSON.stringify(item.json[property])); + } + } + return newItem; + }); +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index e197bd6ca4..b1a3cbda7b 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -75,7 +75,7 @@ "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", "dist/credentials/MauticApi.credentials.js", - "dist/credentials/MoceanApi.credentials.js", + "dist/credentials/MoceanApi.credentials.js", "dist/credentials/MondayComApi.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/Msg91Api.credentials.js", @@ -255,7 +255,7 @@ "@types/jest": "^24.0.18", "@types/lodash.set": "^4.3.6", "@types/moment-timezone": "^0.5.12", - "@types/mongodb": "^3.3.6", + "@types/mongodb": "^3.5.4", "@types/node": "^10.10.1", "@types/nodemailer": "^4.6.5", "@types/redis": "^2.8.11", @@ -284,7 +284,7 @@ "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "lodash.unset": "^4.5.2", - "mongodb": "^3.3.2", + "mongodb": "^3.5.5", "mysql2": "^2.0.1", "n8n-core": "~0.29.0", "nodemailer": "^5.1.1",