🔀 Merge branch 'mongodb_updates' of https://github.com/clonardo/n8n into clonardo-mongodb_updates

This commit is contained in:
Jan Oberhauser 2020-05-01 18:56:48 +02:00
commit eb1213248e
10 changed files with 346 additions and 196 deletions

View file

@ -6,3 +6,10 @@ indent_style = tab
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[package.json]
indent_style = space
indent_size = 2
[*.ts]
quote_type = single

View file

@ -6,6 +6,7 @@
"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",
@ -14,6 +15,7 @@
},
"devDependencies": {
"lerna": "^3.13.1",
"rimraf": "^3.0.2",
"run-script-os": "^1.0.7"
},
"postcss": {}

View file

@ -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.32.0",
"n8n-editor-ui": "~0.43.0",

View file

@ -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
};

View file

@ -1,8 +1,4 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
import { ICredentialType, NodePropertyTypes } from 'n8n-workflow';
export class MongoDb implements ICredentialType {
name = 'mongoDb';
@ -12,34 +8,57 @@ export class MongoDb implements ICredentialType {
displayName: 'Host',
name: 'host',
type: 'string' as NodePropertyTypes,
default: 'localhost',
default: 'localhost'
},
{
displayName: 'Database',
name: 'database',
type: 'string' as NodePropertyTypes,
default: '',
description:
'Note: the database should still be provided even if using an override connection string'
},
{
displayName: 'User',
name: 'user',
type: 'string' as NodePropertyTypes,
default: '',
default: ''
},
{
displayName: 'Password',
name: 'password',
type: 'string' as NodePropertyTypes,
typeOptions: {
password: true,
password: true
},
default: '',
default: ''
},
{
displayName: 'Port',
name: 'port',
type: 'number' as NodePropertyTypes,
default: 27017,
default: 27017
},
{
displayName: 'Override conn string',
name: 'shouldOverrideConnString',
type: 'boolean' as NodePropertyTypes,
default: false,
required: false,
description:
'Whether to override the generated connection string. Credentials will also be ignored in this case.'
},
{
displayName: 'Conn string override',
name: 'connStringOverrideVal',
type: 'string' as NodePropertyTypes,
typeOptions: {
rows: 1
},
default: '',
placeholder: `mongodb://USERNAMEHERE:PASSWORDHERE@localhost:27017/?authSource=admin&readPreference=primary&appname=n8n&ssl=false`,
required: false,
description: `If provided, the value here will be used as a MongoDB connection string, and the MongoDB credentials will be ignored`
}
];
}

View file

@ -3,192 +3,29 @@ import {
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
INodeTypeDescription
} from 'n8n-workflow';
import { nodeDescription } from './mongo.node.options';
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;
});
}
import {
getItemCopy,
validateAndResolveMongoCredentials
} 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<INodeExecutionData[][]> {
const { database, connectionString } = validateAndResolveMongoCredentials(
this.getCredentials('mongoDb')
);
const credentials = this.getCredentials('mongoDb');
const client: MongoClient = await MongoClient.connect(connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true
});
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
let connectionUri = '';
if (credentials.port) {
connectionUri = `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`;
} else {
connectionUri = `mongodb+srv://${credentials.user}:${credentials.password}@${credentials.host}`;
}
const client = await MongoClient.connect(connectionUri, { useNewUrlParser: true, useUnifiedTopology: true });
const mdb = client.db(credentials.database as string);
const mdb = client.db(database as string);
let returnItems = [];
@ -206,7 +43,6 @@ export class MongoDb implements INodeType {
.toArray();
returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]);
} else if (operation === 'insert') {
// ----------------------------------
// insert
@ -229,7 +65,7 @@ export class MongoDb implements INodeType {
returnItems.push({
json: {
...insertItems[parseInt(i, 10)],
id: insertedIds[parseInt(i, 10)] as string,
id: insertedIds[parseInt(i, 10)] as string
}
});
}
@ -258,7 +94,7 @@ export class MongoDb implements INodeType {
continue;
}
const filter: { [key: string] :string } = {};
const filter: { [key: string]: string } = {};
filter[updateKey] = item[updateKey] as string;
await mdb
@ -267,7 +103,6 @@ export class MongoDb implements INodeType {
}
returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]);
} else {
throw new Error(`The operation "${operation}" is not supported!`);
}

