From a380a9a3941e9059668aaf478555fee045827b1a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 4 May 2020 08:56:01 +0200 Subject: [PATCH 01/33] :zap: Add first basic code for external hooks --- packages/cli/commands/start.ts | 7 ++ packages/cli/config/index.ts | 7 ++ packages/cli/src/ExternalHooks.ts | 89 +++++++++++++++++++ packages/cli/src/Interfaces.ts | 5 ++ packages/cli/src/Server.ts | 7 ++ .../cli/src/externalHooksTemp/test-hooks.ts | 10 +++ packages/cli/src/index.ts | 1 + 7 files changed, 126 insertions(+) create mode 100644 packages/cli/src/ExternalHooks.ts create mode 100644 packages/cli/src/externalHooksTemp/test-hooks.ts diff --git a/packages/cli/commands/start.ts b/packages/cli/commands/start.ts index 4e2efaacfd..65edec601d 100644 --- a/packages/cli/commands/start.ts +++ b/packages/cli/commands/start.ts @@ -12,6 +12,7 @@ import { ActiveWorkflowRunner, CredentialTypes, Db, + ExternalHooks, GenericHelpers, LoadNodesAndCredentials, NodeTypes, @@ -108,6 +109,12 @@ export class Start extends Command { const loadNodesAndCredentials = LoadNodesAndCredentials(); await loadNodesAndCredentials.init(); + // Load all external hooks + const externalHooks = ExternalHooks(); + await externalHooks.init(); + + // await externalHooks.run('credentials.new'); + // Add the found types to an instance other parts of the application can use const nodeTypes = NodeTypes(); await nodeTypes.init(loadNodesAndCredentials.nodeTypes); diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts index 69b3154064..5705a5fc7d 100644 --- a/packages/cli/config/index.ts +++ b/packages/cli/config/index.ts @@ -252,6 +252,13 @@ const config = convict({ }, }, + externalHookFiles: { + doc: 'Files containing external hooks', + format: String, + default: '', + env: 'EXTERNAL_HOOK_FILES' + }, + nodes: { exclude: { doc: 'Nodes not to load', diff --git a/packages/cli/src/ExternalHooks.ts b/packages/cli/src/ExternalHooks.ts new file mode 100644 index 0000000000..2c00a525c6 --- /dev/null +++ b/packages/cli/src/ExternalHooks.ts @@ -0,0 +1,89 @@ +import { + Db, + IDatabaseCollections, + IExternalHooks, +} from "./"; + +import * as config from '../config'; +// import { +// access as fsAccess, +// readdir as fsReaddir, +// readFile as fsReadFile, +// stat as fsStat, +// } from 'fs'; + +// TODO: Give different name +interface IHookData { + DbCollections: IDatabaseCollections; +} + +// export EXTERNAL_HOOK_FILES=/data/packages/cli/dist/src/externalHooksTemp/test-hooks.js + +class ExternalHooksClass implements IExternalHooks { + + externalHooks: { + [key: string]: Array<() => {}> + } = {}; + + + async init(): Promise { + console.log('ExternalHooks.init'); + + const externalHookFiles = config.get('externalHookFiles').split(','); + + console.log('externalHookFiles'); + console.log(externalHookFiles); + + for (let hookFilePath of externalHookFiles) { + hookFilePath = hookFilePath.trim(); + if (hookFilePath !== '') { + console.log(' --- load: ' + hookFilePath); + const hookFile = require(hookFilePath); + + for (const resource of Object.keys(hookFile)) { + // if (this.externalHooks[resource] === undefined) { + // this.externalHooks[resource] = {}; + // } + + for (const operation of Object.keys(hookFile[resource])) { + const hookString = `${resource}.${operation}`; + if (this.externalHooks[hookString] === undefined) { + this.externalHooks[hookString] = []; + } + + this.externalHooks[hookString].push.apply(this.externalHooks[hookString], hookFile[resource][operation]); + } + } + } + } + } + + async run(hookName: string): Promise { + console.log('RUN NOW: ' + hookName); + + const hookData: IHookData = { + DbCollections: Db.collections, + }; + + if (this.externalHooks[hookName] === undefined) { + return; + } + + for(const externalHookFunction of this.externalHooks[hookName]) { + externalHookFunction.call(hookData); + } + } + +} + + + +let externalHooksInstance: ExternalHooksClass | undefined; + +export function ExternalHooks(): ExternalHooksClass { + if (externalHooksInstance === undefined) { + externalHooksInstance = new ExternalHooksClass(); + } + + return externalHooksInstance; +} diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 225e02885f..b54e1eb299 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -188,6 +188,11 @@ export interface IExecutingWorkflowData { workflowExecution?: PCancelable; } +export interface IExternalHooks { + init(): Promise; + run(hookName: string): Promise; +} + export interface IN8nConfig { database: IN8nConfigDatabase; endpoints: IN8nConfigEndpoints; diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index ad2a3c1de5..baf4f1aa58 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -19,6 +19,7 @@ import { ActiveWorkflowRunner, CredentialTypes, Db, + ExternalHooks, IActivationError, ICustomRequest, ICredentialsDb, @@ -33,6 +34,7 @@ import { IExecutionsListResponse, IExecutionsStopData, IExecutionsSummary, + IExternalHooks, IN8nUISettings, IPackageVersions, IWorkflowBase, @@ -93,6 +95,7 @@ class App { testWebhooks: TestWebhooks.TestWebhooks; endpointWebhook: string; endpointWebhookTest: string; + externalHooks: IExternalHooks; saveDataErrorExecution: string; saveDataSuccessExecution: string; saveManualExecutions: boolean; @@ -124,6 +127,8 @@ class App { this.protocol = config.get('protocol'); this.sslKey = config.get('ssl_key'); this.sslCert = config.get('ssl_cert'); + + this.externalHooks = ExternalHooks(); } @@ -689,6 +694,8 @@ class App { throw new ResponseHelper.ResponseError(`Credentials with the same type and name exist already.`, undefined, 400); } + await this.externalHooks.run('credentials.new'); + // Encrypt the data const credentials = new Credentials(incomingData.name, incomingData.type, incomingData.nodesAccess); credentials.setData(incomingData.data, encryptionKey); diff --git a/packages/cli/src/externalHooksTemp/test-hooks.ts b/packages/cli/src/externalHooksTemp/test-hooks.ts new file mode 100644 index 0000000000..2cb60decfc --- /dev/null +++ b/packages/cli/src/externalHooksTemp/test-hooks.ts @@ -0,0 +1,10 @@ +export = { + credentials: { + new: [ + () => { + // Here any additional code can run or the creation blocked + throw new Error('No additional credentials can be created.'); + }, + ], + }, +}; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 0b7ae6ad0a..fba554c04e 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,4 +1,5 @@ export * from './CredentialTypes'; +export * from './ExternalHooks'; export * from './Interfaces'; export * from './LoadNodesAndCredentials'; export * from './NodeTypes'; From 0387671cae3084235619a20b76395f144df36f88 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 5 May 2020 01:23:54 +0200 Subject: [PATCH 02/33] :zap: Add additional external hooks and provide additional data --- packages/cli/config/index.ts | 2 +- packages/cli/src/ExternalHooks.ts | 48 ++++++++----------- packages/cli/src/Interfaces.ts | 6 ++- packages/cli/src/Server.ts | 18 +++++-- .../cli/src/externalHooksTemp/test-hooks.ts | 42 ++++++++++++++-- 5 files changed, 79 insertions(+), 37 deletions(-) diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts index 5705a5fc7d..7ef5155a67 100644 --- a/packages/cli/config/index.ts +++ b/packages/cli/config/index.ts @@ -253,7 +253,7 @@ const config = convict({ }, externalHookFiles: { - doc: 'Files containing external hooks', + doc: 'Files containing external hooks. Multiple files can be separated by colon (":")', format: String, default: '', env: 'EXTERNAL_HOOK_FILES' diff --git a/packages/cli/src/ExternalHooks.ts b/packages/cli/src/ExternalHooks.ts index 2c00a525c6..c4f12301b4 100644 --- a/packages/cli/src/ExternalHooks.ts +++ b/packages/cli/src/ExternalHooks.ts @@ -1,21 +1,10 @@ import { Db, - IDatabaseCollections, + IExternalHookFunctions, IExternalHooks, -} from "./"; +} from './'; import * as config from '../config'; -// import { -// access as fsAccess, -// readdir as fsReaddir, -// readFile as fsReadFile, -// stat as fsStat, -// } from 'fs'; - -// TODO: Give different name -interface IHookData { - DbCollections: IDatabaseCollections; -} // export EXTERNAL_HOOK_FILES=/data/packages/cli/dist/src/externalHooksTemp/test-hooks.js @@ -29,39 +18,42 @@ class ExternalHooksClass implements IExternalHooks { async init(): Promise { console.log('ExternalHooks.init'); - const externalHookFiles = config.get('externalHookFiles').split(','); + const externalHookFiles = config.get('externalHookFiles').split(':'); console.log('externalHookFiles'); console.log(externalHookFiles); + // Load all the provided hook-files for (let hookFilePath of externalHookFiles) { hookFilePath = hookFilePath.trim(); if (hookFilePath !== '') { console.log(' --- load: ' + hookFilePath); - const hookFile = require(hookFilePath); + try { + const hookFile = require(hookFilePath); - for (const resource of Object.keys(hookFile)) { - // if (this.externalHooks[resource] === undefined) { - // this.externalHooks[resource] = {}; - // } + for (const resource of Object.keys(hookFile)) { + for (const operation of Object.keys(hookFile[resource])) { + // Save all the hook functions directly under their string + // format in an array + const hookString = `${resource}.${operation}`; + if (this.externalHooks[hookString] === undefined) { + this.externalHooks[hookString] = []; + } - for (const operation of Object.keys(hookFile[resource])) { - const hookString = `${resource}.${operation}`; - if (this.externalHooks[hookString] === undefined) { - this.externalHooks[hookString] = []; + this.externalHooks[hookString].push.apply(this.externalHooks[hookString], hookFile[resource][operation]); } - - this.externalHooks[hookString].push.apply(this.externalHooks[hookString], hookFile[resource][operation]); } + } catch (error) { + throw new Error(`Problem loading external hook file "${hookFilePath}": ${error.message}`); } } } } - async run(hookName: string): Promise { + async run(hookName: string, hookParameters?: any[]): Promise { // tslint:disable-line:no-any console.log('RUN NOW: ' + hookName); - const hookData: IHookData = { + const externalHookFunctions: IExternalHookFunctions = { DbCollections: Db.collections, }; @@ -70,7 +62,7 @@ class ExternalHooksClass implements IExternalHooks { } for(const externalHookFunction of this.externalHooks[hookName]) { - externalHookFunction.call(hookData); + await externalHookFunction.apply(externalHookFunctions, hookParameters); } } diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index b54e1eb299..0b9b9eca69 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -188,9 +188,13 @@ export interface IExecutingWorkflowData { workflowExecution?: PCancelable; } +export interface IExternalHookFunctions { + DbCollections: IDatabaseCollections; +} + export interface IExternalHooks { init(): Promise; - run(hookName: string): Promise; + run(hookName: string, hookParameters?: any[]): Promise; // tslint:disable-line:no-any } export interface IN8nConfig { diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index baf4f1aa58..a592c642c2 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -346,7 +346,7 @@ class App { // Creates a new workflow this.app.post('/rest/workflows', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { - const newWorkflowData = req.body; + const newWorkflowData = req.body as IWorkflowBase; newWorkflowData.name = newWorkflowData.name.trim(); newWorkflowData.createdAt = this.getCurrentDate(); @@ -354,6 +354,8 @@ class App { newWorkflowData.id = undefined; + await this.externalHooks.run('workflow.create', [newWorkflowData]); + // Save the workflow in DB const result = await Db.collections.Workflow!.save(newWorkflowData); @@ -429,9 +431,11 @@ class App { // Updates an existing workflow this.app.patch('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { - const newWorkflowData = req.body; + const newWorkflowData = req.body as IWorkflowBase; const id = req.params.id; + await this.externalHooks.run('workflow.update', [newWorkflowData]); + if (this.activeWorkflowRunner.isActive(id)) { // When workflow gets saved always remove it as the triggers could have been // changed and so the changes would not take effect @@ -497,6 +501,8 @@ class App { this.app.delete('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const id = req.params.id; + await this.externalHooks.run('workflow.delete', [id]); + if (this.activeWorkflowRunner.isActive(id)) { // Before deleting a workflow deactivate it await this.activeWorkflowRunner.remove(id); @@ -658,6 +664,8 @@ class App { this.app.delete('/rest/credentials/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const id = req.params.id; + await this.externalHooks.run('credentials.delete', [id]); + await Db.collections.Credentials!.delete({ id }); return true; @@ -672,6 +680,8 @@ class App { nodeAccess.date = this.getCurrentDate(); } + await this.externalHooks.run('credentials.create'); + const encryptionKey = await UserSettings.getEncryptionKey(); if (encryptionKey === undefined) { throw new Error('No encryption key got found to encrypt the credentials!'); @@ -694,8 +704,6 @@ class App { throw new ResponseHelper.ResponseError(`Credentials with the same type and name exist already.`, undefined, 400); } - await this.externalHooks.run('credentials.new'); - // Encrypt the data const credentials = new Credentials(incomingData.name, incomingData.type, incomingData.nodesAccess); credentials.setData(incomingData.data, encryptionKey); @@ -722,6 +730,8 @@ class App { const id = req.params.id; + await this.externalHooks.run('credentials.update', [id]); + if (incomingData.name === '') { throw new Error('Credentials have to have a name set!'); } diff --git a/packages/cli/src/externalHooksTemp/test-hooks.ts b/packages/cli/src/externalHooksTemp/test-hooks.ts index 2cb60decfc..f10000f1f0 100644 --- a/packages/cli/src/externalHooksTemp/test-hooks.ts +++ b/packages/cli/src/externalHooksTemp/test-hooks.ts @@ -1,10 +1,46 @@ +import { + IExternalHookFunctions, + IWorkflowBase, +} from '../'; + +// TODO: Move that to interfaces +interface IExternalHooks { + credentials?: { + create?: Array<{ (this: IExternalHookFunctions): Promise; }> + delete?: Array<{ (this: IExternalHookFunctions, credentialId: string): Promise; }> + update?: Array<{ (this: IExternalHookFunctions, credentialId: string): Promise; }> + }; + workflow?: { + create?: Array<{ (this: IExternalHookFunctions, workflowData: IWorkflowBase): Promise; }> + delete?: Array<{ (this: IExternalHookFunctions, workflowId: string): Promise; }> + update?: Array<{ (this: IExternalHookFunctions, workflowData: IWorkflowBase): Promise; }> + }; +} + export = { credentials: { - new: [ - () => { + create: [ + async function (this: IExternalHookFunctions) { + // console.log(this.DbCollections.Workflow); + // Here any additional code can run or the creation blocked throw new Error('No additional credentials can be created.'); }, ], }, -}; + workflow: { + update: [ + async function (this: IExternalHookFunctions, workflowData: IWorkflowBase) { + console.log('update workflow hook'); + + // const responseData = await this.DbCollections.Workflow!.findOne(workflowData.id); + // console.log('workflowData'); + // console.log(responseData); + // console.log(workflowData); + + // Here any additional code can run or the creation blocked + throw new Error('Workflow can not be updated.'); + }, + ], + }, +} as IExternalHooks; From 6e1254fd54a43e6a2126b1192336ad0ccb97db69 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 6 May 2020 00:59:58 +0200 Subject: [PATCH 03/33] :zap: Add additional external workflow hooks --- packages/cli/src/ExternalHooks.ts | 17 +++++--- packages/cli/src/Interfaces.ts | 21 ++++++++-- packages/cli/src/Server.ts | 11 ++--- .../cli/src/WorkflowExecuteAdditionalData.ts | 5 +++ packages/cli/src/WorkflowRunner.ts | 4 ++ .../cli/src/externalHooksTemp/test-hooks.ts | 41 +++++++++---------- 6 files changed, 65 insertions(+), 34 deletions(-) diff --git a/packages/cli/src/ExternalHooks.ts b/packages/cli/src/ExternalHooks.ts index c4f12301b4..9d195ccbad 100644 --- a/packages/cli/src/ExternalHooks.ts +++ b/packages/cli/src/ExternalHooks.ts @@ -1,23 +1,28 @@ import { Db, - IExternalHookFunctions, - IExternalHooks, + IExternalHooksFunctions, + IExternalHooksClass, } from './'; import * as config from '../config'; // export EXTERNAL_HOOK_FILES=/data/packages/cli/dist/src/externalHooksTemp/test-hooks.js -class ExternalHooksClass implements IExternalHooks { +class ExternalHooksClass implements IExternalHooksClass { externalHooks: { [key: string]: Array<() => {}> } = {}; + initDidRun = false; async init(): Promise { console.log('ExternalHooks.init'); + if (this.initDidRun === true) { + return; + } + const externalHookFiles = config.get('externalHookFiles').split(':'); console.log('externalHookFiles'); @@ -48,13 +53,15 @@ class ExternalHooksClass implements IExternalHooks { } } } + + this.initDidRun = true; } async run(hookName: string, hookParameters?: any[]): Promise { // tslint:disable-line:no-any console.log('RUN NOW: ' + hookName); - const externalHookFunctions: IExternalHookFunctions = { - DbCollections: Db.collections, + const externalHookFunctions: IExternalHooksFunctions = { + dbCollections: Db.collections, }; if (this.externalHooks[hookName] === undefined) { diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 0b9b9eca69..5e76f2c4e2 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -188,11 +188,26 @@ export interface IExecutingWorkflowData { workflowExecution?: PCancelable; } -export interface IExternalHookFunctions { - DbCollections: IDatabaseCollections; +export interface IExternalHooks { + credentials?: { + create?: Array<{ (this: IExternalHooksFunctions, credentialsData: ICredentialsEncrypted): Promise; }> + delete?: Array<{ (this: IExternalHooksFunctions, credentialId: string): Promise; }> + update?: Array<{ (this: IExternalHooksFunctions, credentialsData: ICredentialsDb): Promise; }> + }; + workflow?: { + activate?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise; }> + create?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowBase): Promise; }> + delete?: Array<{ (this: IExternalHooksFunctions, workflowId: string): Promise; }> + execute?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb, mode: WorkflowExecuteMode): Promise; }> + update?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise; }> + }; } -export interface IExternalHooks { +export interface IExternalHooksFunctions { + dbCollections: IDatabaseCollections; +} + +export interface IExternalHooksClass { init(): Promise; run(hookName: string, hookParameters?: any[]): Promise; // tslint:disable-line:no-any } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index a592c642c2..a36a2fd81e 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -48,7 +48,6 @@ import { WorkflowCredentials, WebhookHelpers, WorkflowExecuteAdditionalData, - WorkflowHelpers, WorkflowRunner, GenericHelpers, } from './'; @@ -477,6 +476,8 @@ class App { if (responseData.active === true) { // When the workflow is supposed to be active add it again try { + await this.externalHooks.run('workflow.activate', [responseData]); + await this.activeWorkflowRunner.add(id); } catch (error) { // If workflow could not be activated set it again to inactive @@ -680,8 +681,6 @@ class App { nodeAccess.date = this.getCurrentDate(); } - await this.externalHooks.run('credentials.create'); - const encryptionKey = await UserSettings.getEncryptionKey(); if (encryptionKey === undefined) { throw new Error('No encryption key got found to encrypt the credentials!'); @@ -709,6 +708,8 @@ class App { credentials.setData(incomingData.data, encryptionKey); const newCredentialsData = credentials.getDataToSave() as ICredentialsDb; + await this.externalHooks.run('credentials.create', [newCredentialsData]); + // Add special database related data newCredentialsData.createdAt = this.getCurrentDate(); newCredentialsData.updatedAt = this.getCurrentDate(); @@ -730,8 +731,6 @@ class App { const id = req.params.id; - await this.externalHooks.run('credentials.update', [id]); - if (incomingData.name === '') { throw new Error('Credentials have to have a name set!'); } @@ -770,6 +769,8 @@ class App { // Add special database related data newCredentialsData.updatedAt = this.getCurrentDate(); + await this.externalHooks.run('credentials.update', [newCredentialsData]); + // Update the credentials in DB await Db.collections.Credentials!.update(id, newCredentialsData); diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 9665080ebd..4b86357852 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -1,5 +1,6 @@ import { Db, + ExternalHooks, IExecutionDb, IExecutionFlattedDb, IPushDataExecutionFinished, @@ -302,6 +303,10 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi workflowData = workflowInfo.code; } + const externalHooks = ExternalHooks(); + await externalHooks.init(); + await externalHooks.run('workflow.execute', [workflowData, mode]); + const nodeTypes = NodeTypes(); const workflowName = workflowData ? workflowData.name : undefined; diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index 3a09606550..c3ce61cedc 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -1,5 +1,6 @@ import { ActiveExecutions, + ExternalHooks, IProcessMessageDataHook, ITransferNodeTypes, IWorkflowExecutionDataProcess, @@ -94,6 +95,9 @@ export class WorkflowRunner { * @memberof WorkflowRunner */ async run(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean): Promise { + const externalHooks = ExternalHooks(); + await externalHooks.run('workflow.execute', [data.workflowData, data.executionMode]); + const executionsProcess = config.get('executions.process') as string; if (executionsProcess === 'main') { return this.runMainProcess(data, loadStaticData); diff --git a/packages/cli/src/externalHooksTemp/test-hooks.ts b/packages/cli/src/externalHooksTemp/test-hooks.ts index f10000f1f0..b49c0600b2 100644 --- a/packages/cli/src/externalHooksTemp/test-hooks.ts +++ b/packages/cli/src/externalHooksTemp/test-hooks.ts @@ -1,45 +1,44 @@ import { - IExternalHookFunctions, + WorkflowExecuteMode, +} from 'n8n-workflow'; + +import { + IExternalHooks, + IExternalHooksFunctions, IWorkflowBase, + IWorkflowDb, } from '../'; -// TODO: Move that to interfaces -interface IExternalHooks { - credentials?: { - create?: Array<{ (this: IExternalHookFunctions): Promise; }> - delete?: Array<{ (this: IExternalHookFunctions, credentialId: string): Promise; }> - update?: Array<{ (this: IExternalHookFunctions, credentialId: string): Promise; }> - }; - workflow?: { - create?: Array<{ (this: IExternalHookFunctions, workflowData: IWorkflowBase): Promise; }> - delete?: Array<{ (this: IExternalHookFunctions, workflowId: string): Promise; }> - update?: Array<{ (this: IExternalHookFunctions, workflowData: IWorkflowBase): Promise; }> - }; -} export = { credentials: { create: [ - async function (this: IExternalHookFunctions) { - // console.log(this.DbCollections.Workflow); - + async function (this: IExternalHooksFunctions) { // Here any additional code can run or the creation blocked - throw new Error('No additional credentials can be created.'); + // throw new Error('No additional credentials can be created.'); }, ], }, workflow: { + execute: [ + async function (this: IExternalHooksFunctions, workflowData: IWorkflowDb, mode: WorkflowExecuteMode) { + console.log('execute: ' + mode); + // if (mode === 'integrated') { + // throw new Error('Workflow can not be executed.'); + // } + } + ], update: [ - async function (this: IExternalHookFunctions, workflowData: IWorkflowBase) { + async function (this: IExternalHooksFunctions, workflowData: IWorkflowBase) { console.log('update workflow hook'); - // const responseData = await this.DbCollections.Workflow!.findOne(workflowData.id); + // const responseData = await this.dbCollections.Workflow!.findOne(workflowData.id); // console.log('workflowData'); // console.log(responseData); // console.log(workflowData); // Here any additional code can run or the creation blocked - throw new Error('Workflow can not be updated.'); + // throw new Error('Workflow can not be updated.'); }, ], }, From 0ab6a70fa10746ff3f84285ff490e170c08d6d9b Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Thu, 4 Jun 2020 15:54:39 +0200 Subject: [PATCH 04/33] Mailchimp OAuth2 support --- .../MailchimpOAuth2Api.credentials.ts | 68 +++++++++++++++++++ .../nodes/Mailchimp/GenericFunctions.ts | 56 +++++++++------ .../nodes/Mailchimp/Mailchimp.node.ts | 38 ++++++++++- .../nodes/Mailchimp/MailchimpTrigger.node.ts | 37 +++++++++- packages/nodes-base/package.json | 1 + 5 files changed, 178 insertions(+), 22 deletions(-) create mode 100644 packages/nodes-base/credentials/MailchimpOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/MailchimpOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MailchimpOAuth2Api.credentials.ts new file mode 100644 index 0000000000..0a0efc7091 --- /dev/null +++ b/packages/nodes-base/credentials/MailchimpOAuth2Api.credentials.ts @@ -0,0 +1,68 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class MailchimpOAuth2Api implements ICredentialType { + name = 'mailchimpOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Mailchimp OAuth2 API'; + properties = [ + { + displayName: 'Mailchimp Server', + name: 'server', + type: 'string' as NodePropertyTypes, + default: 'https://login.mailchimp.com/', + description: 'The server to connect to.', + }, + { + displayName: 'Datacenter', + name: 'dataCenter', + type: 'string' as NodePropertyTypes, + default: 'us10', + description: 'Datacenter that your Mailchimp application is hosted on. Found in the URL of your Mailchimp dashboard.', + }, + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://login.mailchimp.com/oauth2/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://login.mailchimp.com/oauth2/token', + required: true, + }, + { + displayName: 'Metadata', + name: 'metadataUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://login.mailchimp.com/oauth2/metadata', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'header', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts index 91dfcdde85..3d66c2d8ab 100644 --- a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts @@ -1,5 +1,5 @@ import { - OptionsWithUri, + OptionsWithUrl, } from 'request'; import { @@ -14,35 +14,51 @@ import { } from 'n8n-workflow'; export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, qs: IDataObject = {} ,headers?: object): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('mailchimpApi'); - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - - const headerWithAuthentication = Object.assign({}, headers, { Authorization: `apikey ${credentials.apiKey}` }); - - if (!(credentials.apiKey as string).includes('-')) { - throw new Error('The API key is not valid!'); - } - - const datacenter = (credentials.apiKey as string).split('-').pop(); + const authenticationMethod = this.getNodeParameter('authentication', 0) as string; const host = 'api.mailchimp.com/3.0'; - const options: OptionsWithUri = { - headers: headerWithAuthentication, + const options: OptionsWithUrl = { + headers: { + 'Accept': 'application/json' + }, method, qs, - uri: `https://${datacenter}.${host}${endpoint}`, + body, + url: ``, json: true, }; - if (Object.keys(body).length !== 0) { - options.body = body; + if (Object.keys(body).length === 0) { + delete options.body; } + try { - return await this.helpers.request!(options); + if (authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('mailchimpApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + options.headers = Object.assign({}, headers, { Authorization: `apikey ${credentials.apiKey}` }); + + if (!(credentials.apiKey as string).includes('-')) { + throw new Error('The API key is not valid!'); + } + + const datacenter = (credentials.apiKey as string).split('-').pop(); + options.url = `https://${datacenter}.${host}${endpoint}`; + + return await this.helpers.request!(options); + } else { + const credentials = this.getCredentials('mailchimpOAuth2Api'); + const datacenter = credentials!.dataCenter; + + options.url = `https://${datacenter}.${host}${endpoint}`; + //@ts-ignore + return await this.helpers.requestOAuth2!.call(this, 'mailchimpOAuth2Api', options, 'bearer'); + } } catch (error) { if (error.response.body && error.response.body.detail) { throw new Error(`Mailchimp Error response [${error.statusCode}]: ${error.response.body.detail}`); diff --git a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts index cb54ac24e9..3b8d17c302 100644 --- a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts @@ -69,9 +69,44 @@ export class Mailchimp implements INodeType { { name: 'mailchimpApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'mailchimpOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Access Token', + value: 'accessToken', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'accessToken', + description: 'Method of authentication.', + }, { displayName: 'Resource', name: 'resource', @@ -1536,6 +1571,7 @@ export class Mailchimp implements INodeType { responseData = { success: true }; } } + if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else { diff --git a/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts b/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts index 25eac04c20..67e07ad6bd 100644 --- a/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts @@ -33,7 +33,25 @@ export class MailchimpTrigger implements INodeType { { name: 'mailchimpApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'mailchimpOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], webhooks: [ { @@ -50,6 +68,23 @@ export class MailchimpTrigger implements INodeType { } ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Access Token', + value: 'accessToken', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'accessToken', + description: 'Method of authentication.', + }, { displayName: 'List', name: 'list', diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 31cb4a8da5..f1ce8536a6 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -78,6 +78,7 @@ "dist/credentials/KeapOAuth2Api.credentials.js", "dist/credentials/LinkFishApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", + "dist/credentials/MailchimpOAuth2Api.credentials.js", "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MailjetEmailApi.credentials.js", "dist/credentials/MailjetSmsApi.credentials.js", From d3829c90c238d7f5ae64c9dd135d235a69481b3d Mon Sep 17 00:00:00 2001 From: ricardo Date: Sat, 6 Jun 2020 14:57:42 -0400 Subject: [PATCH 05/33] :zap: Google Drive OAuth2 support --- .../GoogleDriveOAuth2Api.credentials.ts | 26 +++ .../nodes/Google/Drive/GenericFunctions.ts | 142 +++++++++++++++ .../nodes/Google/Drive/GoogleDrive.node.ts | 168 ++++++++++-------- packages/nodes-base/nodes/Google/GoogleApi.ts | 23 --- packages/nodes-base/package.json | 2 +- 5 files changed, 259 insertions(+), 102 deletions(-) create mode 100644 packages/nodes-base/credentials/GoogleDriveOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts delete mode 100644 packages/nodes-base/nodes/Google/GoogleApi.ts diff --git a/packages/nodes-base/credentials/GoogleDriveOAuth2Api.credentials.ts b/packages/nodes-base/credentials/GoogleDriveOAuth2Api.credentials.ts new file mode 100644 index 0000000000..77b0a9504c --- /dev/null +++ b/packages/nodes-base/credentials/GoogleDriveOAuth2Api.credentials.ts @@ -0,0 +1,26 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +const scopes = [ + 'https://www.googleapis.com/auth/drive', + 'https://www.googleapis.com/auth/drive.appdata', + 'https://www.googleapis.com/auth/drive.photos.readonly', +]; + +export class GoogleDriveOAuth2Api implements ICredentialType { + name = 'googleDriveOAuth2Api'; + extends = [ + 'googleOAuth2Api', + ]; + displayName = 'Google Drive OAuth2 API'; + properties = [ + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: scopes.join(' '), + }, + ]; +} diff --git a/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts new file mode 100644 index 0000000000..9946cd802a --- /dev/null +++ b/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts @@ -0,0 +1,142 @@ +import { + OptionsWithUri, + } from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +import * as moment from 'moment-timezone'; + +import * as jwt from 'jsonwebtoken'; + +export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string; + + let options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://www.googleapis.com${resource}`, + json: true, + }; + options = Object.assign({}, options, option); + try { + if (Object.keys(body).length === 0) { + delete options.body; + } + + if (authenticationMethod === 'serviceAccount') { + const credentials = this.getCredentials('googleApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const { access_token } = await getAccessToken.call(this, credentials as IDataObject); + + options.headers!.Authorization = `Bearer ${access_token}`; + //@ts-ignore + return await this.helpers.request(options); + } else { + //@ts-ignore + return await this.helpers.requestOAuth2.call(this, 'googleDriveOAuth2Api', options); + } + } catch (error) { + if (error.response && error.response.body && error.response.body.error) { + + let errorMessages; + + if (error.response.body.error.errors) { + // Try to return the error prettier + errorMessages = error.response.body.error.errors; + + errorMessages = errorMessages.map((errorItem: IDataObject) => errorItem.message); + + errorMessages = errorMessages.join('|'); + + } else if (error.response.body.error.message){ + errorMessages = error.response.body.error.message; + } + + throw new Error(`Google Drive error response [${error.statusCode}]: ${errorMessages}`); + } + throw error; + } +} + +export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query.maxResults = 100; + + do { + responseData = await googleApiRequest.call(this, method, endpoint, body, query); + query.pageToken = responseData['nextPageToken']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['nextPageToken'] !== undefined && + responseData['nextPageToken'] !== '' + ); + + return returnData; +} + +function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, credentials: IDataObject) : Promise { + //https://developers.google.com/identity/protocols/oauth2/service-account#httprest + + const scopes = [ + 'https://www.googleapis.com/auth/drive', + 'https://www.googleapis.com/auth/drive.appdata', + 'https://www.googleapis.com/auth/drive.photos.readonly', + ]; + + const now = moment().unix(); + + const signature = jwt.sign( + { + 'iss': credentials.email as string, + 'sub': credentials.email as string, + 'scope': scopes.join(' '), + 'aud': `https://oauth2.googleapis.com/token`, + 'iat': now, + 'exp': now + 3600, + }, + credentials.privateKey as string, + { + algorithm: 'RS256', + header: { + 'kid': credentials.privateKey as string, + 'typ': 'JWT', + 'alg': 'RS256', + }, + } + ); + + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + form: { + grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', + assertion: signature, + }, + uri: 'https://oauth2.googleapis.com/token', + json: true + }; + + //@ts-ignore + return this.helpers.request(options); +} diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts index 387a3d7bc0..8ddfd7c1fa 100644 --- a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts @@ -1,10 +1,8 @@ -import { google } from 'googleapis'; -const { Readable } = require('stream'); - import { BINARY_ENCODING, IExecuteFunctions, } from 'n8n-core'; + import { IDataObject, INodeTypeDescription, @@ -12,8 +10,9 @@ import { INodeType, } from 'n8n-workflow'; -import { getAuthenticationClient } from '../GoogleApi'; - +import { + googleApiRequest, +} from './GenericFunctions'; export class GoogleDrive implements INodeType { description: INodeTypeDescription = { @@ -34,9 +33,43 @@ export class GoogleDrive implements INodeType { { name: 'googleApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'serviceAccount', + ], + }, + }, + }, + { + name: 'googleDriveOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Service Account', + value: 'serviceAccount', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'serviceAccount', + }, { displayName: 'Resource', name: 'resource', @@ -813,26 +846,6 @@ export class GoogleDrive implements INodeType { const items = this.getInputData(); const returnData: IDataObject[] = []; - const credentials = this.getCredentials('googleApi'); - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - - const scopes = [ - 'https://www.googleapis.com/auth/drive', - 'https://www.googleapis.com/auth/drive.appdata', - 'https://www.googleapis.com/auth/drive.photos.readonly', - ]; - - const client = await getAuthenticationClient(credentials.email as string, credentials.privateKey as string, scopes); - - const drive = google.drive({ - version: 'v3', - // @ts-ignore - auth: client, - }); - const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; @@ -857,22 +870,20 @@ export class GoogleDrive implements INodeType { const fileId = this.getNodeParameter('fileId', i) as string; - const copyOptions = { - fileId, + const body: IDataObject = { fields: queryFields, - requestBody: {} as IDataObject, }; const optionProperties = ['name', 'parents']; for (const propertyName of optionProperties) { if (options[propertyName] !== undefined) { - copyOptions.requestBody[propertyName] = options[propertyName]; + body[propertyName] = options[propertyName]; } } - const response = await drive.files.copy(copyOptions); + const response = await googleApiRequest.call(this, 'POST', `/drive/v3/files/${fileId}/copy`, body); - returnData.push(response.data as IDataObject); + returnData.push(response as IDataObject); } else if (operation === 'download') { // ---------------------------------- @@ -881,15 +892,13 @@ export class GoogleDrive implements INodeType { const fileId = this.getNodeParameter('fileId', i) as string; - const response = await drive.files.get( - { - fileId, - alt: 'media', - }, - { - responseType: 'arraybuffer', - }, - ); + const requestOptions = { + resolveWithFullResponse: true, + encoding: null, + json: false, + }; + + const response = await googleApiRequest.call(this, 'GET', `/drive/v3/files/${fileId}`, {}, { alt: 'media' }, undefined, requestOptions); let mimeType: string | undefined; if (response.headers['content-type']) { @@ -912,7 +921,7 @@ export class GoogleDrive implements INodeType { const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string; - const data = Buffer.from(response.data as string); + const data = Buffer.from(response.body as string); items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, undefined, mimeType); @@ -988,20 +997,19 @@ export class GoogleDrive implements INodeType { const pageSize = this.getNodeParameter('limit', i) as number; - const res = await drive.files.list({ + const qs = { pageSize, orderBy: 'modifiedTime', fields: `nextPageToken, files(${queryFields})`, spaces: querySpaces, - corpora: queryCorpora, - driveId, q: queryString, - includeItemsFromAllDrives: (queryCorpora !== '' || driveId !== ''), // Actually depracated, - supportsAllDrives: (queryCorpora !== '' || driveId !== ''), // see https://developers.google.com/drive/api/v3/reference/files/list - // However until June 2020 still needs to be set, to avoid API errors. - }); + includeItemsFromAllDrives: (queryCorpora !== '' || driveId !== ''), + supportsAllDrives: (queryCorpora !== '' || driveId !== ''), + }; - const files = res!.data.files; + const response = await googleApiRequest.call(this, 'GET', `/drive/v3/files`, {}, qs); + + const files = response!.files; return [this.helpers.returnJsonArray(files as IDataObject[])]; @@ -1044,29 +1052,35 @@ export class GoogleDrive implements INodeType { const name = this.getNodeParameter('name', i) as string; const parents = this.getNodeParameter('parents', i) as string[]; - const response = await drive.files.create({ - requestBody: { - name, - originalFilename, - parents, - }, + let qs: IDataObject = { fields: queryFields, - media: { - mimeType, - body: ((buffer: Buffer) => { - const readableInstanceStream = new Readable({ - read() { - this.push(buffer); - this.push(null); - } - }); + uploadType: 'media', + }; - return readableInstanceStream; - })(body), + const requestOptions = { + headers: { + 'Content-Type': mimeType, + 'Content-Length': body.byteLength, }, - }); + encoding: null, + json: false, + }; - returnData.push(response.data as IDataObject); + let response = await googleApiRequest.call(this, 'POST', `/upload/drive/v3/files`, body, qs, undefined, requestOptions); + + body = { + mimeType, + name, + originalFilename, + }; + + qs = { + addParents: parents.join(','), + }; + + response = await googleApiRequest.call(this, 'PATCH', `/drive/v3/files/${JSON.parse(response).id}`, body, qs); + + returnData.push(response as IDataObject); } } else if (resource === 'folder') { @@ -1077,19 +1091,19 @@ export class GoogleDrive implements INodeType { const name = this.getNodeParameter('name', i) as string; - const fileMetadata = { + const body = { name, mimeType: 'application/vnd.google-apps.folder', parents: options.parents || [], }; - const response = await drive.files.create({ - // @ts-ignore - resource: fileMetadata, + const qs = { fields: queryFields, - }); + }; - returnData.push(response.data as IDataObject); + const response = await googleApiRequest.call(this, 'POST', '/drive/v3/files', body, qs); + + returnData.push(response as IDataObject); } } if (['file', 'folder'].includes(resource)) { @@ -1100,9 +1114,7 @@ export class GoogleDrive implements INodeType { const fileId = this.getNodeParameter('fileId', i) as string; - await drive.files.delete({ - fileId, - }); + const response = await googleApiRequest.call(this, 'DELETE', `/drive/v3/files/${fileId}`); // If we are still here it did succeed returnData.push({ diff --git a/packages/nodes-base/nodes/Google/GoogleApi.ts b/packages/nodes-base/nodes/Google/GoogleApi.ts deleted file mode 100644 index 64ba9f9bec..0000000000 --- a/packages/nodes-base/nodes/Google/GoogleApi.ts +++ /dev/null @@ -1,23 +0,0 @@ - -import { JWT } from 'google-auth-library'; -import { google } from 'googleapis'; - - -/** - * Returns the authentication client needed to access spreadsheet - */ -export async function getAuthenticationClient(email: string, privateKey: string, scopes: string[]): Promise { - const client = new google.auth.JWT( - email, - undefined, - privateKey, - scopes, - undefined - ); - - // TODO: Check later if this or the above should be cached - await client.authorize(); - - // @ts-ignore - return client; -} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index a898058634..0f85456439 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -58,6 +58,7 @@ "dist/credentials/GitlabApi.credentials.js", "dist/credentials/GoogleApi.credentials.js", "dist/credentials/GoogleCalendarOAuth2Api.credentials.js", + "dist/credentials/GoogleDriveOAuth2Api.credentials.js", "dist/credentials/GoogleOAuth2Api.credentials.js", "dist/credentials/GoogleSheetsOAuth2Api.credentials.js", "dist/credentials/GumroadApi.credentials.js", @@ -319,7 +320,6 @@ "formidable": "^1.2.1", "glob-promise": "^3.4.0", "gm": "^1.23.1", - "googleapis": "~50.0.0", "imap-simple": "^4.3.0", "jsonwebtoken": "^8.5.1", "lodash.get": "^4.4.2", From d2e738b5c7a9c7e8d09fda24047da236a071be38 Mon Sep 17 00:00:00 2001 From: Robarelli Date: Sun, 7 Jun 2020 23:34:10 -0600 Subject: [PATCH 06/33] Add MondayCom options for changing column values --- .../nodes/MondayCom/BoardItemDescription.ts | 157 ++++++++++++++++++ .../nodes/MondayCom/MondayCom.node.ts | 58 +++++++ 2 files changed, 215 insertions(+) diff --git a/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts b/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts index c6725279ed..c3f81484f9 100644 --- a/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts +++ b/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts @@ -15,6 +15,16 @@ export const boardItemOperations = [ }, }, options: [ + { + name: 'Change Column Value', + value: 'changeColumnValue', + description: 'Change a column value for a board item', + }, + { + name: 'Change Multiple Column Values', + value: 'changeMultipleColumnValues', + description: 'Change multiple column values for a board item', + }, { name: 'Create', value: 'create', @@ -377,4 +387,151 @@ export const boardItemFields = [ default: 50, description: 'How many results to return.', }, +/* -------------------------------------------------------------------------- */ +/* boardItem:changeColumnValue */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Board ID', + name: 'boardId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getBoards', + }, + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeColumnValue', + ], + }, + }, + description: 'The unique identifier of the board.', + }, + { + displayName: 'Item ID', + name: 'itemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeColumnValue', + ], + }, + }, + description: `Item's ID` + }, + { + displayName: 'Column ID', + name: 'columnId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getColumns', + loadOptionsDependsOn: [ + 'boardId' + ], + }, + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeColumnValue', + ], + }, + }, + description: `The column's unique identifier.`, + }, + { + displayName: 'Value', + name: 'value', + type: 'json', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeColumnValue', + ], + }, + }, + description: 'The column value in JSON format.', + }, +/* -------------------------------------------------------------------------- */ +/* boardItem:changeMultipleColumnValues */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Board ID', + name: 'boardId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getBoards', + }, + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeMultipleColumnValues', + ], + }, + }, + description: 'The unique identifier of the board.', + }, + { + displayName: 'Item ID', + name: 'itemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeMultipleColumnValues', + ], + }, + }, + description: `Item's ID` + }, + { + displayName: 'Column Values', + name: 'columnValues', + type: 'json', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeMultipleColumnValues', + ], + }, + }, + description: 'The column fields and values in JSON format.', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts b/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts index f8e538e5cb..26ccdfa06e 100644 --- a/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts +++ b/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts @@ -487,6 +487,64 @@ export class MondayCom implements INodeType { responseData = await mondayComApiRequest.call(this, body); responseData = responseData.data.create_item; } + if (operation === 'changeColumnValue') { + const boardId = parseInt(this.getNodeParameter('boardId', i) as string, 10); + const itemId = parseInt((this.getNodeParameter('itemId', i) as string), 10); + const columnId = this.getNodeParameter('columnId', i) as string; + const value = this.getNodeParameter('value', i) as string; + + const body: IGraphqlBody = { + query: + `mutation ($boardId: Int!, $itemId: Int!, $columnId: String!, $value: JSON!) { + change_column_value (board_id: $boardId, item_id: $itemId, column_id: $columnId, value: $value) { + id + } + }`, + variables: { + boardId, + itemId, + columnId, + }, + }; + + try { + JSON.parse(value); + } catch (e) { + throw new Error('Custom Values must be a valid JSON'); + } + body.variables.value = JSON.stringify(JSON.parse(value)); + + responseData = await mondayComApiRequest.call(this, body); + responseData = responseData.data.change_column_value; + } + if (operation === 'changeMultipleColumnValues') { + const boardId = parseInt(this.getNodeParameter('boardId', i) as string, 10); + const itemId = parseInt((this.getNodeParameter('itemId', i) as string), 10); + const columnValues = this.getNodeParameter('columnValues', i) as string; + + const body: IGraphqlBody = { + query: + `mutation ($boardId: Int!, $itemId: Int!, $columnValues: JSON!) { + change_multiple_column_values (board_id: $boardId, item_id: $itemId, column_values: $columnValues) { + id + } + }`, + variables: { + boardId, + itemId, + }, + }; + + try { + JSON.parse(columnValues); + } catch (e) { + throw new Error('Custom Values must be a valid JSON'); + } + body.variables.columnValues = JSON.stringify(JSON.parse(columnValues)); + + responseData = await mondayComApiRequest.call(this, body); + responseData = responseData.data.change_multiple_column_values; + } if (operation === 'delete') { const itemId = parseInt((this.getNodeParameter('itemId', i) as string), 10); From 8e86443bdb88f0a77bb59f422844c25c2f425232 Mon Sep 17 00:00:00 2001 From: Robarelli Date: Sun, 7 Jun 2020 23:57:39 -0600 Subject: [PATCH 07/33] Add MondayCom option to add updates to an item. Make options alphabetical. --- .../nodes/MondayCom/BoardItemDescription.ts | 338 ++++++++++-------- .../nodes/MondayCom/MondayCom.node.ts | 60 ++-- 2 files changed, 231 insertions(+), 167 deletions(-) diff --git a/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts b/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts index c3f81484f9..4f8cfccecc 100644 --- a/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts +++ b/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts @@ -15,6 +15,11 @@ export const boardItemOperations = [ }, }, options: [ + { + name: 'Add Update', + value: 'addUpdate', + description: `Add an update to an item.`, + }, { name: 'Change Column Value', value: 'changeColumnValue', @@ -58,6 +63,192 @@ export const boardItemOperations = [ export const boardItemFields = [ +/* -------------------------------------------------------------------------- */ +/* boardItem:addUpdate */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Item ID', + name: 'itemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'addUpdate', + ], + }, + }, + description: `Item's ID` + }, + { + displayName: 'Body', + name: 'value', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'addUpdate', + ], + }, + }, + description: 'The update text', + }, +/* -------------------------------------------------------------------------- */ +/* boardItem:changeColumnValue */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Board ID', + name: 'boardId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getBoards', + }, + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeColumnValue', + ], + }, + }, + description: 'The unique identifier of the board.', + }, + { + displayName: 'Item ID', + name: 'itemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeColumnValue', + ], + }, + }, + description: `Item's ID` + }, + { + displayName: 'Column ID', + name: 'columnId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getColumns', + loadOptionsDependsOn: [ + 'boardId' + ], + }, + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeColumnValue', + ], + }, + }, + description: `The column's unique identifier.`, + }, + { + displayName: 'Value', + name: 'value', + type: 'json', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeColumnValue', + ], + }, + }, + description: 'The column value in JSON format.', + }, +/* -------------------------------------------------------------------------- */ +/* boardItem:changeMultipleColumnValues */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Board ID', + name: 'boardId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getBoards', + }, + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeMultipleColumnValues', + ], + }, + }, + description: 'The unique identifier of the board.', + }, + { + displayName: 'Item ID', + name: 'itemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeMultipleColumnValues', + ], + }, + }, + description: `Item's ID` + }, + { + displayName: 'Column Values', + name: 'columnValues', + type: 'json', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'boardItem', + ], + operation: [ + 'changeMultipleColumnValues', + ], + }, + }, + description: 'The column fields and values in JSON format.', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, /* -------------------------------------------------------------------------- */ /* boardItem:create */ /* -------------------------------------------------------------------------- */ @@ -387,151 +578,4 @@ export const boardItemFields = [ default: 50, description: 'How many results to return.', }, -/* -------------------------------------------------------------------------- */ -/* boardItem:changeColumnValue */ -/* -------------------------------------------------------------------------- */ - { - displayName: 'Board ID', - name: 'boardId', - type: 'options', - typeOptions: { - loadOptionsMethod: 'getBoards', - }, - default: '', - required: true, - displayOptions: { - show: { - resource: [ - 'boardItem', - ], - operation: [ - 'changeColumnValue', - ], - }, - }, - description: 'The unique identifier of the board.', - }, - { - displayName: 'Item ID', - name: 'itemId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - resource: [ - 'boardItem', - ], - operation: [ - 'changeColumnValue', - ], - }, - }, - description: `Item's ID` - }, - { - displayName: 'Column ID', - name: 'columnId', - type: 'options', - typeOptions: { - loadOptionsMethod: 'getColumns', - loadOptionsDependsOn: [ - 'boardId' - ], - }, - default: '', - required: true, - displayOptions: { - show: { - resource: [ - 'boardItem', - ], - operation: [ - 'changeColumnValue', - ], - }, - }, - description: `The column's unique identifier.`, - }, - { - displayName: 'Value', - name: 'value', - type: 'json', - required: true, - default: '', - displayOptions: { - show: { - resource: [ - 'boardItem', - ], - operation: [ - 'changeColumnValue', - ], - }, - }, - description: 'The column value in JSON format.', - }, -/* -------------------------------------------------------------------------- */ -/* boardItem:changeMultipleColumnValues */ -/* -------------------------------------------------------------------------- */ - { - displayName: 'Board ID', - name: 'boardId', - type: 'options', - typeOptions: { - loadOptionsMethod: 'getBoards', - }, - default: '', - required: true, - displayOptions: { - show: { - resource: [ - 'boardItem', - ], - operation: [ - 'changeMultipleColumnValues', - ], - }, - }, - description: 'The unique identifier of the board.', - }, - { - displayName: 'Item ID', - name: 'itemId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - resource: [ - 'boardItem', - ], - operation: [ - 'changeMultipleColumnValues', - ], - }, - }, - description: `Item's ID` - }, - { - displayName: 'Column Values', - name: 'columnValues', - type: 'json', - required: true, - default: '', - displayOptions: { - show: { - resource: [ - 'boardItem', - ], - operation: [ - 'changeMultipleColumnValues', - ], - }, - }, - description: 'The column fields and values in JSON format.', - typeOptions: { - alwaysOpenEditWindow: true, - }, - }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts b/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts index 26ccdfa06e..5dc7457081 100644 --- a/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts +++ b/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts @@ -455,37 +455,25 @@ export class MondayCom implements INodeType { } } if (resource === 'boardItem') { - if (operation === 'create') { - const boardId = parseInt(this.getNodeParameter('boardId', i) as string, 10); - const groupId = this.getNodeParameter('groupId', i) as string; - const itemName = this.getNodeParameter('name', i) as string; - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + if (operation === 'addUpdate') { + const itemId = parseInt((this.getNodeParameter('itemId', i) as string), 10); + const value = this.getNodeParameter('value', i) as string; const body: IGraphqlBody = { query: - `mutation ($boardId: Int!, $groupId: String!, $itemName: String!, $columnValues: JSON) { - create_item (board_id: $boardId, group_id: $groupId, item_name: $itemName, column_values: $columnValues) { + `mutation ($itemId: Int!, $value: String!) { + create_update (item_id: $itemId, body: $value) { id } }`, variables: { - boardId, - groupId, - itemName, + itemId, + value, }, }; - if (additionalFields.columnValues) { - try { - JSON.parse(additionalFields.columnValues as string); - } catch (e) { - throw new Error('Custom Values must be a valid JSON'); - } - body.variables.columnValues = JSON.stringify(JSON.parse(additionalFields.columnValues as string)); - } - responseData = await mondayComApiRequest.call(this, body); - responseData = responseData.data.create_item; + responseData = responseData.data.create_update; } if (operation === 'changeColumnValue') { const boardId = parseInt(this.getNodeParameter('boardId', i) as string, 10); @@ -545,6 +533,38 @@ export class MondayCom implements INodeType { responseData = await mondayComApiRequest.call(this, body); responseData = responseData.data.change_multiple_column_values; } + if (operation === 'create') { + const boardId = parseInt(this.getNodeParameter('boardId', i) as string, 10); + const groupId = this.getNodeParameter('groupId', i) as string; + const itemName = this.getNodeParameter('name', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const body: IGraphqlBody = { + query: + `mutation ($boardId: Int!, $groupId: String!, $itemName: String!, $columnValues: JSON) { + create_item (board_id: $boardId, group_id: $groupId, item_name: $itemName, column_values: $columnValues) { + id + } + }`, + variables: { + boardId, + groupId, + itemName, + }, + }; + + if (additionalFields.columnValues) { + try { + JSON.parse(additionalFields.columnValues as string); + } catch (e) { + throw new Error('Custom Values must be a valid JSON'); + } + body.variables.columnValues = JSON.stringify(JSON.parse(additionalFields.columnValues as string)); + } + + responseData = await mondayComApiRequest.call(this, body); + responseData = responseData.data.create_item; + } if (operation === 'delete') { const itemId = parseInt((this.getNodeParameter('itemId', i) as string), 10); From 3f9bdd2e4150184774d1de28883bc5d6d1837077 Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Mon, 8 Jun 2020 14:40:23 +0200 Subject: [PATCH 08/33] OAuth2 support --- .../HubspotOAuth2Api.credentials.ts | 59 +++++++++++++++++++ .../nodes/Hubspot/GenericFunctions.ts | 18 ++++-- .../nodes-base/nodes/Hubspot/Hubspot.node.ts | 37 +++++++++++- .../nodes/Hubspot/HubspotTrigger.node.ts | 35 +++++++++++ packages/nodes-base/package.json | 1 + 5 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 packages/nodes-base/credentials/HubspotOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/HubspotOAuth2Api.credentials.ts b/packages/nodes-base/credentials/HubspotOAuth2Api.credentials.ts new file mode 100644 index 0000000000..775211cbc9 --- /dev/null +++ b/packages/nodes-base/credentials/HubspotOAuth2Api.credentials.ts @@ -0,0 +1,59 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class HubspotOAuth2Api implements ICredentialType { + name = 'hubspotOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Hubspot OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://app.hubspot.com/oauth/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.hubapi.com/oauth/v1/token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: 'grant_type=authorization_code', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'options' as NodePropertyTypes, + options: [ + { + name: 'Body', + value: 'body', + description: 'Send credentials in body', + }, + { + name: 'Header', + value: 'header', + description: 'Send credentials as Basic Auth header', + }, + ], + default: 'header', + description: 'Resource to consume.', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts index 969c392cdd..eb414b82aa 100644 --- a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts @@ -14,12 +14,8 @@ import { } from 'n8n-workflow'; export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any + const authenticationMethod = this.getNodeParameter('authentication', 0); - const node = this.getNode(); - const credentialName = Object.keys(node.credentials!)[0]; - const credentials = this.getCredentials(credentialName); - - query!.hapikey = credentials!.apiKey as string; const options: OptionsWithUri = { method, qs: query, @@ -28,8 +24,18 @@ export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions json: true, useQuerystring: true, }; + try { - return await this.helpers.request!(options); + if (authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('hubspotApi'); + + options.qs.hapikey = credentials!.apiKey as string; + + return await this.helpers.request!(options); + } else { + // @ts-ignore + return await this.helpers.requestOAuth2!.call(this, 'hubspotOAuth2Api', options, 'Bearer'); + } } catch (error) { if (error.response && error.response.body && error.response.body.errors) { // Try to return the error prettier diff --git a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts index 6c1d7be400..bed4606042 100644 --- a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts +++ b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts @@ -73,9 +73,44 @@ export class Hubspot implements INodeType { { name: 'hubspotApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'hubspotOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Access Token', + value: 'accessToken', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'accessToken', + description: 'The method of authentication.', + }, { displayName: 'Resource', name: 'resource', diff --git a/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts b/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts index 25028f88e8..c2ed3f3e0a 100644 --- a/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts +++ b/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts @@ -37,6 +37,24 @@ export class HubspotTrigger implements INodeType { { name: 'hubspotDeveloperApi', required: true, + displayOptions: { + show: { + authentication: [ + 'developerApi', + ], + }, + }, + }, + { + name: 'hubspotOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, }, ], webhooks: [ @@ -54,6 +72,23 @@ export class HubspotTrigger implements INodeType { }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Developer API', + value: 'developerApi', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'developerApi', + description: 'The method of authentication.', + }, { displayName: 'App ID', name: 'appId', diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 31cb4a8da5..d41b5bb4ba 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -68,6 +68,7 @@ "dist/credentials/HttpHeaderAuth.credentials.js", "dist/credentials/HubspotApi.credentials.js", "dist/credentials/HubspotDeveloperApi.credentials.js", + "dist/credentials/HubspotOAuth2Api.credentials.js", "dist/credentials/HunterApi.credentials.js", "dist/credentials/Imap.credentials.js", "dist/credentials/IntercomApi.credentials.js", From 918fba81cc300e58b6f6e43974e650e77a2f26c4 Mon Sep 17 00:00:00 2001 From: ricardo Date: Mon, 8 Jun 2020 16:13:17 -0400 Subject: [PATCH 09/33] :zap: Improve error message --- packages/nodes-base/nodes/Twitter/Twitter.node.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Twitter/Twitter.node.ts b/packages/nodes-base/nodes/Twitter/Twitter.node.ts index 60f95e3798..94718973c5 100644 --- a/packages/nodes-base/nodes/Twitter/Twitter.node.ts +++ b/packages/nodes-base/nodes/Twitter/Twitter.node.ts @@ -133,7 +133,15 @@ export class Twitter implements INodeType { let attachmentBody = {}; let response: IDataObject = {}; - if (binaryData[binaryPropertyName].mimeType.includes('image')) { + const isAnimatedWebp = (Buffer.from(binaryData[binaryPropertyName].data, 'base64').toString().indexOf('ANMF') !== -1); + + const isImage = binaryData[binaryPropertyName].mimeType.includes('image'); + + if (isImage && isAnimatedWebp) { + throw new Error('Animated .webp images are not supported use .git instead'); + } + + if (isImage) { const attachmentBody = { media_data: binaryData[binaryPropertyName].data, From d6144838281c3a9004d2eb440b74b27126628c44 Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Wed, 10 Jun 2020 12:57:13 +0200 Subject: [PATCH 10/33] trigger node fix --- .../nodes/Hubspot/HubspotTrigger.node.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts b/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts index c2ed3f3e0a..38fd678d58 100644 --- a/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts +++ b/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts @@ -281,7 +281,21 @@ export class HubspotTrigger implements INodeType { }; async webhook(this: IWebhookFunctions): Promise { - const credentials = this.getCredentials('hubspotDeveloperApi'); + + const authenticationMethod = this.getNodeParameter('authentication') as string; + + let credentials : IDataObject; + + if (authenticationMethod === 'hubspotDeveloperApi') { + credentials = this.getCredentials('hubspotDeveloperApi') as IDataObject; + } else { + credentials = this.getCredentials('hubspotOAuth2Api') as IDataObject; + } + + if (credentials === undefined) { + throw new Error('No credentials found!'); + } + const req = this.getRequestObject(); const bodyData = req.body; const headerData = this.getHeaderData(); From 57b3a2e05eb59bbfdbd53c39c64d49fb9d0ed150 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 10 Jun 2020 13:00:28 +0200 Subject: [PATCH 11/33] :zap: Cleanup of external hooks --- packages/cli/commands/execute.ts | 5 +++ packages/cli/commands/start.ts | 3 -- packages/cli/src/ExternalHooks.ts | 1 - packages/cli/src/Server.ts | 4 +- .../cli/src/externalHooksTemp/test-hooks.ts | 45 ------------------- 5 files changed, 7 insertions(+), 51 deletions(-) delete mode 100644 packages/cli/src/externalHooksTemp/test-hooks.ts diff --git a/packages/cli/commands/execute.ts b/packages/cli/commands/execute.ts index 7501b4aa9b..54016c5d55 100644 --- a/packages/cli/commands/execute.ts +++ b/packages/cli/commands/execute.ts @@ -10,6 +10,7 @@ import { import { ActiveExecutions, Db, + ExternalHooks, GenericHelpers, IWorkflowBase, IWorkflowExecutionDataProcess, @@ -103,6 +104,10 @@ export class Execute extends Command { // Wait till the n8n-packages have been read await loadNodesAndCredentialsPromise; + // Load all external hooks + const externalHooks = ExternalHooks(); + await externalHooks.init(); + // Add the found types to an instance other parts of the application can use const nodeTypes = NodeTypes(); await nodeTypes.init(loadNodesAndCredentials.nodeTypes); diff --git a/packages/cli/commands/start.ts b/packages/cli/commands/start.ts index 65edec601d..bce82bf890 100644 --- a/packages/cli/commands/start.ts +++ b/packages/cli/commands/start.ts @@ -5,7 +5,6 @@ import { } from 'n8n-core'; import { Command, flags } from '@oclif/command'; const open = require('open'); -// import { dirname } from 'path'; import * as config from '../config'; import { @@ -113,8 +112,6 @@ export class Start extends Command { const externalHooks = ExternalHooks(); await externalHooks.init(); - // await externalHooks.run('credentials.new'); - // Add the found types to an instance other parts of the application can use const nodeTypes = NodeTypes(); await nodeTypes.init(loadNodesAndCredentials.nodeTypes); diff --git a/packages/cli/src/ExternalHooks.ts b/packages/cli/src/ExternalHooks.ts index 9d195ccbad..b2b84cd7c7 100644 --- a/packages/cli/src/ExternalHooks.ts +++ b/packages/cli/src/ExternalHooks.ts @@ -6,7 +6,6 @@ import { import * as config from '../config'; -// export EXTERNAL_HOOK_FILES=/data/packages/cli/dist/src/externalHooksTemp/test-hooks.js class ExternalHooksClass implements IExternalHooksClass { diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index a36a2fd81e..d302e3970a 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -34,7 +34,7 @@ import { IExecutionsListResponse, IExecutionsStopData, IExecutionsSummary, - IExternalHooks, + IExternalHooksClass, IN8nUISettings, IPackageVersions, IWorkflowBase, @@ -94,7 +94,7 @@ class App { testWebhooks: TestWebhooks.TestWebhooks; endpointWebhook: string; endpointWebhookTest: string; - externalHooks: IExternalHooks; + externalHooks: IExternalHooksClass; saveDataErrorExecution: string; saveDataSuccessExecution: string; saveManualExecutions: boolean; diff --git a/packages/cli/src/externalHooksTemp/test-hooks.ts b/packages/cli/src/externalHooksTemp/test-hooks.ts deleted file mode 100644 index b49c0600b2..0000000000 --- a/packages/cli/src/externalHooksTemp/test-hooks.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - WorkflowExecuteMode, -} from 'n8n-workflow'; - -import { - IExternalHooks, - IExternalHooksFunctions, - IWorkflowBase, - IWorkflowDb, -} from '../'; - - -export = { - credentials: { - create: [ - async function (this: IExternalHooksFunctions) { - // Here any additional code can run or the creation blocked - // throw new Error('No additional credentials can be created.'); - }, - ], - }, - workflow: { - execute: [ - async function (this: IExternalHooksFunctions, workflowData: IWorkflowDb, mode: WorkflowExecuteMode) { - console.log('execute: ' + mode); - // if (mode === 'integrated') { - // throw new Error('Workflow can not be executed.'); - // } - } - ], - update: [ - async function (this: IExternalHooksFunctions, workflowData: IWorkflowBase) { - console.log('update workflow hook'); - - // const responseData = await this.dbCollections.Workflow!.findOne(workflowData.id); - // console.log('workflowData'); - // console.log(responseData); - // console.log(workflowData); - - // Here any additional code can run or the creation blocked - // throw new Error('Workflow can not be updated.'); - }, - ], - }, -} as IExternalHooks; From ca32d21f869379ffa784960c185f7dd3e47143c8 Mon Sep 17 00:00:00 2001 From: ricardo Date: Wed, 10 Jun 2020 17:37:01 -0400 Subject: [PATCH 12/33] :zap: Mautic extended --- .../MauticOAuth2Api.credentials.ts | 55 ++ .../nodes/Mautic/ContactDescription.ts | 562 +++++++++++++++++- .../nodes/Mautic/GenericFunctions.ts | 40 +- .../nodes-base/nodes/Mautic/Mautic.node.ts | 184 +++++- packages/nodes-base/package.json | 1 + 5 files changed, 809 insertions(+), 33 deletions(-) create mode 100644 packages/nodes-base/credentials/MauticOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/MauticOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MauticOAuth2Api.credentials.ts new file mode 100644 index 0000000000..8c52f9372c --- /dev/null +++ b/packages/nodes-base/credentials/MauticOAuth2Api.credentials.ts @@ -0,0 +1,55 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class MauticOAuth2Api implements ICredentialType { + name = 'mauticOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Mautic OAuth2 API'; + properties = [ + { + displayName: 'URL', + name: 'url', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'https://name.mautic.net', + }, + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'https://name.mautic.net/oauth/v2/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'https://name.mautic.net/oauth/v2/token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'header', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Mautic/ContactDescription.ts b/packages/nodes-base/nodes/Mautic/ContactDescription.ts index b9eb9b0f42..1ea81acbc8 100644 --- a/packages/nodes-base/nodes/Mautic/ContactDescription.ts +++ b/packages/nodes-base/nodes/Mautic/ContactDescription.ts @@ -226,6 +226,94 @@ export const contactFields = [ }, }, options: [ + { + displayName: 'Address', + name: 'addressUi', + placeholder: 'Address', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'addressValues', + displayName: 'Address', + values: [ + { + displayName: 'Address Line 1', + name: 'address1', + type: 'string', + default: '', + }, + { + displayName: 'Address Line 2', + name: 'address2', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + { + displayName: 'Zip Code', + name: 'zipCode', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'B2B or B2C', + name: 'b2bOrb2c', + type: 'options', + options: [ + { + name: 'B2B', + value: 'B2B', + }, + { + name: 'B2C', + value: 'B2C', + }, + ], + default: '', + }, + { + displayName: 'CRM ID', + name: 'crmId', + type: 'string', + default: '', + }, + { + displayName: 'Fax', + name: 'fax', + type: 'string', + default: '', + }, + { + displayName: 'Has Purchased', + name: 'hasPurchased', + type: 'boolean', + default: false, + }, { displayName: 'IP Address', name: 'ipAddress', @@ -240,6 +328,12 @@ export const contactFields = [ default: '', description: 'Date/time in UTC;', }, + { + displayName: 'Mobile', + name: 'mobile', + type: 'string', + default: '', + }, { displayName: 'Owner ID', name: 'ownerId', @@ -247,6 +341,112 @@ export const contactFields = [ default: '', description: 'ID of a Mautic user to assign this contact to', }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Prospect or Customer', + name: 'prospectOrCustomer', + type: 'options', + options: [ + { + name: 'Prospect', + value: 'Prospect', + }, + { + name: 'Customer', + value: 'Customer', + }, + ], + default: '', + }, + { + displayName: 'Sandbox', + name: 'sandbox', + type: 'boolean', + default: false, + }, + { + displayName: 'Stage', + name: 'stage', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getStages', + }, + default: '', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getTags', + }, + default: '', + }, + { + displayName: 'Social Media', + name: 'socialMediaUi', + placeholder: 'Social Media', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'socialMediaValues', + displayName: 'Social Media', + values: [ + { + displayName: 'Facebook', + name: 'facebook', + type: 'string', + default: '', + }, + { + displayName: 'Foursquare', + name: 'foursquare', + type: 'string', + default: '', + }, + { + displayName: 'Instagram', + name: 'instagram', + type: 'string', + default: '', + }, + { + displayName: 'LinkedIn', + name: 'linkedIn', + type: 'string', + default: '', + }, + { + displayName: 'Skype', + name: 'skype', + type: 'string', + default: '', + }, + { + displayName: 'Twitter', + name: 'twitter', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, ], }, @@ -318,6 +518,103 @@ export const contactFields = [ default: '', description: 'Contact parameters', }, + { + displayName: 'Address', + name: 'addressUi', + placeholder: 'Address', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: {}, + options: [ + { + name: 'addressValues', + displayName: 'Address', + values: [ + { + displayName: 'Address Line 1', + name: 'address1', + type: 'string', + default: '', + }, + { + displayName: 'Address Line 2', + name: 'address2', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + { + displayName: 'Zip Code', + name: 'zipCode', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'B2B or B2C', + name: 'b2bOrb2c', + type: 'options', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + options: [ + { + name: 'B2B', + value: 'B2B', + }, + { + name: 'B2C', + value: 'B2C', + }, + ], + default: '', + }, + { + displayName: 'CRM ID', + name: 'crmId', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + }, { displayName: 'Email', name: 'email', @@ -332,6 +629,19 @@ export const contactFields = [ default: '', description: 'Email address of the contact.', }, + { + displayName: 'Fax', + name: 'fax', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + }, { displayName: 'First Name', name: 'firstName', @@ -346,6 +656,47 @@ export const contactFields = [ default: '', description: 'First Name', }, + { + displayName: 'Has Purchased', + name: 'hasPurchased', + type: 'boolean', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: false, + }, + { + displayName: 'IP Address', + name: 'ipAddress', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + description: 'IP address to associate with the contact', + }, + { + displayName: 'Last Active', + name: 'lastActive', + type: 'dateTime', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + description: 'Date/time in UTC;', + }, { displayName: 'Last Name', name: 'lastName', @@ -360,6 +711,60 @@ export const contactFields = [ default: '', description: 'LastName', }, + { + displayName: 'Mobile', + name: 'mobile', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + }, + { + displayName: 'Owner ID', + name: 'ownerId', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + description: 'ID of a Mautic user to assign this contact to', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + }, + { + displayName: 'Position', + name: 'position', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + description: 'Position', + }, { displayName: 'Primary Company', name: 'company', @@ -378,9 +783,9 @@ export const contactFields = [ description: 'Primary company', }, { - displayName: 'Position', - name: 'position', - type: 'string', + displayName: 'Prospect or Customer', + name: 'prospectOrCustomer', + type: 'options', displayOptions: { show: { '/jsonParameters': [ @@ -388,8 +793,62 @@ export const contactFields = [ ], }, }, + options: [ + { + name: 'Prospect', + value: 'Prospect', + }, + { + name: 'Customer', + value: 'Customer', + }, + ], + default: '', + }, + { + displayName: 'Sandbox', + name: 'sandbox', + type: 'boolean', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: false, + }, + { + displayName: 'Stage', + name: 'stage', + type: 'options', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getStages', + }, + default: '', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'multiOptions', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getTags', + }, default: '', - description: 'Position', }, { displayName: 'Title', @@ -405,27 +864,94 @@ export const contactFields = [ default: '', description: 'Title', }, + { + displayName: 'Social Media', + name: 'socialMediaUi', + placeholder: 'Social Media', + type: 'fixedCollection', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'socialMediaValues', + displayName: 'Social Media', + values: [ + { + displayName: 'Facebook', + name: 'facebook', + type: 'string', + default: '', + }, + { + displayName: 'Foursquare', + name: 'foursquare', + type: 'string', + default: '', + }, + { + displayName: 'Instagram', + name: 'instagram', + type: 'string', + default: '', + }, + { + displayName: 'LinkedIn', + name: 'linkedIn', + type: 'string', + default: '', + }, + { + displayName: 'Skype', + name: 'skype', + type: 'string', + default: '', + }, + { + displayName: 'Twitter', + name: 'twitter', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + }, { displayName: 'IP Address', name: 'ipAddress', type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, default: '', description: 'IP address to associate with the contact', }, - { - displayName: 'Last Active', - name: 'lastActive', - type: 'dateTime', - default: '', - description: 'Date/time in UTC;', - }, - { - displayName: 'Owner ID', - name: 'ownerId', - type: 'string', - default: '', - description: 'ID of a Mautic user to assign this contact to', - }, ], }, diff --git a/packages/nodes-base/nodes/Mautic/GenericFunctions.ts b/packages/nodes-base/nodes/Mautic/GenericFunctions.ts index 6b179db7fd..9861690254 100644 --- a/packages/nodes-base/nodes/Mautic/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mautic/GenericFunctions.ts @@ -10,7 +10,6 @@ import { import { IDataObject, } from 'n8n-workflow'; -import { errors } from 'imap-simple'; interface OMauticErrorResponse { errors: Array<{ @@ -19,7 +18,7 @@ interface OMauticErrorResponse { }>; } -function getErrors(error: OMauticErrorResponse): string { +export function getErrors(error: OMauticErrorResponse): string { const returnErrors: string[] = []; for (const errorItem of error.errors) { @@ -31,23 +30,40 @@ function getErrors(error: OMauticErrorResponse): string { export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('mauticApi'); - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - const base64Key = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64'); + const authenticationMethod = this.getNodeParameter('authentication', 0, 'credentials') as string; + const options: OptionsWithUri = { - headers: { Authorization: `Basic ${base64Key}` }, + headers: {}, method, qs: query, - uri: uri || `${credentials.url}/api${endpoint}`, + uri: uri || `/api${endpoint}`, body, json: true }; - try { - const returnData = await this.helpers.request!(options); - if (returnData.error) { + try { + + let returnData; + + if (authenticationMethod === 'credentials') { + const credentials = this.getCredentials('mauticApi') as IDataObject; + + const base64Key = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64'); + + options.headers!.Authorization = `Basic ${base64Key}`; + + options.uri = `${credentials.url}${options.uri}`; + //@ts-ignore + returnData = await this.helpers.request(options); + } else { + const credentials = this.getCredentials('mauticOAuth2Api') as IDataObject; + + options.uri = `${credentials.url}${options.uri}`; + //@ts-ignore + returnData = await this.helpers.requestOAuth2.call(this, 'mauticOAuth2Api', options); + } + + if (returnData.errors) { // They seem to to sometimes return 200 status but still error. throw new Error(getErrors(returnData)); } diff --git a/packages/nodes-base/nodes/Mautic/Mautic.node.ts b/packages/nodes-base/nodes/Mautic/Mautic.node.ts index 50abcda83e..7bdafe7605 100644 --- a/packages/nodes-base/nodes/Mautic/Mautic.node.ts +++ b/packages/nodes-base/nodes/Mautic/Mautic.node.ts @@ -1,5 +1,3 @@ -import { snakeCase } from 'change-case'; - import { IExecuteFunctions, } from 'n8n-core'; @@ -15,12 +13,18 @@ import { mauticApiRequest, mauticApiRequestAllItems, validateJSON, + getErrors, } from './GenericFunctions'; + import { contactFields, contactOperations, } from './ContactDescription'; +import { + snakeCase, + } from 'change-case'; + export class Mautic implements INodeType { description: INodeTypeDescription = { displayName: 'Mautic', @@ -40,9 +44,43 @@ export class Mautic implements INodeType { { name: 'mauticApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'credentials', + ], + }, + }, + }, + { + name: 'mauticOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Credentials', + value: 'credentials', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'credentials', + }, { displayName: 'Resource', name: 'resource', @@ -77,6 +115,32 @@ export class Mautic implements INodeType { } return returnData; }, + // Get all the available tags to display them to user so that he can + // select them easily + async getTags(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const tags = await mauticApiRequestAllItems.call(this, 'tags', 'GET', '/tags'); + for (const tag of tags) { + returnData.push({ + name: tag.tag, + value: tag.tag, + }); + } + return returnData; + }, + // Get all the available stages to display them to user so that he can + // select them easily + async getStages(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const stages = await mauticApiRequestAllItems.call(this, 'stages', 'GET', '/stages'); + for (const stage of stages) { + returnData.push({ + name: stage.name, + value: stage.id, + }); + } + return returnData; + }, }, }; @@ -124,6 +188,62 @@ export class Mautic implements INodeType { if (additionalFields.ownerId) { body.ownerId = additionalFields.ownerId as string; } + if (additionalFields.addressUi) { + const addressValues = (additionalFields.addressUi as IDataObject).addressValues as IDataObject; + if (addressValues) { + body.address1 = addressValues.address1 as string; + body.address2 = addressValues.address2 as string; + body.city = addressValues.city as string; + body.state = addressValues.state as string; + body.country = addressValues.country as string; + body.zipcode = addressValues.zipCode as string; + } + } + if (additionalFields.socialMediaUi) { + const socialMediaValues = (additionalFields.socialMediaUi as IDataObject).socialMediaValues as IDataObject; + if (socialMediaValues) { + body.facebook = socialMediaValues.facebook as string; + body.foursquare = socialMediaValues.foursquare as string; + body.instagram = socialMediaValues.instagram as string; + body.linkedin = socialMediaValues.linkedIn as string; + body.skype = socialMediaValues.skype as string; + body.twitter = socialMediaValues.twitter as string; + } + } + if (additionalFields.b2bOrb2c) { + body.b2b_or_b2c = additionalFields.b2bOrb2c as string; + } + if (additionalFields.crmId) { + body.crm_id = additionalFields.crmId as string; + } + if (additionalFields.fax) { + body.fax = additionalFields.fax as string; + } + if (additionalFields.hasPurchased) { + body.haspurchased = additionalFields.hasPurchased as boolean; + } + if (additionalFields.mobile) { + body.mobile = additionalFields.mobile as string; + } + if (additionalFields.phone) { + body.phone = additionalFields.phone as string; + } + if (additionalFields.prospectOrCustomer) { + body.prospect_or_customer = additionalFields.prospectOrCustomer as string; + } + if (additionalFields.sandbox) { + body.sandbox = additionalFields.sandbox as boolean; + } + if (additionalFields.stage) { + body.stage = additionalFields.stage as string; + } + if (additionalFields.tags) { + body.tags = additionalFields.tags as string; + } + if (additionalFields.website) { + body.website = additionalFields.website as string; + } + responseData = await mauticApiRequest.call(this, 'POST', '/contacts/new', body); responseData = responseData.contact; } @@ -167,6 +287,61 @@ export class Mautic implements INodeType { if (updateFields.ownerId) { body.ownerId = updateFields.ownerId as string; } + if (updateFields.addressUi) { + const addressValues = (updateFields.addressUi as IDataObject).addressValues as IDataObject; + if (addressValues) { + body.address1 = addressValues.address1 as string; + body.address2 = addressValues.address2 as string; + body.city = addressValues.city as string; + body.state = addressValues.state as string; + body.country = addressValues.country as string; + body.zipcode = addressValues.zipCode as string; + } + } + if (updateFields.socialMediaUi) { + const socialMediaValues = (updateFields.socialMediaUi as IDataObject).socialMediaValues as IDataObject; + if (socialMediaValues) { + body.facebook = socialMediaValues.facebook as string; + body.foursquare = socialMediaValues.foursquare as string; + body.instagram = socialMediaValues.instagram as string; + body.linkedin = socialMediaValues.linkedIn as string; + body.skype = socialMediaValues.skype as string; + body.twitter = socialMediaValues.twitter as string; + } + } + if (updateFields.b2bOrb2c) { + body.b2b_or_b2c = updateFields.b2bOrb2c as string; + } + if (updateFields.crmId) { + body.crm_id = updateFields.crmId as string; + } + if (updateFields.fax) { + body.fax = updateFields.fax as string; + } + if (updateFields.hasPurchased) { + body.haspurchased = updateFields.hasPurchased as boolean; + } + if (updateFields.mobile) { + body.mobile = updateFields.mobile as string; + } + if (updateFields.phone) { + body.phone = updateFields.phone as string; + } + if (updateFields.prospectOrCustomer) { + body.prospect_or_customer = updateFields.prospectOrCustomer as string; + } + if (updateFields.sandbox) { + body.sandbox = updateFields.sandbox as boolean; + } + if (updateFields.stage) { + body.stage = updateFields.stage as string; + } + if (updateFields.tags) { + body.tags = updateFields.tags as string; + } + if (updateFields.website) { + body.website = updateFields.website as string; + } responseData = await mauticApiRequest.call(this, 'PATCH', `/contacts/${contactId}/edit`, body); responseData = responseData.contact; } @@ -193,6 +368,9 @@ export class Mautic implements INodeType { qs.limit = this.getNodeParameter('limit', i) as number; qs.start = 0; responseData = await mauticApiRequest.call(this, 'GET', '/contacts', {}, qs); + if (responseData.errors) { + throw new Error(getErrors(responseData)); + } responseData = responseData.contacts; responseData = Object.values(responseData); } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 2a6df1e4e3..63e51f078a 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -85,6 +85,7 @@ "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", "dist/credentials/MauticApi.credentials.js", + "dist/credentials/MauticOAuth2Api.credentials.js", "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", "dist/credentials/MicrosoftOAuth2Api.credentials.js", "dist/credentials/MicrosoftOneDriveOAuth2Api.credentials.js", From 677ffec6f9b817097ce7ed875b5dddf4da14a3ac Mon Sep 17 00:00:00 2001 From: shraddha shaligram Date: Thu, 11 Jun 2020 15:57:32 -0700 Subject: [PATCH 13/33] add messagebird API improve code readability fixed a minor error --- .../credentials/MessageBirdApi.credentials.ts | 14 + .../nodes/MessageBird/GenericFunctions.ts | 68 ++ .../nodes/MessageBird/MessageBird.node.ts | 354 +++++++++ .../nodes/MessageBird/messagebird.png | Bin 0 -> 1305 bytes packages/nodes-base/package.json | 720 +++++++++--------- 5 files changed, 797 insertions(+), 359 deletions(-) create mode 100644 packages/nodes-base/credentials/MessageBirdApi.credentials.ts create mode 100644 packages/nodes-base/nodes/MessageBird/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/MessageBird/MessageBird.node.ts create mode 100644 packages/nodes-base/nodes/MessageBird/messagebird.png diff --git a/packages/nodes-base/credentials/MessageBirdApi.credentials.ts b/packages/nodes-base/credentials/MessageBirdApi.credentials.ts new file mode 100644 index 0000000000..e67c6a0c9e --- /dev/null +++ b/packages/nodes-base/credentials/MessageBirdApi.credentials.ts @@ -0,0 +1,14 @@ +import { ICredentialType, NodePropertyTypes } from 'n8n-workflow'; + +export class MessageBirdApi implements ICredentialType { + name = 'messageBirdApi'; + displayName = 'MessageBird API'; + properties = [ + { + displayName: 'API Key', + name: 'accessKey', + type: 'string' as NodePropertyTypes, + default: '' + } + ]; +} diff --git a/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts b/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts new file mode 100644 index 0000000000..ff1df3b265 --- /dev/null +++ b/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts @@ -0,0 +1,68 @@ +import { IExecuteFunctions, IHookFunctions } from 'n8n-core'; +import { OptionsWithUri } from 'request'; + +import { IDataObject } from 'n8n-workflow'; + +/** + * Make an API request to Message Bird + * + * @param {IHookFunctions} this + * @param {string} method + * @param {string} url + * @param {object} body + * @returns {Promise} + */ +export async function messageBirdApiRequest( + this: IHookFunctions | IExecuteFunctions, + method: string, + resource: string, + body: IDataObject, + query?: IDataObject +): Promise { + const credentials = this.getCredentials('messageBirdApi'); + if (credentials === undefined) { + throw new Error('No credentials returned!'); + } + + if (query === undefined) { + query = {}; + } + let token; + token = token = `AccessKey ${credentials.accessKey}`; + + const options: OptionsWithUri = { + headers: { + Accept: 'application/json', + Authorization: token + }, + method, + qs: query, + body, + uri: `https://rest.messagebird.com${resource}`, + json: true + }; + + try { + return await this.helpers.request(options); + } catch (error) { + if (error.statusCode === 401) { + throw new Error('The Message Bird credentials are not valid!'); + } + + if (error.response && error.response.body && error.response.body.errors) { + // Try to return the error prettier + let errorMessage; + for (let i = 0; i < error.response.body.errors.length; i++) { + errorMessage = + errorMessage + + `Message Bird Error response [${error.statusCode}]: ${error.response.body.errors[i].description}`; + } + throw new Error(errorMessage); + } + + // If that data does not exist for some reason return the actual error + throw new Error( + `Message Bird error ${error.response.body.errors[0].description}` + ); + } +} diff --git a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts new file mode 100644 index 0000000000..cb0960f1e0 --- /dev/null +++ b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts @@ -0,0 +1,354 @@ +import { IExecuteFunctions } from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType +} from 'n8n-workflow'; + +import { messageBirdApiRequest } from './GenericFunctions'; + +export class MessageBird implements INodeType { + description: INodeTypeDescription = { + displayName: 'MessageBird', + name: 'messageBird', + icon: 'file:messagebird.png', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Sending SMS', + defaults: { + name: 'MessageBird', + color: '#2481d7' + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'messageBirdApi', + required: true + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'SMS', + value: 'sms' + } + ], + default: 'sms', + description: 'The resource to operate on.' + }, + + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: ['sms'] + } + }, + options: [ + { + name: 'Send', + value: 'send', + description: 'Send text messages (SMS)' + } + ], + default: 'send', + description: 'The operation to perform.' + }, + + // ---------------------------------- + // sms:send + // ---------------------------------- + { + displayName: 'From', + name: 'originator', + type: 'string', + default: '', + placeholder: '14155238886', + required: true, + displayOptions: { + show: { + operation: ['send'], + resource: ['sms'] + } + }, + description: 'The number from which to send the message' + }, + { + displayName: 'To', + name: 'recipients', + type: 'string', + default: '', + placeholder: '14155238886/+14155238886', + required: true, + displayOptions: { + show: { + operation: ['send'], + resource: ['sms'] + } + }, + description: 'all recipients separated by commas' + }, + + { + displayName: 'Message', + name: 'message', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['send'], + resource: ['sms'] + } + }, + description: 'The message to be send' + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Fields', + default: {}, + options: [ + //date-time format + { + displayName: 'Created Date-time', + name: 'createdDatetime', + type: 'dateTime', + placeholder: '2011-08-30T09:30:16.768-04:00', + default: '', + description: + 'The date and time of the creation of the message in RFC3339 format (Y-m-dTH:i:sP).' + }, + { + displayName: 'Datacoding', + name: 'datacoding', + type: 'string', + default: '', + description: + 'Using unicode will limit the maximum number of characters to 70 instead of 160' + }, + { + displayName: 'Gateway', + name: 'gateway', + type: 'number', + default: '', + description: 'The SMS route that is used to send the message.' + }, + { + displayName: 'Group Ids', + name: 'groupIds', + placeholder: '1,2', + type: 'string', + default: '', + description: + 'group ids separated by commas, If provided recipients can be omitted' + }, + { + displayName: 'Mclass', + name: 'mclass', + type: 'options', + placeholder: 'permissible values from 0-3', + options: [ + { + name: '0', + value: '0' + }, + { + name: '1', + value: '1' + }, + { + name: '2', + value: '2' + }, + { + name: '3', + value: '3' + } + ], + default: '', + description: + 'Indicated the message type. 1 is a normal message, 0 is a flash message.' + }, + { + displayName: 'Reference', + name: 'reference', + type: 'string', + default: '', + description: 'A client reference.' + }, + { + displayName: 'Report Url', + name: 'reportUrl', + type: 'string', + default: '', + description: + 'The status report URL to be used on a per-message basis.
Reference is required for a status report webhook to be sent.' + }, + //date-time format + { + displayName: 'Scheduled Date-time', + name: 'scheduledDatetime', + type: 'dateTime', + default: '', + placeholder: '2011-08-30T09:30:16.768-04:00', + description: + 'The scheduled date and time of the message in RFC3339 format (Y-m-dTH:i:sP).' + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'sms', + value: 'sms' + }, + { + name: 'binary', + value: 'binary' + }, + { + name: 'flash', + value: 'flash' + } + ], + default: '', + description: + 'The type of message.
Values can be: sms, binary, or flash.' + }, + //hash + { + displayName: 'Type Details', + name: 'typeDetails', + type: 'string', + default: '', + description: + 'A hash with extra information.
Is only used when a binary message is sent.' + }, + { + displayName: 'Validity', + name: 'validity', + type: 'number', + default: '', + typeOptions: { + minValue: 1 + }, + description: 'The amount of seconds that the message is valid.' + } + ] + } + ] + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + + let operation: string; + let resource: string; + + // For POST + let bodyRequest: IDataObject; + // For Query string + let qs: IDataObject; + + let requestMethod; + + for (let i = 0; i < items.length; i++) { + qs = {}; + + resource = this.getNodeParameter('resource', i) as string; + operation = this.getNodeParameter('operation', i) as string; + + if (resource === 'sms') { + //https://developers.messagebird.com/api/sms-messaging/#sms-api + if (operation === 'send') { + // ---------------------------------- + // sms:send + // ---------------------------------- + + requestMethod = 'POST'; + const originator = this.getNodeParameter('originator', i) as string; + const body = this.getNodeParameter('message', i) as string; + + bodyRequest = { + recipients: [], + originator, + body + }; + const additionalFields = this.getNodeParameter( + 'additionalFields', + i + ) as IDataObject; + + if (additionalFields.groupIds) { + bodyRequest.groupIds = additionalFields.groupIds as string; + } + if (additionalFields.type) { + bodyRequest.type = additionalFields.type as string; + } + if (additionalFields.reference) { + bodyRequest.reference = additionalFields.reference as string; + } + if (additionalFields.reportUrl) { + bodyRequest.reportUrl = additionalFields.reportUrl as string; + } + if (additionalFields.validity) { + bodyRequest.validity = additionalFields.reportUrl as number; + } + if (additionalFields.gateway) { + bodyRequest.gateway = additionalFields.gateway as string; + } + if (additionalFields.typeDetails) { + bodyRequest.typeDetails = additionalFields.typeDetails as string; + } + if (additionalFields.datacoding) { + bodyRequest.datacoding = additionalFields.datacoding as string; + } + if (additionalFields.mclass) { + bodyRequest.mclass = additionalFields.mclass as number; + } + if (additionalFields.scheduledDatetime) { + bodyRequest.scheduledDatetime = additionalFields.scheduledDatetime as string; + } + if (additionalFields.createdDatetime) { + bodyRequest.createdDatetime = additionalFields.createdDatetime as string; + } + + const receivers = this.getNodeParameter('recipients', i) as string; + + bodyRequest.recipients = receivers.split(',').map(item => { + return parseInt(item, 10); + }); + } else { + throw new Error(`The operation "${operation}" is not known!`); + } + } else { + throw new Error(`The resource "${resource}" is not known!`); + } + + const responseData = await messageBirdApiRequest.call( + this, + requestMethod, + '/messages', + bodyRequest, + qs + ); + + returnData.push(responseData as IDataObject); + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/MessageBird/messagebird.png b/packages/nodes-base/nodes/MessageBird/messagebird.png new file mode 100644 index 0000000000000000000000000000000000000000..006b762950d6604ab0e58a3ebf90e984f04907f6 GIT binary patch literal 1305 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$;i8Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xg+bI6B3hk`{3u+q^6EQYb$Fqb0`Z}TW&b_ z-}^zkqPOjSoVabPclqS!dv2C(uD$;xw(Q>QzqM&~pR4cBRJYJmeeF@1E!)8ShQay( z-v%)C+MTap-D$>irE9Gdwtt&E^F~OQ4%<4-R^B*+t+V@d4m{vFV|PYphuMeSz6BZV zI!Dg3d@^9G{r=#y+z-Pktq1y_Hm^UBV~`uYa2pR_*`cd~Mn;z-!?$b;KXCBv%-8YN zZM*-o^>ZglY3epIiN9If%Im6qd;Thh?b;h-R8F#9dM1*|JbxG0@^vr%7oJwj;9Zg@ zJ=2?KNy{stibCNK(Zg#!e`vA<3vRg_D_!w_@`WeuGu)4ockQ1YS7^3>3%e1zH0^7I z`J9~Q>DwI^+cU0F+$*a1@Y#~e8}|}3{)^sWvdd_Tz543=^FJT1#;wq465jjaEyLxzEI2Nx`2e|Nhi>6UA@9h-Qwmce%K z$TJ~@9u6EC>zZ_zuKfM#C;PrVJa+st)sCVo-LHOB|I&Z6c`NIl71yLJZt=9fyRt88 zQP9q1B3fMG+s?Z_tk7>sd+_*qV*K4x)yEDMq$L!7xh52G;Cu6h2jMTH7A2Y{2T9Ig z_CI`^H>*G{^uFy5vpo50b_Q8(cE4TMzWhGxiuU2hoBq{!Hm*A0wB-A<=kE?MJMY%G zeSqPj#hiT$eGa~3*dr1!@5Xb_zQ9FYceZ_ck`?>P^Zxe;2|b=!9YLNFf9>Xc*sJ8f z_9avJr(J!EeEc!EdYbA>n4Lk*jUt-nsCK2}_KiCcQ_ zwcR%5$KNb$hpVD{F0v+cN64?YdVdmE zSm~eo!`Kkg_J1y0^#<<37d!Yie@;7W%&oI3OqSP5I||Jl(eT z+b+pn{PRl=S4cd59`&qYY1LJkJfHo_n-)#9toW}~(t74_)5=9#ZKisE*}c<{(S7#< zW9F4PA`u)F+3I_OR)7CfAz_v8_&fDS?UXk*9r2S|WThW|t$Nz_NUSDWXWctqztxw| zZ{EJ}{kmI4X}^lQ1+ybxwlZPW)%M51ZrULboFyt I=akR{0A)r^>i_@% literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 822978eaf4..37b9adecf2 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,363 +1,365 @@ { - "name": "n8n-nodes-base", - "version": "0.64.1", - "description": "Base nodes of n8n", - "license": "SEE LICENSE IN LICENSE.md", - "homepage": "https://n8n.io", - "author": { - "name": "Jan Oberhauser", - "email": "jan@n8n.io" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/n8n-io/n8n.git" - }, - "main": "dist/src/index", - "types": "dist/src/index.d.ts", - "scripts": { - "dev": "npm run watch", - "build": "tsc && gulp", - "tslint": "tslint -p tsconfig.json -c tslint.json", - "watch": "tsc --watch", - "test": "jest" - }, - "files": [ - "dist" + "name": "n8n-nodes-base", + "version": "0.64.1", + "description": "Base nodes of n8n", + "license": "SEE LICENSE IN LICENSE.md", + "homepage": "https://n8n.io", + "author": { + "name": "Jan Oberhauser", + "email": "jan@n8n.io" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/n8n-io/n8n.git" + }, + "main": "dist/src/index", + "types": "dist/src/index.d.ts", + "scripts": { + "dev": "npm run watch", + "build": "tsc && gulp", + "tslint": "tslint -p tsconfig.json -c tslint.json", + "watch": "tsc --watch", + "test": "jest" + }, + "files": [ + "dist" + ], + "n8n": { + "credentials": [ + "dist/credentials/ActiveCampaignApi.credentials.js", + "dist/credentials/AgileCrmApi.credentials.js", + "dist/credentials/AcuitySchedulingApi.credentials.js", + "dist/credentials/AirtableApi.credentials.js", + "dist/credentials/Amqp.credentials.js", + "dist/credentials/AsanaApi.credentials.js", + "dist/credentials/Aws.credentials.js", + "dist/credentials/AffinityApi.credentials.js", + "dist/credentials/BannerbearApi.credentials.js", + "dist/credentials/BitbucketApi.credentials.js", + "dist/credentials/BitlyApi.credentials.js", + "dist/credentials/ChargebeeApi.credentials.js", + "dist/credentials/ClearbitApi.credentials.js", + "dist/credentials/ClickUpApi.credentials.js", + "dist/credentials/ClockifyApi.credentials.js", + "dist/credentials/CockpitApi.credentials.js", + "dist/credentials/CodaApi.credentials.js", + "dist/credentials/CopperApi.credentials.js", + "dist/credentials/CalendlyApi.credentials.js", + "dist/credentials/DisqusApi.credentials.js", + "dist/credentials/DriftApi.credentials.js", + "dist/credentials/DropboxApi.credentials.js", + "dist/credentials/EventbriteApi.credentials.js", + "dist/credentials/FacebookGraphApi.credentials.js", + "dist/credentials/FreshdeskApi.credentials.js", + "dist/credentials/FileMaker.credentials.js", + "dist/credentials/FlowApi.credentials.js", + "dist/credentials/GithubApi.credentials.js", + "dist/credentials/GithubOAuth2Api.credentials.js", + "dist/credentials/GitlabApi.credentials.js", + "dist/credentials/GoogleApi.credentials.js", + "dist/credentials/GoogleCalendarOAuth2Api.credentials.js", + "dist/credentials/GoogleOAuth2Api.credentials.js", + "dist/credentials/GoogleSheetsOAuth2Api.credentials.js", + "dist/credentials/GumroadApi.credentials.js", + "dist/credentials/HarvestApi.credentials.js", + "dist/credentials/HelpScoutOAuth2Api.credentials.js", + "dist/credentials/HttpBasicAuth.credentials.js", + "dist/credentials/HttpDigestAuth.credentials.js", + "dist/credentials/HttpHeaderAuth.credentials.js", + "dist/credentials/HubspotApi.credentials.js", + "dist/credentials/HubspotDeveloperApi.credentials.js", + "dist/credentials/HunterApi.credentials.js", + "dist/credentials/Imap.credentials.js", + "dist/credentials/IntercomApi.credentials.js", + "dist/credentials/InvoiceNinjaApi.credentials.js", + "dist/credentials/JiraSoftwareCloudApi.credentials.js", + "dist/credentials/JiraSoftwareServerApi.credentials.js", + "dist/credentials/JotFormApi.credentials.js", + "dist/credentials/KeapOAuth2Api.credentials.js", + "dist/credentials/LinkFishApi.credentials.js", + "dist/credentials/MailchimpApi.credentials.js", + "dist/credentials/MailgunApi.credentials.js", + "dist/credentials/MailjetEmailApi.credentials.js", + "dist/credentials/MailjetSmsApi.credentials.js", + "dist/credentials/MandrillApi.credentials.js", + "dist/credentials/MattermostApi.credentials.js", + "dist/credentials/MauticApi.credentials.js", + "dist/credentials/MessageBirdApi.credentials.js", + "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", + "dist/credentials/MicrosoftOAuth2Api.credentials.js", + "dist/credentials/MicrosoftOneDriveOAuth2Api.credentials.js", + "dist/credentials/MoceanApi.credentials.js", + "dist/credentials/MondayComApi.credentials.js", + "dist/credentials/MongoDb.credentials.js", + "dist/credentials/Msg91Api.credentials.js", + "dist/credentials/MySql.credentials.js", + "dist/credentials/NextCloudApi.credentials.js", + "dist/credentials/OAuth1Api.credentials.js", + "dist/credentials/OAuth2Api.credentials.js", + "dist/credentials/OpenWeatherMapApi.credentials.js", + "dist/credentials/PagerDutyApi.credentials.js", + "dist/credentials/PayPalApi.credentials.js", + "dist/credentials/PipedriveApi.credentials.js", + "dist/credentials/Postgres.credentials.js", + "dist/credentials/Redis.credentials.js", + "dist/credentials/RocketchatApi.credentials.js", + "dist/credentials/RundeckApi.credentials.js", + "dist/credentials/ShopifyApi.credentials.js", + "dist/credentials/SalesforceOAuth2Api.credentials.js", + "dist/credentials/SlackApi.credentials.js", + "dist/credentials/SlackOAuth2Api.credentials.js", + "dist/credentials/Sms77Api.credentials.js", + "dist/credentials/Smtp.credentials.js", + "dist/credentials/StripeApi.credentials.js", + "dist/credentials/SalesmateApi.credentials.js", + "dist/credentials/SegmentApi.credentials.js", + "dist/credentials/SurveyMonkeyApi.credentials.js", + "dist/credentials/TelegramApi.credentials.js", + "dist/credentials/TodoistApi.credentials.js", + "dist/credentials/TrelloApi.credentials.js", + "dist/credentials/TwilioApi.credentials.js", + "dist/credentials/TwitterOAuth1Api.credentials.js", + "dist/credentials/TypeformApi.credentials.js", + "dist/credentials/TogglApi.credentials.js", + "dist/credentials/UpleadApi.credentials.js", + "dist/credentials/VeroApi.credentials.js", + "dist/credentials/WebflowApi.credentials.js", + "dist/credentials/WooCommerceApi.credentials.js", + "dist/credentials/WordpressApi.credentials.js", + "dist/credentials/ZendeskApi.credentials.js", + "dist/credentials/ZohoOAuth2Api.credentials.js", + "dist/credentials/ZulipApi.credentials.js" ], - "n8n": { - "credentials": [ - "dist/credentials/ActiveCampaignApi.credentials.js", - "dist/credentials/AgileCrmApi.credentials.js", - "dist/credentials/AcuitySchedulingApi.credentials.js", - "dist/credentials/AirtableApi.credentials.js", - "dist/credentials/Amqp.credentials.js", - "dist/credentials/AsanaApi.credentials.js", - "dist/credentials/Aws.credentials.js", - "dist/credentials/AffinityApi.credentials.js", - "dist/credentials/BannerbearApi.credentials.js", - "dist/credentials/BitbucketApi.credentials.js", - "dist/credentials/BitlyApi.credentials.js", - "dist/credentials/ChargebeeApi.credentials.js", - "dist/credentials/ClearbitApi.credentials.js", - "dist/credentials/ClickUpApi.credentials.js", - "dist/credentials/ClockifyApi.credentials.js", - "dist/credentials/CockpitApi.credentials.js", - "dist/credentials/CodaApi.credentials.js", - "dist/credentials/CopperApi.credentials.js", - "dist/credentials/CalendlyApi.credentials.js", - "dist/credentials/DisqusApi.credentials.js", - "dist/credentials/DriftApi.credentials.js", - "dist/credentials/DropboxApi.credentials.js", - "dist/credentials/EventbriteApi.credentials.js", - "dist/credentials/FacebookGraphApi.credentials.js", - "dist/credentials/FreshdeskApi.credentials.js", - "dist/credentials/FileMaker.credentials.js", - "dist/credentials/FlowApi.credentials.js", - "dist/credentials/GithubApi.credentials.js", - "dist/credentials/GithubOAuth2Api.credentials.js", - "dist/credentials/GitlabApi.credentials.js", - "dist/credentials/GoogleApi.credentials.js", - "dist/credentials/GoogleCalendarOAuth2Api.credentials.js", - "dist/credentials/GoogleOAuth2Api.credentials.js", - "dist/credentials/GoogleSheetsOAuth2Api.credentials.js", - "dist/credentials/GumroadApi.credentials.js", - "dist/credentials/HarvestApi.credentials.js", - "dist/credentials/HelpScoutOAuth2Api.credentials.js", - "dist/credentials/HttpBasicAuth.credentials.js", - "dist/credentials/HttpDigestAuth.credentials.js", - "dist/credentials/HttpHeaderAuth.credentials.js", - "dist/credentials/HubspotApi.credentials.js", - "dist/credentials/HubspotDeveloperApi.credentials.js", - "dist/credentials/HunterApi.credentials.js", - "dist/credentials/Imap.credentials.js", - "dist/credentials/IntercomApi.credentials.js", - "dist/credentials/InvoiceNinjaApi.credentials.js", - "dist/credentials/JiraSoftwareCloudApi.credentials.js", - "dist/credentials/JiraSoftwareServerApi.credentials.js", - "dist/credentials/JotFormApi.credentials.js", - "dist/credentials/KeapOAuth2Api.credentials.js", - "dist/credentials/LinkFishApi.credentials.js", - "dist/credentials/MailchimpApi.credentials.js", - "dist/credentials/MailgunApi.credentials.js", - "dist/credentials/MailjetEmailApi.credentials.js", - "dist/credentials/MailjetSmsApi.credentials.js", - "dist/credentials/MandrillApi.credentials.js", - "dist/credentials/MattermostApi.credentials.js", - "dist/credentials/MauticApi.credentials.js", - "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", - "dist/credentials/MicrosoftOAuth2Api.credentials.js", - "dist/credentials/MicrosoftOneDriveOAuth2Api.credentials.js", - "dist/credentials/MoceanApi.credentials.js", - "dist/credentials/MondayComApi.credentials.js", - "dist/credentials/MongoDb.credentials.js", - "dist/credentials/Msg91Api.credentials.js", - "dist/credentials/MySql.credentials.js", - "dist/credentials/NextCloudApi.credentials.js", - "dist/credentials/OAuth1Api.credentials.js", - "dist/credentials/OAuth2Api.credentials.js", - "dist/credentials/OpenWeatherMapApi.credentials.js", - "dist/credentials/PagerDutyApi.credentials.js", - "dist/credentials/PayPalApi.credentials.js", - "dist/credentials/PipedriveApi.credentials.js", - "dist/credentials/Postgres.credentials.js", - "dist/credentials/Redis.credentials.js", - "dist/credentials/RocketchatApi.credentials.js", - "dist/credentials/RundeckApi.credentials.js", - "dist/credentials/ShopifyApi.credentials.js", - "dist/credentials/SalesforceOAuth2Api.credentials.js", - "dist/credentials/SlackApi.credentials.js", - "dist/credentials/SlackOAuth2Api.credentials.js", - "dist/credentials/Sms77Api.credentials.js", - "dist/credentials/Smtp.credentials.js", - "dist/credentials/StripeApi.credentials.js", - "dist/credentials/SalesmateApi.credentials.js", - "dist/credentials/SegmentApi.credentials.js", - "dist/credentials/SurveyMonkeyApi.credentials.js", - "dist/credentials/TelegramApi.credentials.js", - "dist/credentials/TodoistApi.credentials.js", - "dist/credentials/TrelloApi.credentials.js", - "dist/credentials/TwilioApi.credentials.js", - "dist/credentials/TwitterOAuth1Api.credentials.js", - "dist/credentials/TypeformApi.credentials.js", - "dist/credentials/TogglApi.credentials.js", - "dist/credentials/UpleadApi.credentials.js", - "dist/credentials/VeroApi.credentials.js", - "dist/credentials/WebflowApi.credentials.js", - "dist/credentials/WooCommerceApi.credentials.js", - "dist/credentials/WordpressApi.credentials.js", - "dist/credentials/ZendeskApi.credentials.js", - "dist/credentials/ZohoOAuth2Api.credentials.js", - "dist/credentials/ZulipApi.credentials.js" - ], - "nodes": [ - "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", - "dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js", - "dist/nodes/AgileCrm/AgileCrm.node.js", - "dist/nodes/Airtable/Airtable.node.js", - "dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js", - "dist/nodes/Amqp/Amqp.node.js", - "dist/nodes/Amqp/AmqpTrigger.node.js", - "dist/nodes/Asana/Asana.node.js", - "dist/nodes/Asana/AsanaTrigger.node.js", - "dist/nodes/Affinity/Affinity.node.js", - "dist/nodes/Affinity/AffinityTrigger.node.js", - "dist/nodes/Aws/AwsLambda.node.js", - "dist/nodes/Aws/S3/AwsS3.node.js", - "dist/nodes/Aws/AwsSes.node.js", - "dist/nodes/Aws/AwsSns.node.js", - "dist/nodes/Aws/AwsSnsTrigger.node.js", - "dist/nodes/Bannerbear/Bannerbear.node.js", - "dist/nodes/Bitbucket/BitbucketTrigger.node.js", - "dist/nodes/Bitly/Bitly.node.js", - "dist/nodes/Calendly/CalendlyTrigger.node.js", - "dist/nodes/Chargebee/Chargebee.node.js", - "dist/nodes/Chargebee/ChargebeeTrigger.node.js", - "dist/nodes/Clearbit/Clearbit.node.js", - "dist/nodes/ClickUp/ClickUp.node.js", - "dist/nodes/ClickUp/ClickUpTrigger.node.js", - "dist/nodes/Clockify/ClockifyTrigger.node.js", - "dist/nodes/Cockpit/Cockpit.node.js", - "dist/nodes/Coda/Coda.node.js", - "dist/nodes/Copper/CopperTrigger.node.js", - "dist/nodes/Cron.node.js", - "dist/nodes/Crypto.node.js", - "dist/nodes/DateTime.node.js", - "dist/nodes/Discord/Discord.node.js", - "dist/nodes/Disqus/Disqus.node.js", - "dist/nodes/Drift/Drift.node.js", - "dist/nodes/Dropbox/Dropbox.node.js", - "dist/nodes/EditImage.node.js", - "dist/nodes/EmailReadImap.node.js", - "dist/nodes/EmailSend.node.js", - "dist/nodes/ErrorTrigger.node.js", - "dist/nodes/Eventbrite/EventbriteTrigger.node.js", - "dist/nodes/ExecuteCommand.node.js", - "dist/nodes/ExecuteWorkflow.node.js", - "dist/nodes/Facebook/FacebookGraphApi.node.js", - "dist/nodes/FileMaker/FileMaker.node.js", - "dist/nodes/Freshdesk/Freshdesk.node.js", - "dist/nodes/Flow/Flow.node.js", - "dist/nodes/Flow/FlowTrigger.node.js", - "dist/nodes/Function.node.js", - "dist/nodes/FunctionItem.node.js", - "dist/nodes/Github/Github.node.js", - "dist/nodes/Github/GithubTrigger.node.js", - "dist/nodes/Gitlab/Gitlab.node.js", - "dist/nodes/Gitlab/GitlabTrigger.node.js", - "dist/nodes/Google/Calendar/GoogleCalendar.node.js", - "dist/nodes/Google/Drive/GoogleDrive.node.js", - "dist/nodes/Google/Sheet/GoogleSheets.node.js", - "dist/nodes/GraphQL/GraphQL.node.js", - "dist/nodes/Gumroad/GumroadTrigger.node.js", - "dist/nodes/Harvest/Harvest.node.js", - "dist/nodes/HelpScout/HelpScout.node.js", - "dist/nodes/HelpScout/HelpScoutTrigger.node.js", - "dist/nodes/HtmlExtract/HtmlExtract.node.js", - "dist/nodes/HttpRequest.node.js", - "dist/nodes/Hubspot/Hubspot.node.js", - "dist/nodes/Hubspot/HubspotTrigger.node.js", - "dist/nodes/Hunter/Hunter.node.js", - "dist/nodes/If.node.js", - "dist/nodes/Intercom/Intercom.node.js", - "dist/nodes/Interval.node.js", - "dist/nodes/InvoiceNinja/InvoiceNinja.node.js", - "dist/nodes/InvoiceNinja/InvoiceNinjaTrigger.node.js", - "dist/nodes/Jira/Jira.node.js", - "dist/nodes/JotForm/JotFormTrigger.node.js", - "dist/nodes/Keap/Keap.node.js", - "dist/nodes/Keap/KeapTrigger.node.js", - "dist/nodes/LinkFish/LinkFish.node.js", - "dist/nodes/Mailchimp/Mailchimp.node.js", - "dist/nodes/Mailchimp/MailchimpTrigger.node.js", - "dist/nodes/Mailgun/Mailgun.node.js", - "dist/nodes/Mailjet/Mailjet.node.js", - "dist/nodes/Mailjet/MailjetTrigger.node.js", - "dist/nodes/Mandrill/Mandrill.node.js", - "dist/nodes/Mattermost/Mattermost.node.js", - "dist/nodes/Mautic/Mautic.node.js", - "dist/nodes/Mautic/MauticTrigger.node.js", - "dist/nodes/Merge.node.js", - "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", - "dist/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.js", - "dist/nodes/MoveBinaryData.node.js", - "dist/nodes/Mocean/Mocean.node.js", - "dist/nodes/MondayCom/MondayCom.node.js", - "dist/nodes/MongoDb/MongoDb.node.js", - "dist/nodes/MoveBinaryData.node.js", - "dist/nodes/Msg91/Msg91.node.js", - "dist/nodes/MySql/MySql.node.js", - "dist/nodes/NextCloud/NextCloud.node.js", - "dist/nodes/NoOp.node.js", - "dist/nodes/OpenWeatherMap.node.js", - "dist/nodes/PagerDuty/PagerDuty.node.js", - "dist/nodes/PayPal/PayPal.node.js", - "dist/nodes/PayPal/PayPalTrigger.node.js", - "dist/nodes/Pipedrive/Pipedrive.node.js", - "dist/nodes/Pipedrive/PipedriveTrigger.node.js", - "dist/nodes/Postgres/Postgres.node.js", - "dist/nodes/ReadBinaryFile.node.js", - "dist/nodes/ReadBinaryFiles.node.js", - "dist/nodes/ReadPdf.node.js", - "dist/nodes/Redis/Redis.node.js", - "dist/nodes/RenameKeys.node.js", - "dist/nodes/Rocketchat/Rocketchat.node.js", - "dist/nodes/RssFeedRead.node.js", - "dist/nodes/Rundeck/Rundeck.node.js", - "dist/nodes/Salesforce/Salesforce.node.js", - "dist/nodes/Set.node.js", - "dist/nodes/Shopify/Shopify.node.js", - "dist/nodes/Shopify/ShopifyTrigger.node.js", - "dist/nodes/Slack/Slack.node.js", - "dist/nodes/Sms77/Sms77.node.js", - "dist/nodes/SplitInBatches.node.js", - "dist/nodes/SpreadsheetFile.node.js", - "dist/nodes/SseTrigger.node.js", - "dist/nodes/Start.node.js", - "dist/nodes/Stripe/StripeTrigger.node.js", - "dist/nodes/Switch.node.js", - "dist/nodes/Salesmate/Salesmate.node.js", - "dist/nodes/Segment/Segment.node.js", - "dist/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.js", - "dist/nodes/Telegram/Telegram.node.js", - "dist/nodes/Telegram/TelegramTrigger.node.js", - "dist/nodes/Todoist/Todoist.node.js", - "dist/nodes/Toggl/TogglTrigger.node.js", - "dist/nodes/Trello/Trello.node.js", - "dist/nodes/Trello/TrelloTrigger.node.js", - "dist/nodes/Twilio/Twilio.node.js", - "dist/nodes/Twitter/Twitter.node.js", - "dist/nodes/Typeform/TypeformTrigger.node.js", - "dist/nodes/Uplead/Uplead.node.js", - "dist/nodes/Vero/Vero.node.js", - "dist/nodes/Webflow/WebflowTrigger.node.js", - "dist/nodes/Webhook.node.js", - "dist/nodes/Wordpress/Wordpress.node.js", - "dist/nodes/WooCommerce/WooCommerce.node.js", - "dist/nodes/WooCommerce/WooCommerceTrigger.node.js", - "dist/nodes/WriteBinaryFile.node.js", - "dist/nodes/Xml.node.js", - "dist/nodes/Zendesk/Zendesk.node.js", - "dist/nodes/Zendesk/ZendeskTrigger.node.js", - "dist/nodes/Zoho/ZohoCrm.node.js", - "dist/nodes/Zulip/Zulip.node.js" - ] + "nodes": [ + "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", + "dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js", + "dist/nodes/AgileCrm/AgileCrm.node.js", + "dist/nodes/Airtable/Airtable.node.js", + "dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js", + "dist/nodes/Amqp/Amqp.node.js", + "dist/nodes/Amqp/AmqpTrigger.node.js", + "dist/nodes/Asana/Asana.node.js", + "dist/nodes/Asana/AsanaTrigger.node.js", + "dist/nodes/Affinity/Affinity.node.js", + "dist/nodes/Affinity/AffinityTrigger.node.js", + "dist/nodes/Aws/AwsLambda.node.js", + "dist/nodes/Aws/S3/AwsS3.node.js", + "dist/nodes/Aws/AwsSes.node.js", + "dist/nodes/Aws/AwsSns.node.js", + "dist/nodes/Aws/AwsSnsTrigger.node.js", + "dist/nodes/Bannerbear/Bannerbear.node.js", + "dist/nodes/Bitbucket/BitbucketTrigger.node.js", + "dist/nodes/Bitly/Bitly.node.js", + "dist/nodes/Calendly/CalendlyTrigger.node.js", + "dist/nodes/Chargebee/Chargebee.node.js", + "dist/nodes/Chargebee/ChargebeeTrigger.node.js", + "dist/nodes/Clearbit/Clearbit.node.js", + "dist/nodes/ClickUp/ClickUp.node.js", + "dist/nodes/ClickUp/ClickUpTrigger.node.js", + "dist/nodes/Clockify/ClockifyTrigger.node.js", + "dist/nodes/Cockpit/Cockpit.node.js", + "dist/nodes/Coda/Coda.node.js", + "dist/nodes/Copper/CopperTrigger.node.js", + "dist/nodes/Cron.node.js", + "dist/nodes/Crypto.node.js", + "dist/nodes/DateTime.node.js", + "dist/nodes/Discord/Discord.node.js", + "dist/nodes/Disqus/Disqus.node.js", + "dist/nodes/Drift/Drift.node.js", + "dist/nodes/Dropbox/Dropbox.node.js", + "dist/nodes/EditImage.node.js", + "dist/nodes/EmailReadImap.node.js", + "dist/nodes/EmailSend.node.js", + "dist/nodes/ErrorTrigger.node.js", + "dist/nodes/Eventbrite/EventbriteTrigger.node.js", + "dist/nodes/ExecuteCommand.node.js", + "dist/nodes/ExecuteWorkflow.node.js", + "dist/nodes/Facebook/FacebookGraphApi.node.js", + "dist/nodes/FileMaker/FileMaker.node.js", + "dist/nodes/Freshdesk/Freshdesk.node.js", + "dist/nodes/Flow/Flow.node.js", + "dist/nodes/Flow/FlowTrigger.node.js", + "dist/nodes/Function.node.js", + "dist/nodes/FunctionItem.node.js", + "dist/nodes/Github/Github.node.js", + "dist/nodes/Github/GithubTrigger.node.js", + "dist/nodes/Gitlab/Gitlab.node.js", + "dist/nodes/Gitlab/GitlabTrigger.node.js", + "dist/nodes/Google/Calendar/GoogleCalendar.node.js", + "dist/nodes/Google/Drive/GoogleDrive.node.js", + "dist/nodes/Google/Sheet/GoogleSheets.node.js", + "dist/nodes/GraphQL/GraphQL.node.js", + "dist/nodes/Gumroad/GumroadTrigger.node.js", + "dist/nodes/Harvest/Harvest.node.js", + "dist/nodes/HelpScout/HelpScout.node.js", + "dist/nodes/HelpScout/HelpScoutTrigger.node.js", + "dist/nodes/HtmlExtract/HtmlExtract.node.js", + "dist/nodes/HttpRequest.node.js", + "dist/nodes/Hubspot/Hubspot.node.js", + "dist/nodes/Hubspot/HubspotTrigger.node.js", + "dist/nodes/Hunter/Hunter.node.js", + "dist/nodes/If.node.js", + "dist/nodes/Intercom/Intercom.node.js", + "dist/nodes/Interval.node.js", + "dist/nodes/InvoiceNinja/InvoiceNinja.node.js", + "dist/nodes/InvoiceNinja/InvoiceNinjaTrigger.node.js", + "dist/nodes/Jira/Jira.node.js", + "dist/nodes/JotForm/JotFormTrigger.node.js", + "dist/nodes/Keap/Keap.node.js", + "dist/nodes/Keap/KeapTrigger.node.js", + "dist/nodes/LinkFish/LinkFish.node.js", + "dist/nodes/Mailchimp/Mailchimp.node.js", + "dist/nodes/Mailchimp/MailchimpTrigger.node.js", + "dist/nodes/Mailgun/Mailgun.node.js", + "dist/nodes/Mailjet/Mailjet.node.js", + "dist/nodes/Mailjet/MailjetTrigger.node.js", + "dist/nodes/Mandrill/Mandrill.node.js", + "dist/nodes/Mattermost/Mattermost.node.js", + "dist/nodes/Mautic/Mautic.node.js", + "dist/nodes/Mautic/MauticTrigger.node.js", + "dist/nodes/Merge.node.js", + "dist/nodes/MessageBird/MessageBird.node.js", + "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", + "dist/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.js", + "dist/nodes/MoveBinaryData.node.js", + "dist/nodes/Mocean/Mocean.node.js", + "dist/nodes/MondayCom/MondayCom.node.js", + "dist/nodes/MongoDb/MongoDb.node.js", + "dist/nodes/MoveBinaryData.node.js", + "dist/nodes/Msg91/Msg91.node.js", + "dist/nodes/MySql/MySql.node.js", + "dist/nodes/NextCloud/NextCloud.node.js", + "dist/nodes/NoOp.node.js", + "dist/nodes/OpenWeatherMap.node.js", + "dist/nodes/PagerDuty/PagerDuty.node.js", + "dist/nodes/PayPal/PayPal.node.js", + "dist/nodes/PayPal/PayPalTrigger.node.js", + "dist/nodes/Pipedrive/Pipedrive.node.js", + "dist/nodes/Pipedrive/PipedriveTrigger.node.js", + "dist/nodes/Postgres/Postgres.node.js", + "dist/nodes/ReadBinaryFile.node.js", + "dist/nodes/ReadBinaryFiles.node.js", + "dist/nodes/ReadPdf.node.js", + "dist/nodes/Redis/Redis.node.js", + "dist/nodes/RenameKeys.node.js", + "dist/nodes/Rocketchat/Rocketchat.node.js", + "dist/nodes/RssFeedRead.node.js", + "dist/nodes/Rundeck/Rundeck.node.js", + "dist/nodes/Salesforce/Salesforce.node.js", + "dist/nodes/Set.node.js", + "dist/nodes/Shopify/Shopify.node.js", + "dist/nodes/Shopify/ShopifyTrigger.node.js", + "dist/nodes/Slack/Slack.node.js", + "dist/nodes/Sms77/Sms77.node.js", + "dist/nodes/SplitInBatches.node.js", + "dist/nodes/SpreadsheetFile.node.js", + "dist/nodes/SseTrigger.node.js", + "dist/nodes/Start.node.js", + "dist/nodes/Stripe/StripeTrigger.node.js", + "dist/nodes/Switch.node.js", + "dist/nodes/Salesmate/Salesmate.node.js", + "dist/nodes/Segment/Segment.node.js", + "dist/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.js", + "dist/nodes/Telegram/Telegram.node.js", + "dist/nodes/Telegram/TelegramTrigger.node.js", + "dist/nodes/Todoist/Todoist.node.js", + "dist/nodes/Toggl/TogglTrigger.node.js", + "dist/nodes/Trello/Trello.node.js", + "dist/nodes/Trello/TrelloTrigger.node.js", + "dist/nodes/Twilio/Twilio.node.js", + "dist/nodes/Twitter/Twitter.node.js", + "dist/nodes/Typeform/TypeformTrigger.node.js", + "dist/nodes/Uplead/Uplead.node.js", + "dist/nodes/Vero/Vero.node.js", + "dist/nodes/Webflow/WebflowTrigger.node.js", + "dist/nodes/Webhook.node.js", + "dist/nodes/Wordpress/Wordpress.node.js", + "dist/nodes/WooCommerce/WooCommerce.node.js", + "dist/nodes/WooCommerce/WooCommerceTrigger.node.js", + "dist/nodes/WriteBinaryFile.node.js", + "dist/nodes/Xml.node.js", + "dist/nodes/Zendesk/Zendesk.node.js", + "dist/nodes/Zendesk/ZendeskTrigger.node.js", + "dist/nodes/Zoho/ZohoCrm.node.js", + "dist/nodes/Zulip/Zulip.node.js" + ] + }, + "devDependencies": { + "@types/aws4": "^1.5.1", + "@types/basic-auth": "^1.1.2", + "@types/cheerio": "^0.22.15", + "@types/cron": "^1.6.1", + "@types/eventsource": "^1.1.2", + "@types/express": "^4.16.1", + "@types/formidable": "^1.0.31", + "@types/gm": "^1.18.2", + "@types/imap-simple": "^4.2.0", + "@types/jest": "^24.0.18", + "@types/lodash.set": "^4.3.6", + "@types/moment-timezone": "^0.5.12", + "@types/mongodb": "^3.5.4", + "@types/node": "^10.10.1", + "@types/nodemailer": "^6.4.0", + "@types/redis": "^2.8.11", + "@types/request-promise-native": "~1.0.15", + "@types/uuid": "^3.4.6", + "@types/xml2js": "^0.4.3", + "gulp": "^4.0.0", + "jest": "^24.9.0", + "n8n-workflow": "~0.32.0", + "ts-jest": "^24.0.2", + "tslint": "^5.17.0", + "typescript": "~3.7.4" + }, + "dependencies": { + "aws4": "^1.8.0", + "basic-auth": "^2.0.1", + "change-case": "^4.1.1", + "cheerio": "^1.0.0-rc.3", + "cron": "^1.7.2", + "eventsource": "^1.0.7", + "formidable": "^1.2.1", + "glob-promise": "^3.4.0", + "gm": "^1.23.1", + "googleapis": "~50.0.0", + "imap-simple": "^4.3.0", + "iso-639-1": "^2.1.3", + "jsonwebtoken": "^8.5.1", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.unset": "^4.5.2", + "moment": "2.24.0", + "moment-timezone": "^0.5.28", + "mongodb": "^3.5.5", + "mysql2": "^2.0.1", + "n8n-core": "~0.36.0", + "nodemailer": "^6.4.6", + "pdf-parse": "^1.1.1", + "pg-promise": "^9.0.3", + "redis": "^2.8.0", + "request": "^2.88.2", + "rhea": "^1.0.11", + "rss-parser": "^3.7.0", + "uuid": "^3.4.0", + "vm2": "^3.6.10", + "xlsx": "^0.14.3", + "xml2js": "^0.4.22" + }, + "jest": { + "transform": { + "^.+\\.tsx?$": "ts-jest" }, - "devDependencies": { - "@types/aws4": "^1.5.1", - "@types/basic-auth": "^1.1.2", - "@types/cheerio": "^0.22.15", - "@types/cron": "^1.6.1", - "@types/eventsource": "^1.1.2", - "@types/express": "^4.16.1", - "@types/formidable": "^1.0.31", - "@types/gm": "^1.18.2", - "@types/imap-simple": "^4.2.0", - "@types/jest": "^24.0.18", - "@types/lodash.set": "^4.3.6", - "@types/moment-timezone": "^0.5.12", - "@types/mongodb": "^3.5.4", - "@types/node": "^10.10.1", - "@types/nodemailer": "^6.4.0", - "@types/redis": "^2.8.11", - "@types/request-promise-native": "~1.0.15", - "@types/uuid": "^3.4.6", - "@types/xml2js": "^0.4.3", - "gulp": "^4.0.0", - "jest": "^24.9.0", - "n8n-workflow": "~0.32.0", - "ts-jest": "^24.0.2", - "tslint": "^5.17.0", - "typescript": "~3.7.4" - }, - "dependencies": { - "aws4": "^1.8.0", - "basic-auth": "^2.0.1", - "change-case": "^4.1.1", - "cheerio": "^1.0.0-rc.3", - "cron": "^1.7.2", - "eventsource": "^1.0.7", - "formidable": "^1.2.1", - "glob-promise": "^3.4.0", - "gm": "^1.23.1", - "googleapis": "~50.0.0", - "imap-simple": "^4.3.0", - "iso-639-1": "^2.1.3", - "jsonwebtoken": "^8.5.1", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", - "lodash.unset": "^4.5.2", - "moment": "2.24.0", - "moment-timezone": "^0.5.28", - "mongodb": "^3.5.5", - "mysql2": "^2.0.1", - "n8n-core": "~0.36.0", - "nodemailer": "^6.4.6", - "pdf-parse": "^1.1.1", - "pg-promise": "^9.0.3", - "redis": "^2.8.0", - "request": "^2.88.2", - "rhea": "^1.0.11", - "rss-parser": "^3.7.0", - "uuid": "^3.4.0", - "vm2": "^3.6.10", - "xlsx": "^0.14.3", - "xml2js": "^0.4.22" - }, - "jest": { - "transform": { - "^.+\\.tsx?$": "ts-jest" - }, - "testURL": "http://localhost/", - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", - "testPathIgnorePatterns": [ - "/dist/", - "/node_modules/" - ], - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "json" - ] - } + "testURL": "http://localhost/", + "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", + "testPathIgnorePatterns": [ + "/dist/", + "/node_modules/" + ], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "json" + ] + } } From edd4c7a82f568decbcf2891dd62c0b17015feefe Mon Sep 17 00:00:00 2001 From: ricardo Date: Thu, 11 Jun 2020 20:40:58 -0400 Subject: [PATCH 14/33] :zap: Small improvements to MessageBird node --- .../nodes/MessageBird/GenericFunctions.ts | 38 +++-- .../nodes/MessageBird/MessageBird.node.ts | 134 ++++++++++-------- 2 files changed, 88 insertions(+), 84 deletions(-) diff --git a/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts b/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts index ff1df3b265..ee790863d7 100644 --- a/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts +++ b/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts @@ -1,7 +1,15 @@ -import { IExecuteFunctions, IHookFunctions } from 'n8n-core'; -import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IHookFunctions, + } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { + OptionsWithUri, + } from 'request'; + +import { + IDataObject, + } from 'n8n-workflow'; /** * Make an API request to Message Bird @@ -17,23 +25,17 @@ export async function messageBirdApiRequest( method: string, resource: string, body: IDataObject, - query?: IDataObject + query: IDataObject = {}, ): Promise { const credentials = this.getCredentials('messageBirdApi'); if (credentials === undefined) { throw new Error('No credentials returned!'); } - if (query === undefined) { - query = {}; - } - let token; - token = token = `AccessKey ${credentials.accessKey}`; - const options: OptionsWithUri = { headers: { Accept: 'application/json', - Authorization: token + Authorization: `AccessKey ${credentials.accessKey}`, }, method, qs: query, @@ -51,18 +53,12 @@ export async function messageBirdApiRequest( if (error.response && error.response.body && error.response.body.errors) { // Try to return the error prettier - let errorMessage; - for (let i = 0; i < error.response.body.errors.length; i++) { - errorMessage = - errorMessage + - `Message Bird Error response [${error.statusCode}]: ${error.response.body.errors[i].description}`; - } - throw new Error(errorMessage); + const errorMessage = error.response.body.errors.map((e: IDataObject) => e.description); + + throw new Error(`MessageBird Error response [${error.statusCode}]: ${errorMessage.join('|')}`); } // If that data does not exist for some reason return the actual error - throw new Error( - `Message Bird error ${error.response.body.errors[0].description}` - ); + throw error; } } diff --git a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts index cb0960f1e0..8d1190c8d6 100644 --- a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts +++ b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts @@ -1,19 +1,24 @@ -import { IExecuteFunctions } from 'n8n-core'; +import { + IExecuteFunctions, + } from 'n8n-core'; + import { IDataObject, INodeTypeDescription, INodeExecutionData, - INodeType + INodeType, } from 'n8n-workflow'; -import { messageBirdApiRequest } from './GenericFunctions'; +import { + messageBirdApiRequest, + } from './GenericFunctions'; export class MessageBird implements INodeType { description: INodeTypeDescription = { displayName: 'MessageBird', name: 'messageBird', icon: 'file:messagebird.png', - group: ['transform'], + group: ['output'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Sending SMS', @@ -50,15 +55,17 @@ export class MessageBird implements INodeType { type: 'options', displayOptions: { show: { - resource: ['sms'] - } + resource: [ + 'sms', + ], + }, }, options: [ { name: 'Send', value: 'send', description: 'Send text messages (SMS)' - } + }, ], default: 'send', description: 'The operation to perform.' @@ -76,9 +83,13 @@ export class MessageBird implements INodeType { required: true, displayOptions: { show: { - operation: ['send'], - resource: ['sms'] - } + operation: [ + 'send', + ], + resource: [ + 'sms', + ], + }, }, description: 'The number from which to send the message' }, @@ -106,9 +117,13 @@ export class MessageBird implements INodeType { required: true, displayOptions: { show: { - operation: ['send'], - resource: ['sms'] - } + operation: [ + 'send', + ], + resource: [ + 'sms', + ], + }, }, description: 'The message to be send' }, @@ -119,91 +134,87 @@ export class MessageBird implements INodeType { placeholder: 'Add Fields', default: {}, options: [ - //date-time format { displayName: 'Created Date-time', name: 'createdDatetime', type: 'dateTime', - placeholder: '2011-08-30T09:30:16.768-04:00', default: '', - description: - 'The date and time of the creation of the message in RFC3339 format (Y-m-dTH:i:sP).' + description: 'The date and time of the creation of the message in RFC3339 format (Y-m-dTH:i:sP).', }, { displayName: 'Datacoding', name: 'datacoding', - type: 'string', + type: 'options', + options: [ + { + name: 'Auto', + value: 'auto', + }, + { + name: 'Plain', + value: 'plain', + }, + { + name: 'Unicode', + value: 'unicode', + }, + ], default: '', - description: - 'Using unicode will limit the maximum number of characters to 70 instead of 160' + description: 'Using unicode will limit the maximum number of characters to 70 instead of 160', }, { displayName: 'Gateway', name: 'gateway', type: 'number', default: '', - description: 'The SMS route that is used to send the message.' + description: 'The SMS route that is used to send the message.', }, { - displayName: 'Group Ids', + displayName: 'Group IDs', name: 'groupIds', placeholder: '1,2', type: 'string', default: '', - description: - 'group ids separated by commas, If provided recipients can be omitted' + description: 'Group IDs separated by commas, If provided recipients can be omitted', }, { - displayName: 'Mclass', + displayName: 'Message Type', name: 'mclass', type: 'options', - placeholder: 'permissible values from 0-3', + placeholder: 'Permissible values from 0-3', options: [ { - name: '0', - value: '0' + name: 'Normal', + value: 0 }, { - name: '1', - value: '1' + name: 'Flash', + value: 1, }, - { - name: '2', - value: '2' - }, - { - name: '3', - value: '3' - } ], - default: '', - description: - 'Indicated the message type. 1 is a normal message, 0 is a flash message.' + default: 1, + description: 'Indicated the message type. 1 is a normal message, 0 is a flash message.', }, { displayName: 'Reference', name: 'reference', type: 'string', default: '', - description: 'A client reference.' + description: 'A client reference.', }, { displayName: 'Report Url', name: 'reportUrl', type: 'string', default: '', - description: - 'The status report URL to be used on a per-message basis.
Reference is required for a status report webhook to be sent.' + description: 'The status report URL to be used on a per-message basis.
Reference is required for a status report webhook to be sent.', }, - //date-time format { displayName: 'Scheduled Date-time', name: 'scheduledDatetime', type: 'dateTime', default: '', - placeholder: '2011-08-30T09:30:16.768-04:00', - description: - 'The scheduled date and time of the message in RFC3339 format (Y-m-dTH:i:sP).' + description: 'The scheduled date and time of the message in RFC3339 format (Y-m-dTH:i:sP).', }, { displayName: 'Type', @@ -211,44 +222,41 @@ export class MessageBird implements INodeType { type: 'options', options: [ { - name: 'sms', + name: 'SMS', value: 'sms' }, { - name: 'binary', + name: 'Binary', value: 'binary' }, { - name: 'flash', + name: 'Flash', value: 'flash' } ], default: '', - description: - 'The type of message.
Values can be: sms, binary, or flash.' + description: 'The type of message.
Values can be: sms, binary, or flash.' }, - //hash { displayName: 'Type Details', name: 'typeDetails', type: 'string', default: '', - description: - 'A hash with extra information.
Is only used when a binary message is sent.' + description: 'A hash with extra information.
Is only used when a binary message is sent.', }, { displayName: 'Validity', name: 'validity', type: 'number', - default: '', + default: 1, typeOptions: { - minValue: 1 + minValue: 1, }, - description: 'The amount of seconds that the message is valid.' - } - ] - } - ] + description: 'The amount of seconds that the message is valid.', + }, + ], + }, + ], }; async execute(this: IExecuteFunctions): Promise { From 09862d602ee74aa0c3e047a565292a05c926bd5f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 12 Jun 2020 09:39:56 +0200 Subject: [PATCH 15/33] :zap: Minor improvements to MessageBird-Node --- .../nodes/MessageBird/GenericFunctions.ts | 16 ++++---- .../nodes/MessageBird/MessageBird.node.ts | 38 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts b/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts index ee790863d7..5a7eda2210 100644 --- a/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts +++ b/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts @@ -1,15 +1,15 @@ +import { + OptionsWithUri, +} from 'request'; + import { IExecuteFunctions, IHookFunctions, - } from 'n8n-core'; - -import { - OptionsWithUri, - } from 'request'; +} from 'n8n-core'; import { IDataObject, - } from 'n8n-workflow'; +} from 'n8n-workflow'; /** * Make an API request to Message Bird @@ -26,7 +26,7 @@ export async function messageBirdApiRequest( resource: string, body: IDataObject, query: IDataObject = {}, -): Promise { +): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('messageBirdApi'); if (credentials === undefined) { throw new Error('No credentials returned!'); @@ -41,7 +41,7 @@ export async function messageBirdApiRequest( qs: query, body, uri: `https://rest.messagebird.com${resource}`, - json: true + json: true, }; try { diff --git a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts index 8d1190c8d6..9c74b6a3d9 100644 --- a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts +++ b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts @@ -64,11 +64,11 @@ export class MessageBird implements INodeType { { name: 'Send', value: 'send', - description: 'Send text messages (SMS)' + description: 'Send text messages (SMS)', }, ], default: 'send', - description: 'The operation to perform.' + description: 'The operation to perform.', }, // ---------------------------------- @@ -91,7 +91,7 @@ export class MessageBird implements INodeType { ], }, }, - description: 'The number from which to send the message' + description: 'The number from which to send the message.', }, { displayName: 'To', @@ -103,10 +103,10 @@ export class MessageBird implements INodeType { displayOptions: { show: { operation: ['send'], - resource: ['sms'] - } + resource: ['sms'], + }, }, - description: 'all recipients separated by commas' + description: 'All recipients separated by commas.', }, { @@ -125,7 +125,7 @@ export class MessageBird implements INodeType { ], }, }, - description: 'The message to be send' + description: 'The message to be send.', }, { displayName: 'Additional Fields', @@ -160,7 +160,7 @@ export class MessageBird implements INodeType { }, ], default: '', - description: 'Using unicode will limit the maximum number of characters to 70 instead of 160', + description: 'Using unicode will limit the maximum number of characters to 70 instead of 160.', }, { displayName: 'Gateway', @@ -175,7 +175,7 @@ export class MessageBird implements INodeType { placeholder: '1,2', type: 'string', default: '', - description: 'Group IDs separated by commas, If provided recipients can be omitted', + description: 'Group IDs separated by commas, If provided recipients can be omitted.', }, { displayName: 'Message Type', @@ -183,14 +183,14 @@ export class MessageBird implements INodeType { type: 'options', placeholder: 'Permissible values from 0-3', options: [ - { - name: 'Normal', - value: 0 - }, { name: 'Flash', value: 1, }, + { + name: 'Normal', + value: 0, + }, ], default: 1, description: 'Indicated the message type. 1 is a normal message, 0 is a flash message.', @@ -221,10 +221,6 @@ export class MessageBird implements INodeType { name: 'type', type: 'options', options: [ - { - name: 'SMS', - value: 'sms' - }, { name: 'Binary', value: 'binary' @@ -232,10 +228,14 @@ export class MessageBird implements INodeType { { name: 'Flash', value: 'flash' - } + }, + { + name: 'SMS', + value: 'sms' + }, ], default: '', - description: 'The type of message.
Values can be: sms, binary, or flash.' + description: 'The type of message.
Values can be: sms, binary, or flash.', }, { displayName: 'Type Details', From 6a3f075612b9beee9b8321910c62bd0f6b93f365 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 12 Jun 2020 10:30:22 +0200 Subject: [PATCH 16/33] :bug: Fix bug with parameter being a string --- packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts b/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts index 90e4e8b1fd..52fe2fcc84 100644 --- a/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts +++ b/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts @@ -670,7 +670,7 @@ export class GoogleSheets implements INodeType { sheetId: range.sheetId, dimension: deletePropertyToDimensions[propertyName] as string, startIndex: range.startIndex, - endIndex: range.startIndex + range.amount, + endIndex: parseInt(range.startIndex.toString(), 10) + parseInt(range.amount.toString(), 10), } } }); From 002437862d560c412aeaa382fd223caf6a1958d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Fri, 12 Jun 2020 10:28:40 -0300 Subject: [PATCH 17/33] :zap: Fix PNG file copy for CLI build --watch command (#661) --- packages/node-dev/src/Build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-dev/src/Build.ts b/packages/node-dev/src/Build.ts index ddb74add0a..fd695efb4b 100644 --- a/packages/node-dev/src/Build.ts +++ b/packages/node-dev/src/Build.ts @@ -105,10 +105,10 @@ export async function buildFiles (options?: IBuildOptions): Promise { } return new Promise((resolve, reject) => { + copyfiles([join(process.cwd(), './*.png'), outputDirectory], { up: true }, () => resolve(outputDirectory)); buildProcess.on('exit', code => { // Remove the tmp tsconfig file tsconfigData.cleanup(); - copyfiles([join(process.cwd(), './*.png'), outputDirectory], { up: true }, () => resolve(outputDirectory)); }); }); } From 3705e2bfbf9fd7acaf1689b75d01815d40eea9b4 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 12 Jun 2020 15:29:55 +0200 Subject: [PATCH 18/33] :bookmark: Release n8n-node-dev@0.8.0 --- packages/node-dev/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index 9d859de394..1c252141b3 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.7.0", + "version": "0.8.0", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From d1c5eb26fb550e682a289e2924291d3acb42a29b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 12 Jun 2020 15:32:39 +0200 Subject: [PATCH 19/33] :bookmark: Release n8n-node-dev@0.9.0 --- packages/node-dev/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index 1c252141b3..66394fec29 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.8.0", + "version": "0.9.0", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From ed160764c9a8e9424609b2bdb0df5c138228f17b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 12 Jun 2020 15:51:38 +0200 Subject: [PATCH 20/33] :zap: Remove debug messages --- packages/cli/src/ExternalHooks.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/cli/src/ExternalHooks.ts b/packages/cli/src/ExternalHooks.ts index b2b84cd7c7..355415158a 100644 --- a/packages/cli/src/ExternalHooks.ts +++ b/packages/cli/src/ExternalHooks.ts @@ -16,22 +16,16 @@ class ExternalHooksClass implements IExternalHooksClass { async init(): Promise { - console.log('ExternalHooks.init'); - if (this.initDidRun === true) { return; } const externalHookFiles = config.get('externalHookFiles').split(':'); - console.log('externalHookFiles'); - console.log(externalHookFiles); - // Load all the provided hook-files for (let hookFilePath of externalHookFiles) { hookFilePath = hookFilePath.trim(); if (hookFilePath !== '') { - console.log(' --- load: ' + hookFilePath); try { const hookFile = require(hookFilePath); @@ -57,8 +51,6 @@ class ExternalHooksClass implements IExternalHooksClass { } async run(hookName: string, hookParameters?: any[]): Promise { // tslint:disable-line:no-any - console.log('RUN NOW: ' + hookName); - const externalHookFunctions: IExternalHooksFunctions = { dbCollections: Db.collections, }; From 2bddf55d4ffaafeb6cb5b0ba92d014a374cf2a32 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 12 Jun 2020 21:12:21 +0200 Subject: [PATCH 21/33] :bug: Fix issue that it always used data of first item --- packages/nodes-base/nodes/OpenWeatherMap.node.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/nodes-base/nodes/OpenWeatherMap.node.ts b/packages/nodes-base/nodes/OpenWeatherMap.node.ts index e12e384094..38e883e53f 100644 --- a/packages/nodes-base/nodes/OpenWeatherMap.node.ts +++ b/packages/nodes-base/nodes/OpenWeatherMap.node.ts @@ -213,20 +213,20 @@ export class OpenWeatherMap implements INodeType { // Set base data qs = { APPID: credentials.accessToken, - units: this.getNodeParameter('format', 0) as string + units: this.getNodeParameter('format', i) as string }; // Get the location - locationSelection = this.getNodeParameter('locationSelection', 0) as string; + locationSelection = this.getNodeParameter('locationSelection', i) as string; if (locationSelection === 'cityName') { - qs.q = this.getNodeParameter('cityName', 0) as string; + qs.q = this.getNodeParameter('cityName', i) as string; } else if (locationSelection === 'cityId') { - qs.id = this.getNodeParameter('cityId', 0) as number; + qs.id = this.getNodeParameter('cityId', i) as number; } else if (locationSelection === 'coordinates') { - qs.lat = this.getNodeParameter('latitude', 0) as string; - qs.lon = this.getNodeParameter('longitude', 0) as string; + qs.lat = this.getNodeParameter('latitude', i) as string; + qs.lon = this.getNodeParameter('longitude', i) as string; } else if (locationSelection === 'zipCode') { - qs.zip = this.getNodeParameter('zipCode', 0) as string; + qs.zip = this.getNodeParameter('zipCode', i) as string; } else { throw new Error(`The locationSelection "${locationSelection}" is not known!`); } From a5055940b3c2360abf23fd008a1333282e84c865 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Jun 2020 12:34:20 +0200 Subject: [PATCH 22/33] :bug: Fix issue with multiuser-setup --- .../migrations/1588157391238-InitialMigration.ts | 8 ++++---- .../migrations/1587669153312-InitialMigration.ts | 16 +++++++++------- .../migrations/1588102412422-InitialMigration.ts | 8 ++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/databases/mysqldb/migrations/1588157391238-InitialMigration.ts b/packages/cli/src/databases/mysqldb/migrations/1588157391238-InitialMigration.ts index fc11ef32fb..1d1d4d8cc5 100644 --- a/packages/cli/src/databases/mysqldb/migrations/1588157391238-InitialMigration.ts +++ b/packages/cli/src/databases/mysqldb/migrations/1588157391238-InitialMigration.ts @@ -8,8 +8,8 @@ export class InitialMigration1588157391238 implements MigrationInterface { async up(queryRunner: QueryRunner): Promise { const tablePrefix = config.get('database.tablePrefix'); - await queryRunner.query('CREATE TABLE IF NOT EXISTS `' + tablePrefix + 'credentials_entity` (`id` int NOT NULL AUTO_INCREMENT, `name` varchar(128) NOT NULL, `data` text NOT NULL, `type` varchar(32) NOT NULL, `nodesAccess` json NOT NULL, `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, INDEX `IDX_07fde106c0b471d8cc80a64fc8` (`type`), PRIMARY KEY (`id`)) ENGINE=InnoDB', undefined); - await queryRunner.query('CREATE TABLE IF NOT EXISTS `' + tablePrefix + 'execution_entity` (`id` int NOT NULL AUTO_INCREMENT, `data` text NOT NULL, `finished` tinyint NOT NULL, `mode` varchar(255) NOT NULL, `retryOf` varchar(255) NULL, `retrySuccessId` varchar(255) NULL, `startedAt` datetime NOT NULL, `stoppedAt` datetime NOT NULL, `workflowData` json NOT NULL, `workflowId` varchar(255) NULL, INDEX `IDX_c4d999a5e90784e8caccf5589d` (`workflowId`), PRIMARY KEY (`id`)) ENGINE=InnoDB', undefined); + await queryRunner.query('CREATE TABLE IF NOT EXISTS `' + tablePrefix + 'credentials_entity` (`id` int NOT NULL AUTO_INCREMENT, `name` varchar(128) NOT NULL, `data` text NOT NULL, `type` varchar(32) NOT NULL, `nodesAccess` json NOT NULL, `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, INDEX `IDX_' + tablePrefix + '07fde106c0b471d8cc80a64fc8` (`type`), PRIMARY KEY (`id`)) ENGINE=InnoDB', undefined); + await queryRunner.query('CREATE TABLE IF NOT EXISTS `' + tablePrefix + 'execution_entity` (`id` int NOT NULL AUTO_INCREMENT, `data` text NOT NULL, `finished` tinyint NOT NULL, `mode` varchar(255) NOT NULL, `retryOf` varchar(255) NULL, `retrySuccessId` varchar(255) NULL, `startedAt` datetime NOT NULL, `stoppedAt` datetime NOT NULL, `workflowData` json NOT NULL, `workflowId` varchar(255) NULL, INDEX `IDX_' + tablePrefix + 'c4d999a5e90784e8caccf5589d` (`workflowId`), PRIMARY KEY (`id`)) ENGINE=InnoDB', undefined); await queryRunner.query('CREATE TABLE IF NOT EXISTS`' + tablePrefix + 'workflow_entity` (`id` int NOT NULL AUTO_INCREMENT, `name` varchar(128) NOT NULL, `active` tinyint NOT NULL, `nodes` json NOT NULL, `connections` json NOT NULL, `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, `settings` json NULL, `staticData` json NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB', undefined); } @@ -17,9 +17,9 @@ export class InitialMigration1588157391238 implements MigrationInterface { const tablePrefix = config.get('database.tablePrefix'); await queryRunner.query('DROP TABLE `' + tablePrefix + 'workflow_entity`', undefined); - await queryRunner.query('DROP INDEX `IDX_c4d999a5e90784e8caccf5589d` ON `' + tablePrefix + 'execution_entity`', undefined); + await queryRunner.query('DROP INDEX `IDX_' + tablePrefix + 'c4d999a5e90784e8caccf5589d` ON `' + tablePrefix + 'execution_entity`', undefined); await queryRunner.query('DROP TABLE `' + tablePrefix + 'execution_entity`', undefined); - await queryRunner.query('DROP INDEX `IDX_07fde106c0b471d8cc80a64fc8` ON `' + tablePrefix + 'credentials_entity`', undefined); + await queryRunner.query('DROP INDEX `IDX_' + tablePrefix + '07fde106c0b471d8cc80a64fc8` ON `' + tablePrefix + 'credentials_entity`', undefined); await queryRunner.query('DROP TABLE `' + tablePrefix + 'credentials_entity`', undefined); } diff --git a/packages/cli/src/databases/postgresdb/migrations/1587669153312-InitialMigration.ts b/packages/cli/src/databases/postgresdb/migrations/1587669153312-InitialMigration.ts index 555015c10d..29a80e434f 100644 --- a/packages/cli/src/databases/postgresdb/migrations/1587669153312-InitialMigration.ts +++ b/packages/cli/src/databases/postgresdb/migrations/1587669153312-InitialMigration.ts @@ -7,29 +7,31 @@ export class InitialMigration1587669153312 implements MigrationInterface { async up(queryRunner: QueryRunner): Promise { let tablePrefix = config.get('database.tablePrefix'); + const tablePrefixIndex = tablePrefix; const schema = config.get('database.postgresdb.schema'); if (schema) { tablePrefix = schema + '.' + tablePrefix; } - await queryRunner.query(`CREATE TABLE IF NOT EXISTS ${tablePrefix}credentials_entity ("id" SERIAL NOT NULL, "name" character varying(128) NOT NULL, "data" text NOT NULL, "type" character varying(32) NOT NULL, "nodesAccess" json NOT NULL, "createdAt" TIMESTAMP NOT NULL, "updatedAt" TIMESTAMP NOT NULL, CONSTRAINT PK_814c3d3c36e8a27fa8edb761b0e PRIMARY KEY ("id"))`, undefined); - await queryRunner.query(`CREATE INDEX IF NOT EXISTS IDX_07fde106c0b471d8cc80a64fc8 ON ${tablePrefix}credentials_entity (type) `, undefined); - await queryRunner.query(`CREATE TABLE IF NOT EXISTS ${tablePrefix}execution_entity ("id" SERIAL NOT NULL, "data" text NOT NULL, "finished" boolean NOT NULL, "mode" character varying NOT NULL, "retryOf" character varying, "retrySuccessId" character varying, "startedAt" TIMESTAMP NOT NULL, "stoppedAt" TIMESTAMP NOT NULL, "workflowData" json NOT NULL, "workflowId" character varying, CONSTRAINT PK_e3e63bbf986767844bbe1166d4e PRIMARY KEY ("id"))`, undefined); - await queryRunner.query(`CREATE INDEX IF NOT EXISTS IDX_c4d999a5e90784e8caccf5589d ON ${tablePrefix}execution_entity ("workflowId") `, undefined); - await queryRunner.query(`CREATE TABLE IF NOT EXISTS ${tablePrefix}workflow_entity ("id" SERIAL NOT NULL, "name" character varying(128) NOT NULL, "active" boolean NOT NULL, "nodes" json NOT NULL, "connections" json NOT NULL, "createdAt" TIMESTAMP NOT NULL, "updatedAt" TIMESTAMP NOT NULL, "settings" json, "staticData" json, CONSTRAINT PK_eded7d72664448da7745d551207 PRIMARY KEY ("id"))`, undefined); + await queryRunner.query(`CREATE TABLE IF NOT EXISTS ${tablePrefix}credentials_entity ("id" SERIAL NOT NULL, "name" character varying(128) NOT NULL, "data" text NOT NULL, "type" character varying(32) NOT NULL, "nodesAccess" json NOT NULL, "createdAt" TIMESTAMP NOT NULL, "updatedAt" TIMESTAMP NOT NULL, CONSTRAINT PK_${tablePrefixIndex}814c3d3c36e8a27fa8edb761b0e PRIMARY KEY ("id"))`, undefined); + await queryRunner.query(`CREATE INDEX IF NOT EXISTS IDX_${tablePrefixIndex}07fde106c0b471d8cc80a64fc8 ON ${tablePrefix}credentials_entity (type) `, undefined); + await queryRunner.query(`CREATE TABLE IF NOT EXISTS ${tablePrefix}execution_entity ("id" SERIAL NOT NULL, "data" text NOT NULL, "finished" boolean NOT NULL, "mode" character varying NOT NULL, "retryOf" character varying, "retrySuccessId" character varying, "startedAt" TIMESTAMP NOT NULL, "stoppedAt" TIMESTAMP NOT NULL, "workflowData" json NOT NULL, "workflowId" character varying, CONSTRAINT PK_${tablePrefixIndex}e3e63bbf986767844bbe1166d4e PRIMARY KEY ("id"))`, undefined); + await queryRunner.query(`CREATE INDEX IF NOT EXISTS IDX_${tablePrefixIndex}c4d999a5e90784e8caccf5589d ON ${tablePrefix}execution_entity ("workflowId") `, undefined); + await queryRunner.query(`CREATE TABLE IF NOT EXISTS ${tablePrefix}workflow_entity ("id" SERIAL NOT NULL, "name" character varying(128) NOT NULL, "active" boolean NOT NULL, "nodes" json NOT NULL, "connections" json NOT NULL, "createdAt" TIMESTAMP NOT NULL, "updatedAt" TIMESTAMP NOT NULL, "settings" json, "staticData" json, CONSTRAINT PK_${tablePrefixIndex}eded7d72664448da7745d551207 PRIMARY KEY ("id"))`, undefined); } async down(queryRunner: QueryRunner): Promise { let tablePrefix = config.get('database.tablePrefix'); + const tablePrefixIndex = tablePrefix; const schema = config.get('database.postgresdb.schema'); if (schema) { tablePrefix = schema + '.' + tablePrefix; } await queryRunner.query(`DROP TABLE ${tablePrefix}workflow_entity`, undefined); - await queryRunner.query(`DROP INDEX IDX_c4d999a5e90784e8caccf5589d`, undefined); + await queryRunner.query(`DROP INDEX IDX_${tablePrefixIndex}c4d999a5e90784e8caccf5589d`, undefined); await queryRunner.query(`DROP TABLE ${tablePrefix}execution_entity`, undefined); - await queryRunner.query(`DROP INDEX IDX_07fde106c0b471d8cc80a64fc8`, undefined); + await queryRunner.query(`DROP INDEX IDX_${tablePrefixIndex}07fde106c0b471d8cc80a64fc8`, undefined); await queryRunner.query(`DROP TABLE ${tablePrefix}credentials_entity`, undefined); } diff --git a/packages/cli/src/databases/sqlite/migrations/1588102412422-InitialMigration.ts b/packages/cli/src/databases/sqlite/migrations/1588102412422-InitialMigration.ts index 31b271d633..c2bb55040e 100644 --- a/packages/cli/src/databases/sqlite/migrations/1588102412422-InitialMigration.ts +++ b/packages/cli/src/databases/sqlite/migrations/1588102412422-InitialMigration.ts @@ -9,9 +9,9 @@ export class InitialMigration1588102412422 implements MigrationInterface { const tablePrefix = config.get('database.tablePrefix'); await queryRunner.query(`CREATE TABLE IF NOT EXISTS "${tablePrefix}credentials_entity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(128) NOT NULL, "data" text NOT NULL, "type" varchar(32) NOT NULL, "nodesAccess" text NOT NULL, "createdAt" datetime NOT NULL, "updatedAt" datetime NOT NULL)`, undefined); - await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_07fde106c0b471d8cc80a64fc8" ON "${tablePrefix}credentials_entity" ("type") `, undefined); + await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_${tablePrefix}07fde106c0b471d8cc80a64fc8" ON "${tablePrefix}credentials_entity" ("type") `, undefined); await queryRunner.query(`CREATE TABLE IF NOT EXISTS "${tablePrefix}execution_entity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "data" text NOT NULL, "finished" boolean NOT NULL, "mode" varchar NOT NULL, "retryOf" varchar, "retrySuccessId" varchar, "startedAt" datetime NOT NULL, "stoppedAt" datetime NOT NULL, "workflowData" text NOT NULL, "workflowId" varchar)`, undefined); - await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_c4d999a5e90784e8caccf5589d" ON "${tablePrefix}execution_entity" ("workflowId") `, undefined); + await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_${tablePrefix}c4d999a5e90784e8caccf5589d" ON "${tablePrefix}execution_entity" ("workflowId") `, undefined); await queryRunner.query(`CREATE TABLE IF NOT EXISTS "${tablePrefix}workflow_entity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(128) NOT NULL, "active" boolean NOT NULL, "nodes" text NOT NULL, "connections" text NOT NULL, "createdAt" datetime NOT NULL, "updatedAt" datetime NOT NULL, "settings" text, "staticData" text)`, undefined); } @@ -19,9 +19,9 @@ export class InitialMigration1588102412422 implements MigrationInterface { const tablePrefix = config.get('database.tablePrefix'); await queryRunner.query(`DROP TABLE "${tablePrefix}workflow_entity"`, undefined); - await queryRunner.query(`DROP INDEX "IDX_c4d999a5e90784e8caccf5589d"`, undefined); + await queryRunner.query(`DROP INDEX "IDX_${tablePrefix}c4d999a5e90784e8caccf5589d"`, undefined); await queryRunner.query(`DROP TABLE "${tablePrefix}execution_entity"`, undefined); - await queryRunner.query(`DROP INDEX "IDX_07fde106c0b471d8cc80a64fc8"`, undefined); + await queryRunner.query(`DROP INDEX "IDX_${tablePrefix}07fde106c0b471d8cc80a64fc8"`, undefined); await queryRunner.query(`DROP TABLE "${tablePrefix}credentials_entity"`, undefined); } From 094614508d691bfa8e2e7008376a00578ade3838 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Jun 2020 13:47:49 +0200 Subject: [PATCH 23/33] :zap: Some minor improvements --- .../nodes/Google/Drive/GenericFunctions.ts | 36 +++++++++---------- .../nodes/Google/Drive/GoogleDrive.node.ts | 4 +-- .../nodes-base/nodes/Twitter/Twitter.node.ts | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts index 9946cd802a..7d15e492ad 100644 --- a/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts @@ -1,6 +1,6 @@ import { OptionsWithUri, - } from 'request'; +} from 'request'; import { IExecuteFunctions, @@ -64,7 +64,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF errorMessages = errorMessages.join('|'); - } else if (error.response.body.error.message){ + } else if (error.response.body.error.message) { errorMessages = error.response.body.error.message; } @@ -74,7 +74,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF } } -export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any +export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any const returnData: IDataObject[] = []; @@ -93,7 +93,7 @@ export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOp return returnData; } -function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, credentials: IDataObject) : Promise { +function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, credentials: IDataObject): Promise { //https://developers.google.com/identity/protocols/oauth2/service-account#httprest const scopes = [ @@ -106,25 +106,25 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa const signature = jwt.sign( { - 'iss': credentials.email as string, - 'sub': credentials.email as string, - 'scope': scopes.join(' '), - 'aud': `https://oauth2.googleapis.com/token`, - 'iat': now, - 'exp': now + 3600, + 'iss': credentials.email as string, + 'sub': credentials.email as string, + 'scope': scopes.join(' '), + 'aud': `https://oauth2.googleapis.com/token`, + 'iat': now, + 'exp': now + 3600, }, credentials.privateKey as string, { - algorithm: 'RS256', - header: { - 'kid': credentials.privateKey as string, - 'typ': 'JWT', - 'alg': 'RS256', - }, + algorithm: 'RS256', + header: { + 'kid': credentials.privateKey as string, + 'typ': 'JWT', + 'alg': 'RS256', + }, } - ); + ); - const options: OptionsWithUri = { + const options: OptionsWithUri = { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts index 8ddfd7c1fa..f6291b1045 100644 --- a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts @@ -797,7 +797,7 @@ export class GoogleDrive implements INodeType { { name: 'domain', value: 'domain', - description:"All files shared to the user's domain that are searchable", + description: 'All files shared to the user\'s domain that are searchable', }, { name: 'drive', @@ -945,7 +945,7 @@ export class GoogleDrive implements INodeType { queryCorpora = options.corpora as string; } - let driveId : string | undefined; + let driveId: string | undefined; driveId = options.driveId as string; if (driveId === '') { driveId = undefined; diff --git a/packages/nodes-base/nodes/Twitter/Twitter.node.ts b/packages/nodes-base/nodes/Twitter/Twitter.node.ts index 94718973c5..a62f9fd95b 100644 --- a/packages/nodes-base/nodes/Twitter/Twitter.node.ts +++ b/packages/nodes-base/nodes/Twitter/Twitter.node.ts @@ -138,7 +138,7 @@ export class Twitter implements INodeType { const isImage = binaryData[binaryPropertyName].mimeType.includes('image'); if (isImage && isAnimatedWebp) { - throw new Error('Animated .webp images are not supported use .git instead'); + throw new Error('Animated .webp images are not supported use .gif instead'); } if (isImage) { From 8d8d9f256f006575cf90395c75ed9f2e50aa3a62 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Jun 2020 16:43:37 +0200 Subject: [PATCH 24/33] :zap: Add OAuth2 support to Mautic-Trigger Node --- .../nodes/Mautic/MauticTrigger.node.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts b/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts index a3ba20f28a..4f844e1188 100644 --- a/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts +++ b/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts @@ -38,7 +38,25 @@ export class MauticTrigger implements INodeType { { name: 'mauticApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'credentials', + ], + }, + }, + }, + { + name: 'mauticOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], webhooks: [ { @@ -49,6 +67,22 @@ export class MauticTrigger implements INodeType { }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Credentials', + value: 'credentials', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'credentials', + }, { displayName: 'Events', name: 'events', From 81a29ff16d40368f577940aa1edd2e9e2f530aa0 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Jun 2020 16:44:49 +0200 Subject: [PATCH 25/33] :zap: Fix issue with some parameters returning strings --- .../nodes/Google/Sheet/GoogleSheets.node.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts b/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts index 52fe2fcc84..042eb39105 100644 --- a/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts +++ b/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts @@ -622,7 +622,7 @@ export class GoogleSheets implements INodeType { // ---------------------------------- // append // ---------------------------------- - const keyRow = this.getNodeParameter('keyRow', 0) as number; + const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10); const items = this.getInputData(); @@ -693,8 +693,8 @@ export class GoogleSheets implements INodeType { return []; } - const dataStartRow = this.getNodeParameter('dataStartRow', 0) as number; - const keyRow = this.getNodeParameter('keyRow', 0) as number; + const dataStartRow = parseInt(this.getNodeParameter('dataStartRow', 0) as string, 10); + const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10); const items = this.getInputData(); @@ -735,8 +735,8 @@ export class GoogleSheets implements INodeType { } ]; } else { - const dataStartRow = this.getNodeParameter('dataStartRow', 0) as number; - const keyRow = this.getNodeParameter('keyRow', 0) as number; + const dataStartRow = parseInt(this.getNodeParameter('dataStartRow', 0) as string, 10); + const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10); returnData = sheet.structureArrayDataByColumn(sheetData, keyRow, dataStartRow); } @@ -769,8 +769,8 @@ export class GoogleSheets implements INodeType { const data = await sheet.batchUpdate(updateData, valueInputMode); } else { const keyName = this.getNodeParameter('key', 0) as string; - const keyRow = this.getNodeParameter('keyRow', 0) as number; - const dataStartRow = this.getNodeParameter('dataStartRow', 0) as number; + const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10); + const dataStartRow = parseInt(this.getNodeParameter('dataStartRow', 0) as string, 10); const setData: IDataObject[] = []; items.forEach((item) => { From 0af237e5b18e2b1ca81ecef3f3e64d47ba6bb220 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Jun 2020 17:46:13 +0200 Subject: [PATCH 26/33] :zap: Some minor improvements to Monday.com-Node --- .../nodes/MondayCom/BoardItemDescription.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts b/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts index 4f8cfccecc..9537c32521 100644 --- a/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts +++ b/packages/nodes-base/nodes/MondayCom/BoardItemDescription.ts @@ -82,10 +82,10 @@ export const boardItemFields = [ ], }, }, - description: `Item's ID` + description: 'The unique identifier of the item to add update to.', }, { - displayName: 'Body', + displayName: 'Update Text', name: 'value', type: 'string', required: true, @@ -100,7 +100,7 @@ export const boardItemFields = [ ], }, }, - description: 'The update text', + description: 'The update text to add.', }, /* -------------------------------------------------------------------------- */ /* boardItem:changeColumnValue */ @@ -142,7 +142,7 @@ export const boardItemFields = [ ], }, }, - description: `Item's ID` + description: 'The unique identifier of the item to to change column of.', }, { displayName: 'Column ID', @@ -184,7 +184,7 @@ export const boardItemFields = [ ], }, }, - description: 'The column value in JSON format.', + description: 'The column value in JSON format. Documentation can be found here.', }, /* -------------------------------------------------------------------------- */ /* boardItem:changeMultipleColumnValues */ @@ -244,7 +244,7 @@ export const boardItemFields = [ ], }, }, - description: 'The column fields and values in JSON format.', + description: 'The column fields and values in JSON format. Documentation can be found here.', typeOptions: { alwaysOpenEditWindow: true, }, From 2af04e775dfdb62086946007bc025e564f0eeef2 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Jun 2020 19:22:49 +0200 Subject: [PATCH 27/33] :zap: Add delete option to MongoDb-Node --- .../nodes-base/nodes/MongoDb/MongoDb.node.ts | 13 +++++++- .../nodes/MongoDb/mongo.node.options.ts | 30 ++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts index dbd7ab2929..d91a287b88 100644 --- a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts +++ b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts @@ -32,7 +32,18 @@ export class MongoDb implements INodeType { const items = this.getInputData(); const operation = this.getNodeParameter('operation', 0) as string; - if (operation === 'find') { + if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + const { deletedCount } = await mdb + .collection(this.getNodeParameter('collection', 0) as string) + .deleteMany(JSON.parse(this.getNodeParameter('query', 0) as string)); + + returnItems = this.helpers.returnJsonArray([{ deletedCount }]); + + } else if (operation === 'find') { // ---------------------------------- // find // ---------------------------------- diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts index 190ff3de88..953a45a620 100644 --- a/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts @@ -28,6 +28,11 @@ export const nodeDescription: INodeTypeDescription = { name: 'operation', type: 'options', options: [ + { + name: 'Delete', + value: 'delete', + description: 'Delete documents.' + }, { name: 'Find', value: 'find', @@ -57,13 +62,36 @@ export const nodeDescription: INodeTypeDescription = { description: 'MongoDB Collection' }, + // ---------------------------------- + // delete + // ---------------------------------- + { + displayName: 'Delete Query (JSON format)', + name: 'query', + type: 'json', + typeOptions: { + rows: 5 + }, + displayOptions: { + show: { + operation: [ + 'delete' + ], + }, + }, + default: '{}', + placeholder: `{ "birth": { "$gt": "1950-01-01" } }`, + required: true, + description: 'MongoDB Delete query.' + }, + // ---------------------------------- // find // ---------------------------------- { displayName: 'Query (JSON format)', name: 'query', - type: 'string', + type: 'json', typeOptions: { rows: 5 }, From 1cfdccc311066aa2efecb650ae5ec4687c7ed62b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Jun 2020 20:31:57 +0200 Subject: [PATCH 28/33] :bookmark: Release n8n-nodes-base@0.65.0 --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 182922492c..8a3c76c261 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.64.1", + "version": "0.65.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 716716bd1c11111c4952f61c57c5f58c40e5d74f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Jun 2020 20:34:22 +0200 Subject: [PATCH 29/33] :arrow_up: Set n8n-nodes-base@0.65.0 on n8n --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index f3a747bfe2..f221cd59c7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -102,7 +102,7 @@ "mysql2": "^2.0.1", "n8n-core": "~0.36.0", "n8n-editor-ui": "~0.47.0", - "n8n-nodes-base": "~0.64.1", + "n8n-nodes-base": "~0.65.0", "n8n-workflow": "~0.33.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", From 7f74408487664cc9f2aaf11494bb9b4f8f8aaa6b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Jun 2020 20:35:00 +0200 Subject: [PATCH 30/33] :bookmark: Release n8n@0.70.0 --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index f221cd59c7..1628a80b82 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.69.1", + "version": "0.70.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From bc4d407c4427e41259e2b180ec2053424fb97431 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sat, 13 Jun 2020 19:48:24 -0400 Subject: [PATCH 31/33] :sparkles: Improvements to Hubspot-Node --- .../HubspotOAuth2Api.credentials.ts | 26 +- .../nodes/Hubspot/GenericFunctions.ts | 37 +- .../nodes-base/nodes/Hubspot/Hubspot.node.ts | 8 +- .../nodes/Hubspot/HubspotTrigger.node.ts | 61 +--- packages/nodes-base/package.json | 344 +----------------- 5 files changed, 62 insertions(+), 414 deletions(-) diff --git a/packages/nodes-base/credentials/HubspotOAuth2Api.credentials.ts b/packages/nodes-base/credentials/HubspotOAuth2Api.credentials.ts index 775211cbc9..ce18b899df 100644 --- a/packages/nodes-base/credentials/HubspotOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/HubspotOAuth2Api.credentials.ts @@ -3,6 +3,12 @@ import { NodePropertyTypes, } from 'n8n-workflow'; +const scopes = [ + 'contacts', + 'forms', + 'tickets', +]; + export class HubspotOAuth2Api implements ICredentialType { name = 'hubspotOAuth2Api'; extends = [ @@ -27,8 +33,8 @@ export class HubspotOAuth2Api implements ICredentialType { { displayName: 'Scope', name: 'scope', - type: 'string' as NodePropertyTypes, - default: '', + type: 'hidden' as NodePropertyTypes, + default: scopes.join(' '), }, { displayName: 'Auth URI Query Parameters', @@ -39,20 +45,8 @@ export class HubspotOAuth2Api implements ICredentialType { { displayName: 'Authentication', name: 'authentication', - type: 'options' as NodePropertyTypes, - options: [ - { - name: 'Body', - value: 'body', - description: 'Send credentials in body', - }, - { - name: 'Header', - value: 'header', - description: 'Send credentials as Basic Auth header', - }, - ], - default: 'header', + type: 'hidden' as NodePropertyTypes, + default: 'body', description: 'Resource to consume.', }, ]; diff --git a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts index eb414b82aa..d8d68abfb6 100644 --- a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts @@ -14,7 +14,12 @@ import { } from 'n8n-workflow'; export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any - const authenticationMethod = this.getNodeParameter('authentication', 0); + + let authenticationMethod = this.getNodeParameter('authentication', 0); + + if (this.getNode().type.includes('Trigger')) { + authenticationMethod = 'developerApi'; + } const options: OptionsWithUri = { method, @@ -26,26 +31,40 @@ export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions }; try { - if (authenticationMethod === 'accessToken') { + if (authenticationMethod === 'apiKey') { const credentials = this.getCredentials('hubspotApi'); options.qs.hapikey = credentials!.apiKey as string; + return await this.helpers.request!(options); + } else if (authenticationMethod === 'developerApi') { + const credentials = this.getCredentials('hubspotDeveloperApi'); + + options.qs.hapikey = credentials!.apiKey as string; + return await this.helpers.request!(options); } else { // @ts-ignore return await this.helpers.requestOAuth2!.call(this, 'hubspotOAuth2Api', options, 'Bearer'); } } catch (error) { - if (error.response && error.response.body && error.response.body.errors) { - // Try to return the error prettier - let errorMessages = error.response.body.errors; + let errorMessages; - if (errorMessages[0].message) { - // @ts-ignore - errorMessages = errorMessages.map(errorItem => errorItem.message); + if (error.response && error.response.body) { + + if (error.response.body.message) { + + errorMessages = [error.response.body.message]; + + } else if (error.response.body.errors) { + // Try to return the error prettier + errorMessages = error.response.body.errors; + + if (errorMessages[0].message) { + // @ts-ignore + errorMessages = errorMessages.map(errorItem => errorItem.message); + } } - throw new Error(`Hubspot error response [${error.statusCode}]: ${errorMessages.join('|')}`); } diff --git a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts index bed4606042..adf01d1ec3 100644 --- a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts +++ b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts @@ -76,7 +76,7 @@ export class Hubspot implements INodeType { displayOptions: { show: { authentication: [ - 'accessToken', + 'apiKey', ], }, }, @@ -100,15 +100,15 @@ export class Hubspot implements INodeType { type: 'options', options: [ { - name: 'Access Token', - value: 'accessToken', + name: 'API Key', + value: 'apiKey', }, { name: 'OAuth2', value: 'oAuth2', }, ], - default: 'accessToken', + default: 'apiKey', description: 'The method of authentication.', }, { diff --git a/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts b/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts index 38fd678d58..47b2318b36 100644 --- a/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts +++ b/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts @@ -37,24 +37,6 @@ export class HubspotTrigger implements INodeType { { name: 'hubspotDeveloperApi', required: true, - displayOptions: { - show: { - authentication: [ - 'developerApi', - ], - }, - }, - }, - { - name: 'hubspotOAuth2Api', - required: true, - displayOptions: { - show: { - authentication: [ - 'oAuth2', - ], - }, - }, }, ], webhooks: [ @@ -72,23 +54,6 @@ export class HubspotTrigger implements INodeType { }, ], properties: [ - { - displayName: 'Authentication', - name: 'authentication', - type: 'options', - options: [ - { - name: 'Developer API', - value: 'developerApi', - }, - { - name: 'OAuth2', - value: 'oAuth2', - }, - ], - default: 'developerApi', - description: 'The method of authentication.', - }, { displayName: 'App ID', name: 'appId', @@ -282,15 +247,7 @@ export class HubspotTrigger implements INodeType { async webhook(this: IWebhookFunctions): Promise { - const authenticationMethod = this.getNodeParameter('authentication') as string; - - let credentials : IDataObject; - - if (authenticationMethod === 'hubspotDeveloperApi') { - credentials = this.getCredentials('hubspotDeveloperApi') as IDataObject; - } else { - credentials = this.getCredentials('hubspotOAuth2Api') as IDataObject; - } + const credentials = this.getCredentials('hubspotDeveloperApi') as IDataObject; if (credentials === undefined) { throw new Error('No credentials found!'); @@ -303,12 +260,18 @@ export class HubspotTrigger implements INodeType { if (headerData['x-hubspot-signature'] === undefined) { return {}; } - const hash = `${credentials!.clientSecret}${JSON.stringify(bodyData)}`; - const signature = createHash('sha256').update(hash).digest('hex'); - //@ts-ignore - if (signature !== headerData['x-hubspot-signature']) { - return {}; + + // check signare if client secret is defined + + if (credentials.clientSecret !== '') { + const hash = `${credentials!.clientSecret}${JSON.stringify(bodyData)}`; + const signature = createHash('sha256').update(hash).digest('hex'); + //@ts-ignore + if (signature !== headerData['x-hubspot-signature']) { + return {}; + } } + for (let i = 0; i < bodyData.length; i++) { const subscriptionType = bodyData[i].subscriptionType as string; if (subscriptionType.includes('contact')) { diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 7b90d3e2a9..5b33ebcc98 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -69,6 +69,7 @@ "dist/credentials/HttpHeaderAuth.credentials.js", "dist/credentials/HubspotApi.credentials.js", "dist/credentials/HubspotDeveloperApi.credentials.js", + "dist/credentials/HubspotOAuth2Api.credentials.js", "dist/credentials/HunterApi.credentials.js", "dist/credentials/Imap.credentials.js", "dist/credentials/IntercomApi.credentials.js", @@ -356,340 +357,11 @@ "/dist/", "/node_modules/" ], - "n8n": { - "credentials": [ - "dist/credentials/ActiveCampaignApi.credentials.js", - "dist/credentials/AgileCrmApi.credentials.js", - "dist/credentials/AcuitySchedulingApi.credentials.js", - "dist/credentials/AirtableApi.credentials.js", - "dist/credentials/Amqp.credentials.js", - "dist/credentials/AsanaApi.credentials.js", - "dist/credentials/Aws.credentials.js", - "dist/credentials/AffinityApi.credentials.js", - "dist/credentials/BannerbearApi.credentials.js", - "dist/credentials/BitbucketApi.credentials.js", - "dist/credentials/BitlyApi.credentials.js", - "dist/credentials/ChargebeeApi.credentials.js", - "dist/credentials/ClearbitApi.credentials.js", - "dist/credentials/ClickUpApi.credentials.js", - "dist/credentials/ClockifyApi.credentials.js", - "dist/credentials/CockpitApi.credentials.js", - "dist/credentials/CodaApi.credentials.js", - "dist/credentials/CopperApi.credentials.js", - "dist/credentials/CalendlyApi.credentials.js", - "dist/credentials/DisqusApi.credentials.js", - "dist/credentials/DriftApi.credentials.js", - "dist/credentials/DropboxApi.credentials.js", - "dist/credentials/EventbriteApi.credentials.js", - "dist/credentials/FacebookGraphApi.credentials.js", - "dist/credentials/FreshdeskApi.credentials.js", - "dist/credentials/FileMaker.credentials.js", - "dist/credentials/FlowApi.credentials.js", - "dist/credentials/GithubApi.credentials.js", - "dist/credentials/GithubOAuth2Api.credentials.js", - "dist/credentials/GitlabApi.credentials.js", - "dist/credentials/GoogleApi.credentials.js", - "dist/credentials/GoogleCalendarOAuth2Api.credentials.js", - "dist/credentials/GoogleOAuth2Api.credentials.js", - "dist/credentials/GoogleSheetsOAuth2Api.credentials.js", - "dist/credentials/GumroadApi.credentials.js", - "dist/credentials/HarvestApi.credentials.js", - "dist/credentials/HelpScoutOAuth2Api.credentials.js", - "dist/credentials/HttpBasicAuth.credentials.js", - "dist/credentials/HttpDigestAuth.credentials.js", - "dist/credentials/HttpHeaderAuth.credentials.js", - "dist/credentials/HubspotApi.credentials.js", - "dist/credentials/HubspotDeveloperApi.credentials.js", - "dist/credentials/HubspotOAuth2Api.credentials.js", - "dist/credentials/HunterApi.credentials.js", - "dist/credentials/Imap.credentials.js", - "dist/credentials/IntercomApi.credentials.js", - "dist/credentials/InvoiceNinjaApi.credentials.js", - "dist/credentials/JiraSoftwareCloudApi.credentials.js", - "dist/credentials/JiraSoftwareServerApi.credentials.js", - "dist/credentials/JotFormApi.credentials.js", - "dist/credentials/KeapOAuth2Api.credentials.js", - "dist/credentials/LinkFishApi.credentials.js", - "dist/credentials/MailchimpApi.credentials.js", - "dist/credentials/MailgunApi.credentials.js", - "dist/credentials/MailjetEmailApi.credentials.js", - "dist/credentials/MailjetSmsApi.credentials.js", - "dist/credentials/MandrillApi.credentials.js", - "dist/credentials/MattermostApi.credentials.js", - "dist/credentials/MauticApi.credentials.js", - "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", - "dist/credentials/MicrosoftOAuth2Api.credentials.js", - "dist/credentials/MicrosoftOneDriveOAuth2Api.credentials.js", - "dist/credentials/MoceanApi.credentials.js", - "dist/credentials/MondayComApi.credentials.js", - "dist/credentials/MongoDb.credentials.js", - "dist/credentials/Msg91Api.credentials.js", - "dist/credentials/MySql.credentials.js", - "dist/credentials/NextCloudApi.credentials.js", - "dist/credentials/OAuth1Api.credentials.js", - "dist/credentials/OAuth2Api.credentials.js", - "dist/credentials/OpenWeatherMapApi.credentials.js", - "dist/credentials/PagerDutyApi.credentials.js", - "dist/credentials/PayPalApi.credentials.js", - "dist/credentials/PipedriveApi.credentials.js", - "dist/credentials/Postgres.credentials.js", - "dist/credentials/Redis.credentials.js", - "dist/credentials/RocketchatApi.credentials.js", - "dist/credentials/RundeckApi.credentials.js", - "dist/credentials/ShopifyApi.credentials.js", - "dist/credentials/SalesforceOAuth2Api.credentials.js", - "dist/credentials/SlackApi.credentials.js", - "dist/credentials/SlackOAuth2Api.credentials.js", - "dist/credentials/Sms77Api.credentials.js", - "dist/credentials/Smtp.credentials.js", - "dist/credentials/StripeApi.credentials.js", - "dist/credentials/SalesmateApi.credentials.js", - "dist/credentials/SegmentApi.credentials.js", - "dist/credentials/SurveyMonkeyApi.credentials.js", - "dist/credentials/TelegramApi.credentials.js", - "dist/credentials/TodoistApi.credentials.js", - "dist/credentials/TrelloApi.credentials.js", - "dist/credentials/TwilioApi.credentials.js", - "dist/credentials/TwitterOAuth1Api.credentials.js", - "dist/credentials/TypeformApi.credentials.js", - "dist/credentials/TogglApi.credentials.js", - "dist/credentials/UpleadApi.credentials.js", - "dist/credentials/VeroApi.credentials.js", - "dist/credentials/WebflowApi.credentials.js", - "dist/credentials/WooCommerceApi.credentials.js", - "dist/credentials/WordpressApi.credentials.js", - "dist/credentials/ZendeskApi.credentials.js", - "dist/credentials/ZohoOAuth2Api.credentials.js", - "dist/credentials/ZulipApi.credentials.js" - ], - "nodes": [ - "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", - "dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js", - "dist/nodes/AgileCrm/AgileCrm.node.js", - "dist/nodes/Airtable/Airtable.node.js", - "dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js", - "dist/nodes/Amqp/Amqp.node.js", - "dist/nodes/Amqp/AmqpTrigger.node.js", - "dist/nodes/Asana/Asana.node.js", - "dist/nodes/Asana/AsanaTrigger.node.js", - "dist/nodes/Affinity/Affinity.node.js", - "dist/nodes/Affinity/AffinityTrigger.node.js", - "dist/nodes/Aws/AwsLambda.node.js", - "dist/nodes/Aws/S3/AwsS3.node.js", - "dist/nodes/Aws/AwsSes.node.js", - "dist/nodes/Aws/AwsSns.node.js", - "dist/nodes/Aws/AwsSnsTrigger.node.js", - "dist/nodes/Bannerbear/Bannerbear.node.js", - "dist/nodes/Bitbucket/BitbucketTrigger.node.js", - "dist/nodes/Bitly/Bitly.node.js", - "dist/nodes/Calendly/CalendlyTrigger.node.js", - "dist/nodes/Chargebee/Chargebee.node.js", - "dist/nodes/Chargebee/ChargebeeTrigger.node.js", - "dist/nodes/Clearbit/Clearbit.node.js", - "dist/nodes/ClickUp/ClickUp.node.js", - "dist/nodes/ClickUp/ClickUpTrigger.node.js", - "dist/nodes/Clockify/ClockifyTrigger.node.js", - "dist/nodes/Cockpit/Cockpit.node.js", - "dist/nodes/Coda/Coda.node.js", - "dist/nodes/Copper/CopperTrigger.node.js", - "dist/nodes/Cron.node.js", - "dist/nodes/Crypto.node.js", - "dist/nodes/DateTime.node.js", - "dist/nodes/Discord/Discord.node.js", - "dist/nodes/Disqus/Disqus.node.js", - "dist/nodes/Drift/Drift.node.js", - "dist/nodes/Dropbox/Dropbox.node.js", - "dist/nodes/EditImage.node.js", - "dist/nodes/EmailReadImap.node.js", - "dist/nodes/EmailSend.node.js", - "dist/nodes/ErrorTrigger.node.js", - "dist/nodes/Eventbrite/EventbriteTrigger.node.js", - "dist/nodes/ExecuteCommand.node.js", - "dist/nodes/ExecuteWorkflow.node.js", - "dist/nodes/Facebook/FacebookGraphApi.node.js", - "dist/nodes/FileMaker/FileMaker.node.js", - "dist/nodes/Freshdesk/Freshdesk.node.js", - "dist/nodes/Flow/Flow.node.js", - "dist/nodes/Flow/FlowTrigger.node.js", - "dist/nodes/Function.node.js", - "dist/nodes/FunctionItem.node.js", - "dist/nodes/Github/Github.node.js", - "dist/nodes/Github/GithubTrigger.node.js", - "dist/nodes/Gitlab/Gitlab.node.js", - "dist/nodes/Gitlab/GitlabTrigger.node.js", - "dist/nodes/Google/Calendar/GoogleCalendar.node.js", - "dist/nodes/Google/Drive/GoogleDrive.node.js", - "dist/nodes/Google/Sheet/GoogleSheets.node.js", - "dist/nodes/GraphQL/GraphQL.node.js", - "dist/nodes/Gumroad/GumroadTrigger.node.js", - "dist/nodes/Harvest/Harvest.node.js", - "dist/nodes/HelpScout/HelpScout.node.js", - "dist/nodes/HelpScout/HelpScoutTrigger.node.js", - "dist/nodes/HtmlExtract/HtmlExtract.node.js", - "dist/nodes/HttpRequest.node.js", - "dist/nodes/Hubspot/Hubspot.node.js", - "dist/nodes/Hubspot/HubspotTrigger.node.js", - "dist/nodes/Hunter/Hunter.node.js", - "dist/nodes/If.node.js", - "dist/nodes/Intercom/Intercom.node.js", - "dist/nodes/Interval.node.js", - "dist/nodes/InvoiceNinja/InvoiceNinja.node.js", - "dist/nodes/InvoiceNinja/InvoiceNinjaTrigger.node.js", - "dist/nodes/Jira/Jira.node.js", - "dist/nodes/JotForm/JotFormTrigger.node.js", - "dist/nodes/Keap/Keap.node.js", - "dist/nodes/Keap/KeapTrigger.node.js", - "dist/nodes/LinkFish/LinkFish.node.js", - "dist/nodes/Mailchimp/Mailchimp.node.js", - "dist/nodes/Mailchimp/MailchimpTrigger.node.js", - "dist/nodes/Mailgun/Mailgun.node.js", - "dist/nodes/Mailjet/Mailjet.node.js", - "dist/nodes/Mailjet/MailjetTrigger.node.js", - "dist/nodes/Mandrill/Mandrill.node.js", - "dist/nodes/Mattermost/Mattermost.node.js", - "dist/nodes/Mautic/Mautic.node.js", - "dist/nodes/Mautic/MauticTrigger.node.js", - "dist/nodes/Merge.node.js", - "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", - "dist/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.js", - "dist/nodes/MoveBinaryData.node.js", - "dist/nodes/Mocean/Mocean.node.js", - "dist/nodes/MondayCom/MondayCom.node.js", - "dist/nodes/MongoDb/MongoDb.node.js", - "dist/nodes/MoveBinaryData.node.js", - "dist/nodes/Msg91/Msg91.node.js", - "dist/nodes/MySql/MySql.node.js", - "dist/nodes/NextCloud/NextCloud.node.js", - "dist/nodes/NoOp.node.js", - "dist/nodes/OpenWeatherMap.node.js", - "dist/nodes/PagerDuty/PagerDuty.node.js", - "dist/nodes/PayPal/PayPal.node.js", - "dist/nodes/PayPal/PayPalTrigger.node.js", - "dist/nodes/Pipedrive/Pipedrive.node.js", - "dist/nodes/Pipedrive/PipedriveTrigger.node.js", - "dist/nodes/Postgres/Postgres.node.js", - "dist/nodes/ReadBinaryFile.node.js", - "dist/nodes/ReadBinaryFiles.node.js", - "dist/nodes/ReadPdf.node.js", - "dist/nodes/Redis/Redis.node.js", - "dist/nodes/RenameKeys.node.js", - "dist/nodes/Rocketchat/Rocketchat.node.js", - "dist/nodes/RssFeedRead.node.js", - "dist/nodes/Rundeck/Rundeck.node.js", - "dist/nodes/Salesforce/Salesforce.node.js", - "dist/nodes/Set.node.js", - "dist/nodes/Shopify/Shopify.node.js", - "dist/nodes/Shopify/ShopifyTrigger.node.js", - "dist/nodes/Slack/Slack.node.js", - "dist/nodes/Sms77/Sms77.node.js", - "dist/nodes/SplitInBatches.node.js", - "dist/nodes/SpreadsheetFile.node.js", - "dist/nodes/SseTrigger.node.js", - "dist/nodes/Start.node.js", - "dist/nodes/Stripe/StripeTrigger.node.js", - "dist/nodes/Switch.node.js", - "dist/nodes/Salesmate/Salesmate.node.js", - "dist/nodes/Segment/Segment.node.js", - "dist/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.js", - "dist/nodes/Telegram/Telegram.node.js", - "dist/nodes/Telegram/TelegramTrigger.node.js", - "dist/nodes/Todoist/Todoist.node.js", - "dist/nodes/Toggl/TogglTrigger.node.js", - "dist/nodes/Trello/Trello.node.js", - "dist/nodes/Trello/TrelloTrigger.node.js", - "dist/nodes/Twilio/Twilio.node.js", - "dist/nodes/Twitter/Twitter.node.js", - "dist/nodes/Typeform/TypeformTrigger.node.js", - "dist/nodes/Uplead/Uplead.node.js", - "dist/nodes/Vero/Vero.node.js", - "dist/nodes/Webflow/WebflowTrigger.node.js", - "dist/nodes/Webhook.node.js", - "dist/nodes/Wordpress/Wordpress.node.js", - "dist/nodes/WooCommerce/WooCommerce.node.js", - "dist/nodes/WooCommerce/WooCommerceTrigger.node.js", - "dist/nodes/WriteBinaryFile.node.js", - "dist/nodes/Xml.node.js", - "dist/nodes/Zendesk/Zendesk.node.js", - "dist/nodes/Zendesk/ZendeskTrigger.node.js", - "dist/nodes/Zoho/ZohoCrm.node.js", - "dist/nodes/Zulip/Zulip.node.js" - ] - }, - "devDependencies": { - "@types/aws4": "^1.5.1", - "@types/basic-auth": "^1.1.2", - "@types/cheerio": "^0.22.15", - "@types/cron": "^1.6.1", - "@types/eventsource": "^1.1.2", - "@types/express": "^4.16.1", - "@types/formidable": "^1.0.31", - "@types/gm": "^1.18.2", - "@types/imap-simple": "^4.2.0", - "@types/jest": "^24.0.18", - "@types/lodash.set": "^4.3.6", - "@types/moment-timezone": "^0.5.12", - "@types/mongodb": "^3.5.4", - "@types/node": "^10.10.1", - "@types/nodemailer": "^6.4.0", - "@types/redis": "^2.8.11", - "@types/request-promise-native": "~1.0.15", - "@types/uuid": "^3.4.6", - "@types/xml2js": "^0.4.3", - "gulp": "^4.0.0", - "jest": "^24.9.0", - "n8n-workflow": "~0.31.0", - "ts-jest": "^24.0.2", - "tslint": "^5.17.0", - "typescript": "~3.7.4" - }, - "dependencies": { - "aws4": "^1.8.0", - "basic-auth": "^2.0.1", - "change-case": "^4.1.1", - "cheerio": "^1.0.0-rc.3", - "cron": "^1.7.2", - "eventsource": "^1.0.7", - "formidable": "^1.2.1", - "glob-promise": "^3.4.0", - "gm": "^1.23.1", - "googleapis": "~50.0.0", - "imap-simple": "^4.3.0", - "jsonwebtoken": "^8.5.1", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", - "lodash.unset": "^4.5.2", - "moment": "2.24.0", - "moment-timezone": "^0.5.28", - "mongodb": "^3.5.5", - "mysql2": "^2.0.1", - "n8n-core": "~0.34.0", - "nodemailer": "^6.4.6", - "pdf-parse": "^1.1.1", - "pg-promise": "^9.0.3", - "redis": "^2.8.0", - "request": "^2.88.2", - "rhea": "^1.0.11", - "rss-parser": "^3.7.0", - "uuid": "^3.4.0", - "vm2": "^3.6.10", - "xlsx": "^0.14.3", - "xml2js": "^0.4.22" - }, - "jest": { - "transform": { - "^.+\\.tsx?$": "ts-jest" - }, - "testURL": "http://localhost/", - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", - "testPathIgnorePatterns": [ - "/dist/", - "/node_modules/" - ], - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "json" - ] - } + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "json" + ] + } } From a23b0e6f9ca6509f7e4022426fb631b107cabeda Mon Sep 17 00:00:00 2001 From: ricardo Date: Sat, 13 Jun 2020 22:29:21 -0400 Subject: [PATCH 32/33] :zap: Small fix --- packages/nodes-base/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 5b33ebcc98..f762717e76 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -80,6 +80,7 @@ "dist/credentials/KeapOAuth2Api.credentials.js", "dist/credentials/LinkFishApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", + "dist/credentials/MailchimpOAuth2Api.credentials.js", "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MailjetEmailApi.credentials.js", "dist/credentials/MailjetSmsApi.credentials.js", From e859b27a891d2040cba73f65d7384c06daebe699 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sat, 13 Jun 2020 22:37:03 -0400 Subject: [PATCH 33/33] :zap: Improvements to Mailchimp-Node --- .../MailchimpOAuth2Api.credentials.ts | 14 ------- .../nodes/Mailchimp/GenericFunctions.ts | 39 +++++++++++++------ .../nodes/Mailchimp/Mailchimp.node.ts | 10 ++--- .../nodes/Mailchimp/MailchimpTrigger.node.ts | 8 ++-- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/packages/nodes-base/credentials/MailchimpOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MailchimpOAuth2Api.credentials.ts index 0a0efc7091..886424b6a6 100644 --- a/packages/nodes-base/credentials/MailchimpOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/MailchimpOAuth2Api.credentials.ts @@ -11,20 +11,6 @@ export class MailchimpOAuth2Api implements ICredentialType { ]; displayName = 'Mailchimp OAuth2 API'; properties = [ - { - displayName: 'Mailchimp Server', - name: 'server', - type: 'string' as NodePropertyTypes, - default: 'https://login.mailchimp.com/', - description: 'The server to connect to.', - }, - { - displayName: 'Datacenter', - name: 'dataCenter', - type: 'string' as NodePropertyTypes, - default: 'us10', - description: 'Datacenter that your Mailchimp application is hosted on. Found in the URL of your Mailchimp dashboard.', - }, { displayName: 'Authorization URL', name: 'authUrl', diff --git a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts index 3d66c2d8ab..59362b50d6 100644 --- a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts @@ -34,33 +34,34 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio } try { - if (authenticationMethod === 'accessToken') { + if (authenticationMethod === 'apiKey') { const credentials = this.getCredentials('mailchimpApi'); - + if (credentials === undefined) { throw new Error('No credentials got returned!'); } - + options.headers = Object.assign({}, headers, { Authorization: `apikey ${credentials.apiKey}` }); - + if (!(credentials.apiKey as string).includes('-')) { throw new Error('The API key is not valid!'); } - + const datacenter = (credentials.apiKey as string).split('-').pop(); options.url = `https://${datacenter}.${host}${endpoint}`; - + return await this.helpers.request!(options); } else { - const credentials = this.getCredentials('mailchimpOAuth2Api'); - const datacenter = credentials!.dataCenter; - - options.url = `https://${datacenter}.${host}${endpoint}`; + const credentials = this.getCredentials('mailchimpOAuth2Api') as IDataObject; + + const { api_endpoint } = await getMetadata.call(this, credentials.oauthTokenData as IDataObject); + + options.url = `${api_endpoint}/3.0${endpoint}`; //@ts-ignore - return await this.helpers.requestOAuth2!.call(this, 'mailchimpOAuth2Api', options, 'bearer'); + return await this.helpers.requestOAuth2!.call(this, 'mailchimpOAuth2Api', options, 'Bearer'); } } catch (error) { - if (error.response.body && error.response.body.detail) { + if (error.respose && error.response.body && error.response.body.detail) { throw new Error(`Mailchimp Error response [${error.statusCode}]: ${error.response.body.detail}`); } throw error; @@ -96,3 +97,17 @@ export function validateJSON(json: string | undefined): any { // tslint:disable- } return result; } + +function getMetadata(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, oauthTokenData: IDataObject) { + const credentials = this.getCredentials('mailchimpOAuth2Api') as IDataObject; + const options: OptionsWithUrl = { + headers: { + 'Accept': 'application/json', + 'Authorization': `OAuth ${oauthTokenData.access_token}`, + }, + method: 'GET', + url: credentials.metadataUrl as string, + json: true, + }; + return this.helpers.request!(options); +} diff --git a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts index 3b8d17c302..25dbeb9984 100644 --- a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts @@ -72,7 +72,7 @@ export class Mailchimp implements INodeType { displayOptions: { show: { authentication: [ - 'accessToken', + 'apiKey', ], }, }, @@ -96,15 +96,15 @@ export class Mailchimp implements INodeType { type: 'options', options: [ { - name: 'Access Token', - value: 'accessToken', + name: 'API Key', + value: 'apiKey', }, { name: 'OAuth2', value: 'oAuth2', }, ], - default: 'accessToken', + default: 'apiKey', description: 'Method of authentication.', }, { @@ -1571,7 +1571,7 @@ export class Mailchimp implements INodeType { responseData = { success: true }; } } - + if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else { diff --git a/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts b/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts index 67e07ad6bd..9fbd80a0f5 100644 --- a/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts @@ -36,7 +36,7 @@ export class MailchimpTrigger implements INodeType { displayOptions: { show: { authentication: [ - 'accessToken', + 'apiKey', ], }, }, @@ -74,15 +74,15 @@ export class MailchimpTrigger implements INodeType { type: 'options', options: [ { - name: 'Access Token', - value: 'accessToken', + name: 'API Key', + value: 'apiKey', }, { name: 'OAuth2', value: 'oAuth2', }, ], - default: 'accessToken', + default: 'apiKey', description: 'Method of authentication.', }, {