View file

@ -0,0 +1,131 @@
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: '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.'
}
]
};

View file

@ -0,0 +1,52 @@
import { CredentialInformation } from 'n8n-workflow';
/**
* Credentials object for Mongo, if using individual parameters
*/
export interface IMongoParametricCredentials {
/**
* Whether to allow overriding the parametric credentials with a connection string
*/
shouldOverrideConnString: false | undefined | null;
host: string;
database: string;
user: string;
password: string;
port?: number;
}
/**
* Credentials object for Mongo, if using override connection string
*/
export interface IMongoOverrideCredentials {
/**
* Whether to allow overriding the parametric credentials with a connection string
*/
shouldOverrideConnString: true;
/**
* If using an override connection string, this is where it will be.
*/
connStringOverrideVal: string;
database: string;
}
/**
* Unified credential object type (whether params are overridden with a connection string or not)
*/
export type IMongoCredentialsType =
| IMongoParametricCredentials
| IMongoOverrideCredentials;
/**
* Resolve the database and connection string from input credentials
*/
export type IMongoCredentials = {
/**
* Database name (used to create the Mongo client)
*/
database: string;
/**
* Generated connection string (after validating and figuring out overrides)
*/
connectionString: string;
};

View file

@ -0,0 +1,104 @@
import {
IDataObject,
INodeExecutionData,
ICredentialDataDecryptedObject
} from 'n8n-workflow';
import {
IMongoCredentialsType,
IMongoParametricCredentials,
IMongoCredentials
} from './mongo.node.types';
/**
* 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
*/
function buildParameterizedConnString(
credentials: IMongoParametricCredentials
): string {
if (credentials.port) {
return `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`;
} else {
return `mongodb+srv://${credentials.user}:${credentials.password}@${credentials.host}`;
}
}
/**
* Build mongoDb connection string and resolve database name.
* If a connection string override value is provided, that will be used in place of individual args
*
* @param {ICredentialDataDecryptedObject} credentials raw/input MongoDB credentials to use
*/
function buildMongoConnectionParams(
credentials: IMongoCredentialsType
): IMongoCredentials {
const sanitizedDbName =
credentials.database && credentials.database.trim().length > 0
? credentials.database.trim()
: '';
if (credentials.shouldOverrideConnString) {
if (
credentials.connStringOverrideVal &&
credentials.connStringOverrideVal.trim().length > 0
) {
return {
connectionString: credentials.connStringOverrideVal.trim(),
database: sanitizedDbName
};
} else {
throw new Error(
'Cannot override credentials: valid MongoDB connection string not provided '
);
}
} else {
return {
connectionString: buildParameterizedConnString(credentials),
database: sanitizedDbName
};
}
}
/**
* Verify credentials. If ok, build mongoDb connection string and resolve database name.
*
* @param {ICredentialDataDecryptedObject} credentials raw/input MongoDB credentials to use
*/
export function validateAndResolveMongoCredentials(
credentials?: ICredentialDataDecryptedObject
): IMongoCredentials {
if (credentials == undefined) {
throw new Error('No credentials got returned!');
} else {
return buildMongoConnectionParams(
(credentials as any) as IMongoCredentialsType
);
}
}
/**
* 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;
});
}

View file

@ -261,7 +261,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",
@ -292,7 +292,7 @@
"lodash.unset": "^4.5.2",
"moment": "2.24.0",
"moment-timezone": "^0.5.28",
"mongodb": "^3.3.2",
"mongodb": "^3.5.5",
"mysql2": "^2.0.1",
"n8n-core": "~0.32.0",
"nodemailer": "^5.1.1",