From a380a9a3941e9059668aaf478555fee045827b1a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 4 May 2020 08:56:01 +0200 Subject: [PATCH 001/120] :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 4e2efaacfd7..65edec601d8 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 69b31540642..5705a5fc7d1 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 00000000000..2c00a525c61 --- /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 225e02885f9..b54e1eb299b 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 ad2a3c1de5e..baf4f1aa580 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 00000000000..2cb60decfcc --- /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 0b7ae6ad0a9..fba554c04e0 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 002/120] :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 5705a5fc7d1..7ef5155a67b 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 2c00a525c61..c4f12301b42 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 b54e1eb299b..0b9b9eca695 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 baf4f1aa580..a592c642c23 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 2cb60decfcc..f10000f1f04 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 003/120] :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 c4f12301b42..9d195ccbad8 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 0b9b9eca695..5e76f2c4e21 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 a592c642c23..a36a2fd81e0 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 9665080ebd2..4b86357852b 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 3a096065502..c3ce61cedc9 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 f10000f1f04..b49c0600b20 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 af3d799e5c8701ca70aad24b97f0641716962846 Mon Sep 17 00:00:00 2001 From: ricardo Date: Mon, 1 Jun 2020 20:42:38 -0400 Subject: [PATCH 004/120] :sparkles: Oauth1 support and Twitter node --- packages/cli/package.json | 1 + packages/cli/src/Server.ts | 160 +++++++++++++- packages/core/src/Interfaces.ts | 23 +- packages/core/src/NodeExecuteFunctions.ts | 115 ++++++++-- packages/editor-ui/src/Interface.ts | 1 + .../src/components/CredentialsInput.vue | 23 +- .../src/components/mixins/restApi.ts | 5 + .../credentials/OAuth1Api.credentials.ts | 63 ++++++ .../TwitterOAuth1Api.credentials.ts | 38 ++++ .../nodes/Github/GenericFunctions.ts | 2 +- .../nodes/Google/GenericFunctions.ts | 2 +- .../nodes/HelpScout/GenericFunctions.ts | 2 +- packages/nodes-base/nodes/HttpRequest.node.ts | 2 +- .../nodes-base/nodes/Keap/GenericFunctions.ts | 2 +- .../nodes/Microsoft/Excel/GenericFunctions.ts | 2 +- .../Microsoft/OneDrive/GenericFunctions.ts | 2 +- packages/nodes-base/nodes/OAuth.node.ts | 2 +- .../nodes/Salesforce/GenericFunctions.ts | 2 +- .../nodes/Slack/GenericFunctions.ts | 2 +- .../nodes/Twitter/GenericFunctions.ts | 44 ++++ .../nodes/Twitter/TweetDescription.ts | 208 ++++++++++++++++++ .../nodes/Twitter/TweetInterface.ts | 8 + .../nodes-base/nodes/Twitter/Twitter.node.ts | 151 +++++++++++++ packages/nodes-base/nodes/Twitter/twitter.png | Bin 0 -> 1490 bytes .../nodes-base/nodes/Zoho/GenericFunctions.ts | 2 +- packages/nodes-base/package.json | 3 + 26 files changed, 822 insertions(+), 43 deletions(-) create mode 100644 packages/nodes-base/credentials/OAuth1Api.credentials.ts create mode 100644 packages/nodes-base/credentials/TwitterOAuth1Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Twitter/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Twitter/TweetDescription.ts create mode 100644 packages/nodes-base/nodes/Twitter/TweetInterface.ts create mode 100644 packages/nodes-base/nodes/Twitter/Twitter.node.ts create mode 100644 packages/nodes-base/nodes/Twitter/twitter.png diff --git a/packages/cli/package.json b/packages/cli/package.json index 5ab81530649..0b4e2fa9cb8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -103,6 +103,7 @@ "n8n-editor-ui": "~0.45.0", "n8n-nodes-base": "~0.62.1", "n8n-workflow": "~0.31.0", + "oauth-1.0a": "^2.2.6", "open": "^7.0.0", "pg": "^7.11.0", "request-promise-native": "^1.0.7", diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index d9c760a7c9c..88e60d4c521 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -13,10 +13,13 @@ import { import * as bodyParser from 'body-parser'; require('body-parser-xml')(bodyParser); import * as history from 'connect-history-api-fallback'; -import * as requestPromise from 'request-promise-native'; import * as _ from 'lodash'; import * as clientOAuth2 from 'client-oauth2'; +import * as clientOAuth1 from 'oauth-1.0a'; +import { RequestOptions } from 'oauth-1.0a'; import * as csrf from 'csrf'; +import * as requestPromise from 'request-promise-native'; +import { createHmac } from 'crypto'; import { ActiveExecutions, @@ -90,7 +93,8 @@ import * as jwks from 'jwks-rsa'; // @ts-ignore import * as timezones from 'google-timezones-json'; import * as parseUrl from 'parseurl'; - +import * as querystring from 'querystring'; +import { OptionsWithUrl } from 'request-promise-native'; class App { @@ -890,6 +894,158 @@ class App { return returnData; })); + // ---------------------------------------- + // OAuth1-Credential/Auth + // ---------------------------------------- + + // Authorize OAuth Data + this.app.get('/rest/oauth1-credential/auth', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + if (req.query.id === undefined) { + throw new Error('Required credential id is missing!'); + } + + const result = await Db.collections.Credentials!.findOne(req.query.id as string); + if (result === undefined) { + res.status(404).send('The credential is not known.'); + return ''; + } + + let encryptionKey = undefined; + encryptionKey = await UserSettings.getEncryptionKey(); + if (encryptionKey === undefined) { + throw new Error('No encryption key got found to decrypt the credentials!'); + } + + // Decrypt the currently saved credentials + const workflowCredentials: IWorkflowCredentials = { + [result.type as string]: { + [result.name as string]: result as ICredentialsEncrypted, + }, + }; + const credentialsHelper = new CredentialsHelper(workflowCredentials, encryptionKey); + const decryptedDataOriginal = credentialsHelper.getDecrypted(result.name, result.type, true); + const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type); + + const signatureMethod = _.get(oauthCredentials, 'signatureMethod') as string; + + const oauth = new clientOAuth1({ + consumer: { + key: _.get(oauthCredentials, 'consumerKey') as string, + secret: _.get(oauthCredentials, 'consumerSecret') as string, + }, + signature_method: signatureMethod, + hash_function(base, key) { + const algorithm = (signatureMethod === 'HMAC-SHA1') ? 'sha1' : 'sha256'; + return createHmac(algorithm, key) + .update(base) + .digest('base64'); + }, + }); + + const callback = `${WebhookHelpers.getWebhookBaseUrl()}rest/oauth1-credential/callback?cid=${req.query.id}`; + + const options: RequestOptions = { + method: 'POST', + url: (_.get(oauthCredentials, 'requestTokenUrl') as string), + data: { + oauth_callback: callback, + }, + }; + + const data = oauth.toHeader(oauth.authorize(options as RequestOptions)); + + //@ts-ignore + options.headers = data; + + const response = await requestPromise(options); + + // Response comes as x-www-form-urlencoded string so convert it to JSON + + const responseJson = querystring.parse(response); + + const returnUri = `${_.get(oauthCredentials, 'authUrl')}?oauth_token=${responseJson.oauth_token}`; + + // Encrypt the data + const credentials = new Credentials(result.name, result.type, result.nodesAccess); + + credentials.setData(decryptedDataOriginal, encryptionKey); + const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb; + + // Add special database related data + newCredentialsData.updatedAt = this.getCurrentDate(); + + // Update the credentials in DB + await Db.collections.Credentials!.update(req.query.id as string, newCredentialsData); + + return returnUri; + })); + + // Verify and store app code. Generate access tokens and store for respective credential. + this.app.get('/rest/oauth1-credential/callback', async (req: express.Request, res: express.Response) => { + const { oauth_verifier, oauth_token, cid } = req.query; + + if (oauth_verifier === undefined || oauth_token === undefined) { + throw new Error('Insufficient parameters for OAuth1 callback'); + } + + const result = await Db.collections.Credentials!.findOne(cid as any); + if (result === undefined) { + const errorResponse = new ResponseHelper.ResponseError('The credential is not known.', undefined, 404); + return ResponseHelper.sendErrorResponse(res, errorResponse); + } + + let encryptionKey = undefined; + encryptionKey = await UserSettings.getEncryptionKey(); + if (encryptionKey === undefined) { + const errorResponse = new ResponseHelper.ResponseError('No encryption key got found to decrypt the credentials!', undefined, 503); + return ResponseHelper.sendErrorResponse(res, errorResponse); + } + + // Decrypt the currently saved credentials + const workflowCredentials: IWorkflowCredentials = { + [result.type as string]: { + [result.name as string]: result as ICredentialsEncrypted, + }, + }; + const credentialsHelper = new CredentialsHelper(workflowCredentials, encryptionKey); + const decryptedDataOriginal = credentialsHelper.getDecrypted(result.name, result.type, true); + const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type); + + const options: OptionsWithUrl = { + method: 'POST', + url: _.get(oauthCredentials, 'accessTokenUrl') as string, + qs: { + oauth_token, + oauth_verifier, + } + }; + + let oauthToken; + + try { + oauthToken = await requestPromise(options); + } catch (error) { + const errorResponse = new ResponseHelper.ResponseError('Unable to get access tokens!', undefined, 404); + return ResponseHelper.sendErrorResponse(res, errorResponse); + } + + // Response comes as x-www-form-urlencoded string so convert it to JSON + + const oauthTokenJson = querystring.parse(oauthToken); + + decryptedDataOriginal.oauthTokenData = oauthTokenJson; + + const credentials = new Credentials(result.name, result.type, result.nodesAccess); + credentials.setData(decryptedDataOriginal, encryptionKey); + const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb; + // Add special database related data + newCredentialsData.updatedAt = this.getCurrentDate(); + // Save the credentials in DB + await Db.collections.Credentials!.update(cid as any, newCredentialsData); + + res.sendFile(pathResolve(__dirname, '../../templates/oauth-callback.html')); + }); + // ---------------------------------------- // OAuth2-Credential/Auth diff --git a/packages/core/src/Interfaces.ts b/packages/core/src/Interfaces.ts index 4b0a5ed4518..5aa0e10a64e 100644 --- a/packages/core/src/Interfaces.ts +++ b/packages/core/src/Interfaces.ts @@ -18,7 +18,7 @@ import { } from 'n8n-workflow'; -import { OptionsWithUri } from 'request'; +import { OptionsWithUri, OptionsWithUrl } from 'request'; import * as requestPromise from 'request-promise-native'; interface Constructable { @@ -36,7 +36,8 @@ export interface IExecuteFunctions extends IExecuteFunctionsBase { helpers: { prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise; request: requestPromise.RequestPromiseAPI, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[]; }; } @@ -46,7 +47,8 @@ export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase { helpers: { prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise; request: requestPromise.RequestPromiseAPI, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any }; } @@ -55,7 +57,8 @@ export interface IPollFunctions extends IPollFunctionsBase { helpers: { prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise; request: requestPromise.RequestPromiseAPI, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[]; }; } @@ -70,7 +73,8 @@ export interface ITriggerFunctions extends ITriggerFunctionsBase { helpers: { prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise; request: requestPromise.RequestPromiseAPI, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[]; }; } @@ -94,7 +98,8 @@ export interface IUserSettings { export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase { helpers: { request?: requestPromise.RequestPromiseAPI, - requestOAuth?: (this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions) => Promise, // tslint:disable-line:no-any + requestOAuth2?: (this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions) => Promise, // tslint:disable-line:no-any + requestOAuth1?(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any }; } @@ -102,7 +107,8 @@ export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase { export interface IHookFunctions extends IHookFunctionsBase { helpers: { request: requestPromise.RequestPromiseAPI, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any }; } @@ -111,7 +117,8 @@ export interface IWebhookFunctions extends IWebhookFunctionsBase { helpers: { prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise; request: requestPromise.RequestPromiseAPI, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise, // tslint:disable-line:no-any returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[]; }; } diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index f11abba582b..f0d0d017512 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -36,15 +36,20 @@ import { WorkflowExecuteMode, } from 'n8n-workflow'; +import * as clientOAuth1 from 'oauth-1.0a'; +import { RequestOptions, Token } from 'oauth-1.0a'; import * as clientOAuth2 from 'client-oauth2'; import { get } from 'lodash'; import * as express from 'express'; import * as path from 'path'; -import { OptionsWithUri } from 'request'; +import { OptionsWithUrl, OptionsWithUri } from 'request'; import * as requestPromise from 'request-promise-native'; import { Magic, MAGIC_MIME_TYPE } from 'mmmagic'; +import { createHmac } from 'crypto'; + + const magic = new Magic(MAGIC_MIME_TYPE); @@ -116,7 +121,7 @@ export async function prepareBinaryData(binaryData: Buffer, filePath?: string, m * @param {IWorkflowExecuteAdditionalData} additionalData * @returns */ -export function requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, tokenType?: string, property?: string) { +export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, tokenType?: string, property?: string) { const credentials = this.getCredentials(credentialsType) as ICredentialDataDecryptedObject; if (credentials === undefined) { @@ -170,6 +175,63 @@ export function requestOAuth(this: IAllExecuteFunctions, credentialsType: string }); } +/* Makes a request using OAuth1 data for authentication +* +* @export +* @param {IAllExecuteFunctions} this +* @param {string} credentialsType +* @param {(OptionsWithUrl | requestPromise.RequestPromiseOptions)} requestOptionså +* @returns +*/ +export function requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions) { + const credentials = this.getCredentials(credentialsType) as ICredentialDataDecryptedObject; + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + if (credentials.oauthTokenData === undefined) { + throw new Error('OAuth credentials not connected!'); + } + + const oauth = new clientOAuth1({ + consumer: { + key: credentials.consumerKey as string, + secret: credentials.consumerSecret as string, + }, + signature_method: credentials.signatureMethod as string, + hash_function(base, key) { + const algorithm = (credentials.signatureMethod === 'HMAC-SHA1') ? 'sha1' : 'sha256'; + return createHmac(algorithm, key) + .update(base) + .digest('base64'); + }, + }); + + const oauthTokenData = credentials.oauthTokenData as IDataObject; + + const token: Token = { + key: oauthTokenData.oauth_token as string, + secret: oauthTokenData.oauth_token_secret as string, + }; + + const newRequestOptions = { + //@ts-ignore + url: requestOptions.url, + method: requestOptions.method, + data: requestOptions.body, + json: requestOptions.json, + }; + + //@ts-ignore + newRequestOptions.form = oauth.authorize(newRequestOptions as RequestOptions, token); + + return this.helpers.request!(newRequestOptions) + .catch(async (error: IResponseError) => { + // Unknown error so simply throw it + throw error; + }); +} /** @@ -462,8 +524,11 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio helpers: { prepareBinaryData, request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any - return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any + return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + }, + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise { // tslint:disable-line:no-any + return requestOAuth1.call(this, credentialsType, requestOptions); }, returnJsonArray, }, @@ -522,8 +587,11 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi helpers: { prepareBinaryData, request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any - return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any + return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + }, + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise { // tslint:disable-line:no-any + return requestOAuth1.call(this, credentialsType, requestOptions); }, returnJsonArray, }, @@ -615,8 +683,11 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx helpers: { prepareBinaryData, request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any - return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any + return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + }, + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise { // tslint:disable-line:no-any + return requestOAuth1.call(this, credentialsType, requestOptions); }, returnJsonArray, }, @@ -710,8 +781,11 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData: helpers: { prepareBinaryData, request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any - return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any + return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + }, + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise { // tslint:disable-line:no-any + return requestOAuth1.call(this, credentialsType, requestOptions); }, }, }; @@ -763,8 +837,11 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio }, helpers: { request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any - return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any + return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + }, + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise { // tslint:disable-line:no-any + return requestOAuth1.call(this, credentialsType, requestOptions); }, }, }; @@ -827,8 +904,11 @@ export function getExecuteHookFunctions(workflow: Workflow, node: INode, additio }, helpers: { request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any - return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any + return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + }, + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise { // tslint:disable-line:no-any + return requestOAuth1.call(this, credentialsType, requestOptions); }, }, }; @@ -918,8 +998,11 @@ export function getExecuteWebhookFunctions(workflow: Workflow, node: INode, addi helpers: { prepareBinaryData, request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any - return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any + return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); + }, + requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise { // tslint:disable-line:no-any + return requestOAuth1.call(this, credentialsType, requestOptions); }, returnJsonArray, }, diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index e0e63553593..63ea4223a9c 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -145,6 +145,7 @@ export interface IRestApi { deleteExecutions(sendData: IExecutionDeleteFilter): Promise; retryExecution(id: string, loadWorkflow?: boolean): Promise; getTimezones(): Promise; + oAuth1CredentialAuthorize(sendData: ICredentialsResponse): Promise; oAuth2CredentialAuthorize(sendData: ICredentialsResponse): Promise; oAuth2Callback(code: string, state: string): Promise; } diff --git a/packages/editor-ui/src/components/CredentialsInput.vue b/packages/editor-ui/src/components/CredentialsInput.vue index db9f643d521..ba8121ddbed 100644 --- a/packages/editor-ui/src/components/CredentialsInput.vue +++ b/packages/editor-ui/src/components/CredentialsInput.vue @@ -47,13 +47,13 @@ Not all required credential properties are filled - + Is connected - + Is NOT connected @@ -222,8 +222,11 @@ export default mixins( if (this.credentialTypeData.name === 'oAuth2Api') { return true; } + if (this.credentialTypeData.name === 'oAuth1Api') { + return true; + } const types = this.parentTypes(this.credentialTypeData.name); - return types.includes('oAuth2Api'); + return types.includes('oAuth2Api') || types.includes('oAuth1Api'); }, isOAuthConnected (): boolean { if (this.isOAuthType === false) { @@ -233,7 +236,9 @@ export default mixins( return this.credentialDataDynamic !== null && !!this.credentialDataDynamic.data!.oauthTokenData; }, oAuthCallbackUrl (): string { - return this.$store.getters.getWebhookBaseUrl + 'rest/oauth2-credential/callback'; + const types = this.parentTypes(this.credentialTypeData.name); + const oauthType = (this.credentialTypeData.name === 'oAuth2Api' || types.includes('oAuth2Api')) ? 'oauth2' : 'oauth1'; + return this.$store.getters.getWebhookBaseUrl + `rest/${oauthType}-credential/callback`; }, requiredPropertiesFilled (): boolean { for (const property of this.credentialProperties) { @@ -323,7 +328,7 @@ export default mixins( return result; }, - async oAuth2CredentialAuthorize () { + async oAuthCredentialAuthorize () { let url; let credentialData = this.credentialDataDynamic; @@ -350,8 +355,14 @@ export default mixins( } } + const types = this.parentTypes(this.credentialTypeData.name); + try { - url = await this.restApi().oAuth2CredentialAuthorize(credentialData as ICredentialsResponse) as string; + if (this.credentialTypeData.name === 'oAuth2Api' || types.includes('oAuth2Api')) { + url = await this.restApi().oAuth2CredentialAuthorize(credentialData as ICredentialsResponse) as string; + } else if (this.credentialTypeData.name === 'oAuth1Api' || types.includes('oAuth1Api')) { + url = await this.restApi().oAuth1CredentialAuthorize(credentialData as ICredentialsResponse) as string; + } } catch (error) { this.$showError(error, 'OAuth Authorization Error', 'Error generating authorization URL:'); return; diff --git a/packages/editor-ui/src/components/mixins/restApi.ts b/packages/editor-ui/src/components/mixins/restApi.ts index be114786a9a..bca3fff338a 100644 --- a/packages/editor-ui/src/components/mixins/restApi.ts +++ b/packages/editor-ui/src/components/mixins/restApi.ts @@ -252,6 +252,11 @@ export const restApi = Vue.extend({ return self.restApi().makeRestApiRequest('GET', `/credential-types`); }, + // Get OAuth1 Authorization URL using the stored credentials + oAuth1CredentialAuthorize: (sendData: ICredentialsResponse): Promise => { + return self.restApi().makeRestApiRequest('GET', `/oauth1-credential/auth`, sendData); + }, + // Get OAuth2 Authorization URL using the stored credentials oAuth2CredentialAuthorize: (sendData: ICredentialsResponse): Promise => { return self.restApi().makeRestApiRequest('GET', `/oauth2-credential/auth`, sendData); diff --git a/packages/nodes-base/credentials/OAuth1Api.credentials.ts b/packages/nodes-base/credentials/OAuth1Api.credentials.ts new file mode 100644 index 00000000000..d5c18445f07 --- /dev/null +++ b/packages/nodes-base/credentials/OAuth1Api.credentials.ts @@ -0,0 +1,63 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class OAuth1Api implements ICredentialType { + name = 'oAuth1Api'; + displayName = 'OAuth1 API'; + properties = [ + { + displayName: 'Consumer Key', + name: 'consumerKey', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + }, + { + displayName: 'Consumer Secret', + name: 'consumerSecret', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + }, + { + displayName: 'Request Token URL', + name: 'requestTokenUrl', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + }, + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + }, + { + displayName: 'Signature Method', + name: 'signatureMethod', + type: 'options' as NodePropertyTypes, + options: [ + { + name: 'HMAC-SHA1', + value: 'HMAC-SHA1' + }, + { + name: 'HMAC-SHA256', + value: 'HMAC-SHA256' + }, + ], + default: '', + required: true, + }, + ]; +} diff --git a/packages/nodes-base/credentials/TwitterOAuth1Api.credentials.ts b/packages/nodes-base/credentials/TwitterOAuth1Api.credentials.ts new file mode 100644 index 00000000000..60c1d70064b --- /dev/null +++ b/packages/nodes-base/credentials/TwitterOAuth1Api.credentials.ts @@ -0,0 +1,38 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class TwitterOAuth1Api implements ICredentialType { + name = 'twitterOAuth1Api'; + extends = [ + 'oAuth1Api', + ]; + displayName = 'Twitter OAuth API'; + properties = [ + { + displayName: 'Request Token URL', + name: 'requestTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.twitter.com/oauth/request_token', + }, + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.twitter.com/oauth/authorize', + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.twitter.com/oauth/access_token', + }, + { + displayName: 'Signature Method', + name: 'signatureMethod', + type: 'hidden' as NodePropertyTypes, + default: 'HMAC-SHA1', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Github/GenericFunctions.ts b/packages/nodes-base/nodes/Github/GenericFunctions.ts index f6c5d076c64..592367019e7 100644 --- a/packages/nodes-base/nodes/Github/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Github/GenericFunctions.ts @@ -51,7 +51,7 @@ export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, const baseUrl = credentials!.server || 'https://api.github.com'; options.uri = `${baseUrl}${endpoint}`; - return await this.helpers.requestOAuth.call(this, 'githubOAuth2Api', options); + return await this.helpers.requestOAuth2.call(this, 'githubOAuth2Api', options); } } catch (error) { if (error.statusCode === 401) { diff --git a/packages/nodes-base/nodes/Google/GenericFunctions.ts b/packages/nodes-base/nodes/Google/GenericFunctions.ts index 80edb443701..b8c18429efb 100644 --- a/packages/nodes-base/nodes/Google/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/GenericFunctions.ts @@ -27,7 +27,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF delete options.body; } //@ts-ignore - return await this.helpers.requestOAuth.call(this, 'googleOAuth2Api', options); + return await this.helpers.requestOAuth2.call(this, 'googleOAuth2Api', options); } catch (error) { if (error.response && error.response.body && error.response.body.message) { // Try to return the error prettier diff --git a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts index 76b64ed3e73..a43545a32ee 100644 --- a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts +++ b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts @@ -32,7 +32,7 @@ export async function helpscoutApiRequest(this: IExecuteFunctions | IExecuteSing delete options.body; } //@ts-ignore - return await this.helpers.requestOAuth.call(this, 'helpScoutOAuth2Api', options); + return await this.helpers.requestOAuth2.call(this, 'helpScoutOAuth2Api', options); } catch (error) { if (error.response && error.response.body && error.response.body._embedded diff --git a/packages/nodes-base/nodes/HttpRequest.node.ts b/packages/nodes-base/nodes/HttpRequest.node.ts index 990c007e074..499575a7284 100644 --- a/packages/nodes-base/nodes/HttpRequest.node.ts +++ b/packages/nodes-base/nodes/HttpRequest.node.ts @@ -801,7 +801,7 @@ export class HttpRequest implements INodeType { // Now that the options are all set make the actual http request if (oAuth2Api !== undefined) { - response = await this.helpers.requestOAuth.call(this, 'oAuth2Api', requestOptions); + response = await this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions); } else { response = await this.helpers.request(requestOptions); } diff --git a/packages/nodes-base/nodes/Keap/GenericFunctions.ts b/packages/nodes-base/nodes/Keap/GenericFunctions.ts index c04fb057d57..d00228fadc1 100644 --- a/packages/nodes-base/nodes/Keap/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Keap/GenericFunctions.ts @@ -37,7 +37,7 @@ export async function keapApiRequest(this: IWebhookFunctions | IHookFunctions | delete options.body; } //@ts-ignore - return await this.helpers.requestOAuth.call(this, 'keapOAuth2Api', options); + return await this.helpers.requestOAuth2.call(this, 'keapOAuth2Api', options); } catch (error) { if (error.response && error.response.body && error.response.body.message) { // Try to return the error prettier diff --git a/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts index 33090090b35..4180b4336b0 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts @@ -24,7 +24,7 @@ export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSing options.headers = Object.assign({}, options.headers, headers); } //@ts-ignore - return await this.helpers.requestOAuth.call(this, 'microsoftExcelOAuth2Api', options); + return await this.helpers.requestOAuth2.call(this, 'microsoftExcelOAuth2Api', options); } catch (error) { if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { // Try to return the error prettier diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts index 1bd1bee202f..7040bb053ea 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts @@ -35,7 +35,7 @@ export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSing } //@ts-ignore - return await this.helpers.requestOAuth.call(this, 'microsoftOneDriveOAuth2Api', options); + return await this.helpers.requestOAuth2.call(this, 'microsoftOneDriveOAuth2Api', options); } catch (error) { if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { // Try to return the error prettier diff --git a/packages/nodes-base/nodes/OAuth.node.ts b/packages/nodes-base/nodes/OAuth.node.ts index 4f5ff5debb0..685b07cc406 100644 --- a/packages/nodes-base/nodes/OAuth.node.ts +++ b/packages/nodes-base/nodes/OAuth.node.ts @@ -138,7 +138,7 @@ export class OAuth implements INodeType { json: true, }; - const responseData = await this.helpers.requestOAuth.call(this, 'oAuth2Api', requestOptions); + const responseData = await this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions); return [this.helpers.returnJsonArray(responseData)]; } else { throw new Error('Unknown operation'); diff --git a/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts b/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts index c8cd97d9fab..648735feb2b 100644 --- a/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts @@ -20,7 +20,7 @@ export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSin }; try { //@ts-ignore - return await this.helpers.requestOAuth.call(this, 'salesforceOAuth2Api', options); + return await this.helpers.requestOAuth2.call(this, 'salesforceOAuth2Api', options); } catch (error) { if (error.response && error.response.body && error.response.body[0] && error.response.body[0].message) { // Try to return the error prettier diff --git a/packages/nodes-base/nodes/Slack/GenericFunctions.ts b/packages/nodes-base/nodes/Slack/GenericFunctions.ts index 7d65e1a93a3..ad04536b36f 100644 --- a/packages/nodes-base/nodes/Slack/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Slack/GenericFunctions.ts @@ -43,7 +43,7 @@ export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFu return await this.helpers.request(options); } else { //@ts-ignore - return await this.helpers.requestOAuth.call(this, 'slackOAuth2Api', options, 'bearer', 'authed_user.access_token'); + return await this.helpers.requestOAuth2.call(this, 'slackOAuth2Api', options, 'bearer', 'authed_user.access_token'); } } catch (error) { if (error.statusCode === 401) { diff --git a/packages/nodes-base/nodes/Twitter/GenericFunctions.ts b/packages/nodes-base/nodes/Twitter/GenericFunctions.ts new file mode 100644 index 00000000000..c47f63cb172 --- /dev/null +++ b/packages/nodes-base/nodes/Twitter/GenericFunctions.ts @@ -0,0 +1,44 @@ +import { + OptionsWithUrl, + } from 'request'; + +import { + IHookFunctions, + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function twitterApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + let options: OptionsWithUrl = { + method, + body, + qs, + url: uri || `https://api.twitter.com/1.1${resource}`, + json: true + }; + try { + if (Object.keys(option).length !== 0) { + options = Object.assign({}, options, option); + } + if (Object.keys(body).length === 0) { + delete options.body; + } + //@ts-ignore + return await this.helpers.requestOAuth1.call(this, 'twitterOAuth1Api', options); + } catch (error) { + if (error.response && error.response.body && error.response.body.errors) { + // Try to return the error prettier + const errorMessages = error.response.body.errors.map((error: IDataObject) => { + return error.message; + }); + throw new Error(`Twitter error response [${error.statusCode}]: ${errorMessages.join(' | ')}`); + } + + throw error; + } +} diff --git a/packages/nodes-base/nodes/Twitter/TweetDescription.ts b/packages/nodes-base/nodes/Twitter/TweetDescription.ts new file mode 100644 index 00000000000..5b97406b403 --- /dev/null +++ b/packages/nodes-base/nodes/Twitter/TweetDescription.ts @@ -0,0 +1,208 @@ +import { + INodeProperties, + } from 'n8n-workflow'; + +export const tweetOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'tweet', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new tweet', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const tweetFields = [ +/* -------------------------------------------------------------------------- */ +/* tweet:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Text', + name: 'text', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: true, + default: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'tweet', + ], + }, + }, + description: 'The text of the status update. URL encode as necessary. t.co link wrapping will affect character counts. ', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'tweet', + ], + }, + }, + options: [ + { + displayName: 'Attachments', + name: 'attachmentsUi', + placeholder: 'Add Attachments', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + options: [ + { + name: 'attachmentsValues', + displayName: 'Attachments Values', + values: [ + { + displayName: 'Data', + name: 'data', + type: 'string', + default: '', + description: 'The base64-encoded file content being uploaded.', + }, + { + displayName: 'Category', + name: 'category', + type: 'options', + options: [ + { + name: 'Amplify Video', + value: 'amplifyVideo', + }, + { + name: 'Gif', + value: 'tweetGif', + }, + { + name: 'Image', + value: 'tweetImage', + }, + { + name: 'Video', + value: 'tweetVideo', + }, + ], + default: '', + description: 'The category that represents how the media will be used', + }, + ], + }, + { + name: 'attachmentsBinary', + displayName: 'Attachments Binary', + values: [ + { + displayName: 'Property', + name: 'property', + type: 'string', + default: '', + description: 'Name of the binary properties which contain data which should be added to email as attachment', + }, + { + displayName: 'Category', + name: 'category', + type: 'options', + options: [ + { + name: 'Amplify Video', + value: 'amplifyVideo', + }, + { + name: 'Gif', + value: 'tweetGif', + }, + { + name: 'Image', + value: 'tweetImage', + }, + { + name: 'Video', + value: 'tweetVideo', + }, + ], + default: '', + description: 'The category that represents how the media will be used', + }, + ], + }, + ], + default: '', + description: 'Array of supported attachments to add to the message.', + }, + { + displayName: 'Display Coordinates', + name: 'displayCoordinates', + type: 'boolean', + default: false, + description: 'Whether or not to put a pin on the exact coordinates a Tweet has been sent from.', + }, + { + displayName: 'Location', + name: 'locationFieldsUi', + type: 'fixedCollection', + placeholder: 'Add Location', + default: {}, + description: `Subscriber location information.n`, + options: [ + { + name: 'locationFieldsValues', + displayName: 'Location', + values: [ + { + displayName: 'Latitude', + name: 'latitude', + type: 'string', + required: true, + description: 'The location latitude.', + default: '', + }, + { + displayName: 'Longitude', + name: 'longitude', + type: 'string', + required: true, + description: 'The location longitude.', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Possibly Sensitive', + name: 'possiblySensitive', + type: 'boolean', + default: false, + description: 'If you upload Tweet media that might be considered sensitive content such as nudity, or medical procedures, you must set this value to true.', + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Twitter/TweetInterface.ts b/packages/nodes-base/nodes/Twitter/TweetInterface.ts new file mode 100644 index 00000000000..10b16d6a2dd --- /dev/null +++ b/packages/nodes-base/nodes/Twitter/TweetInterface.ts @@ -0,0 +1,8 @@ +export interface ITweet { + display_coordinates?: boolean; + lat?: number; + long?: number; + media_ids?: string; + possibly_sensitive?: boolean; + status: string; +} diff --git a/packages/nodes-base/nodes/Twitter/Twitter.node.ts b/packages/nodes-base/nodes/Twitter/Twitter.node.ts new file mode 100644 index 00000000000..6e173736740 --- /dev/null +++ b/packages/nodes-base/nodes/Twitter/Twitter.node.ts @@ -0,0 +1,151 @@ + +import { + IExecuteFunctions, + } from 'n8n-core'; + +import { + IBinaryKeyData, + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + tweetFields, + tweetOperations, +} from './TweetDescription'; + +import { + twitterApiRequest, +} from './GenericFunctions'; + +import { + ITweet, + } from './TweetInterface'; + + import { + snakeCase, + } from 'change-case'; + +export class Twitter implements INodeType { + description: INodeTypeDescription = { + displayName: 'Twitter ', + name: 'twitter', + icon: 'file:twitter.png', + group: ['input', 'output'], + version: 1, + description: 'Consume Twitter API', + subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}', + defaults: { + name: 'Twitter', + color: '#1DA1F2', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'twitterOAuth1Api', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Tweet', + value: 'tweet', + }, + ], + default: 'tweet', + description: 'The resource to operate on.', + }, + // TWEET + ...tweetOperations, + ...tweetFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'tweet') { + //https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update + if (operation === 'create') { + const text = this.getNodeParameter('text', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: ITweet = { + status: text, + }; + if (additionalFields.attachmentsUi) { + const mediaIds = []; + const attachmentsUi = additionalFields.attachmentsUi as IDataObject; + const uploadUri = 'https://upload.twitter.com/1.1/media/upload.json'; + if (attachmentsUi.attachmentsValues) { + const attachtments = attachmentsUi.attachmentsValues as IDataObject[]; + for (const attachment of attachtments) { + const body = { + media_data: attachment.data, + media_category: snakeCase(attachment.category as string).toUpperCase(), + }; + const response = await twitterApiRequest.call(this, 'POST', '', body, {}, uploadUri); + mediaIds.push(response.media_id); + } + } + if (attachmentsUi.attachmentsBinary) { + const attachtments = attachmentsUi.attachmentsBinary as IDataObject[]; + for (const attachment of attachtments) { + + const binaryData = items[i].binary as IBinaryKeyData; + const propertyName = attachment.property as string; + + if (binaryData === undefined) { + throw new Error('No binary data set. So file can not be written!'); + } + + if (!binaryData[propertyName]) { + continue; + } + + const body = { + media_data: binaryData[propertyName].data, + media_category: snakeCase(attachment.category as string).toUpperCase(), + }; + const response = await twitterApiRequest.call(this, 'POST', '', body, {}, uploadUri); + mediaIds.push(response.media_id_string); + } + } + body.media_ids = mediaIds.join(','); + } + if (additionalFields.possiblySensitive) { + body.possibly_sensitive = additionalFields.possibly_sensitive as boolean; + } + if (additionalFields.locationFieldsUi) { + const locationUi = additionalFields.locationFieldsUi as IDataObject; + if (locationUi.locationFieldsValues) { + const values = locationUi.locationFieldsValues as IDataObject; + body.lat = parseFloat(values.lalatitude as string); + body.long = parseFloat(values.lalatitude as string); + } + } + responseData = await twitterApiRequest.call(this, 'POST', '/statuses/update.json', body); + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Twitter/twitter.png b/packages/nodes-base/nodes/Twitter/twitter.png new file mode 100644 index 0000000000000000000000000000000000000000..6f4436e441476dcf1526d32c50d0fa8cb1990ed0 GIT binary patch literal 1490 zcmZ`(dpOez82;G|Db_q}EaXx{V~r85A=(HHm1|U+%Y@3Nu|+hOgPBXwkW0#rmt{bU@dASpTpX+tCtFBnVs4_jsB-IZpN-*u+$kycrg@20&(Z%n?gk&rv zN9?ctSoYE(Mi4w-rc-dy(H!PF+NJvCTIsfdz76QyOpF_|?%BRH<)e8MHKI(?W}V^u zyw-|Hd$5<5v3aHEvse07LhFhU=)!pdj{TzY=k8f&gRMWDb<3VPC4Xl%ca0oNwu+w_ z;G+X?g?7Z}=anP7fWaaEmDBpPuwCO=xJc@_i@HbUj*s5y0@S9x^^9IH!i;-Oa?=#@ zCjARt=Y=cUO#|5mtzzq-WxYyQ0~2iqmBZ;ttsYy&U;^xn%;|?kho~*%p{-@_jBfQ* zaRnVZpd3Q@kXlsqB%ax;AuI^>;kUe;w;Es zQ1=!&3^=E6xxIyzYbp*zFrPab7E(P@M6A8B{T5@nw6Ey*OM)E-Y?RIvEtySI-*S4h zb8+^0m{C?RgTEj4O&pS3BW)|$#A&DE7U3=n z&`@sop$v2|o5>~XWxtUUy!Rc}7;)08qXAyZU9rT$m`4UCEqq(z_Q{md5Srj4FLo;N zfOx3Zge_aDo6O1MxO0PEb8A;x^iLuu9iI|sn3WDwag*wIu7hS*@+ceSHmg4yvomU2 zq02dQtRZZ8IpeNF&GG5&oi_!IxYcv@kcjsphy>RDc$QD`C}lr?xW~bG1|%mLt%71( z44rc+uZr`63q&@ciTpFSjmgrc2 zb8(F2x)-y%ZO8h!rKa0yjv*aW%fr(ajE zk^YOu)b|_r$BQ)YrFa^*Pgq__Kc$f=fr+1%dk40;Cz)P>yEhz6LKxhrEeU|o4>)n$ z`%Pnjb$uzM=baKvN(YP7>DExY@nR@6ZVo zh!&zjue+awrBP6ro=)X+Dh1DV6Zz)-ZdYT}q`vf;WA6(7BH7{4UD>guqfMAmd0O_7 zRs2EO%Z56Z zGWQNEK_O=NR3W2)O{~lb?_=zoHT>H|O#-jVj??N%nCcj7jI-nkV^B1uP-JSJ^wZrB z3qeCv-HNp?pCAcqE%&%~>=VW+Uy16ltQu<$VvNwG!Cia6WOBn2@esLV)wczmP+ z02{0g)(X4J3Ty3WZI8FX;_Y^!u~ Date: Mon, 1 Jun 2020 20:51:26 -0400 Subject: [PATCH 005/120] :zap: small fix --- packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts index b772a47d0c7..18d808cef79 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts @@ -31,7 +31,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF delete options.body; } //@ts-ignore - return await this.helpers.requestOAuth.call(this, 'googleCalendarOAuth2Api', options); + return await this.helpers.requestOAuth2.call(this, 'googleCalendarOAuth2Api', options); } catch (error) { if (error.response && error.response.body && error.response.body.message) { // Try to return the error prettier From e3a6064196e7d9c7b87f8f6384e740ad03e1e943 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 3 Jun 2020 15:06:13 +0200 Subject: [PATCH 006/120] :zap: Small changes to Twitter-Node --- packages/cli/src/Server.ts | 4 +- .../src/components/CredentialsInput.vue | 7 +-- .../nodes/Twitter/GenericFunctions.ts | 2 +- .../nodes/Twitter/TweetDescription.ts | 50 +++---------------- .../nodes-base/nodes/Twitter/Twitter.node.ts | 44 +++++++--------- 5 files changed, 30 insertions(+), 77 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 88e60d4c521..1d7f1506dec 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -988,7 +988,7 @@ class App { throw new Error('Insufficient parameters for OAuth1 callback'); } - const result = await Db.collections.Credentials!.findOne(cid as any); + const result = await Db.collections.Credentials!.findOne(cid as any); // tslint:disable-line:no-any if (result === undefined) { const errorResponse = new ResponseHelper.ResponseError('The credential is not known.', undefined, 404); return ResponseHelper.sendErrorResponse(res, errorResponse); @@ -1041,7 +1041,7 @@ class App { // Add special database related data newCredentialsData.updatedAt = this.getCurrentDate(); // Save the credentials in DB - await Db.collections.Credentials!.update(cid as any, newCredentialsData); + await Db.collections.Credentials!.update(cid as any, newCredentialsData); // tslint:disable-line:no-any res.sendFile(pathResolve(__dirname, '../../templates/oauth-callback.html')); }); diff --git a/packages/editor-ui/src/components/CredentialsInput.vue b/packages/editor-ui/src/components/CredentialsInput.vue index ba8121ddbed..3d8edec3ecf 100644 --- a/packages/editor-ui/src/components/CredentialsInput.vue +++ b/packages/editor-ui/src/components/CredentialsInput.vue @@ -219,14 +219,11 @@ export default mixins( return this.credentialDataTemp; }, isOAuthType (): boolean { - if (this.credentialTypeData.name === 'oAuth2Api') { - return true; - } - if (this.credentialTypeData.name === 'oAuth1Api') { + if (['oAuth1Api', 'oAuth2Api'].includes(this.credentialTypeData.name)) { return true; } const types = this.parentTypes(this.credentialTypeData.name); - return types.includes('oAuth2Api') || types.includes('oAuth1Api'); + return types.includes('oAuth1Api') || types.includes('oAuth2Api'); }, isOAuthConnected (): boolean { if (this.isOAuthType === false) { diff --git a/packages/nodes-base/nodes/Twitter/GenericFunctions.ts b/packages/nodes-base/nodes/Twitter/GenericFunctions.ts index c47f63cb172..d13ea1c5487 100644 --- a/packages/nodes-base/nodes/Twitter/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Twitter/GenericFunctions.ts @@ -1,6 +1,6 @@ import { OptionsWithUrl, - } from 'request'; +} from 'request'; import { IHookFunctions, diff --git a/packages/nodes-base/nodes/Twitter/TweetDescription.ts b/packages/nodes-base/nodes/Twitter/TweetDescription.ts index 5b97406b403..46e1760e6fa 100644 --- a/packages/nodes-base/nodes/Twitter/TweetDescription.ts +++ b/packages/nodes-base/nodes/Twitter/TweetDescription.ts @@ -1,6 +1,6 @@ import { INodeProperties, - } from 'n8n-workflow'; +} from 'n8n-workflow'; export const tweetOperations = [ { @@ -78,53 +78,15 @@ export const tweetFields = [ }, options: [ { - name: 'attachmentsValues', - displayName: 'Attachments Values', + name: 'attachment', + displayName: 'Attachment', values: [ { - displayName: 'Data', - name: 'data', + displayName: 'Binary Property', + name: 'binaryPropertyName', type: 'string', default: '', - description: 'The base64-encoded file content being uploaded.', - }, - { - displayName: 'Category', - name: 'category', - type: 'options', - options: [ - { - name: 'Amplify Video', - value: 'amplifyVideo', - }, - { - name: 'Gif', - value: 'tweetGif', - }, - { - name: 'Image', - value: 'tweetImage', - }, - { - name: 'Video', - value: 'tweetVideo', - }, - ], - default: '', - description: 'The category that represents how the media will be used', - }, - ], - }, - { - name: 'attachmentsBinary', - displayName: 'Attachments Binary', - values: [ - { - displayName: 'Property', - name: 'property', - type: 'string', - default: '', - description: 'Name of the binary properties which contain data which should be added to email as attachment', + description: 'Name of the binary properties which contain data which should be added to tweet as attachment', }, { displayName: 'Category', diff --git a/packages/nodes-base/nodes/Twitter/Twitter.node.ts b/packages/nodes-base/nodes/Twitter/Twitter.node.ts index 6e173736740..09b1dcde933 100644 --- a/packages/nodes-base/nodes/Twitter/Twitter.node.ts +++ b/packages/nodes-base/nodes/Twitter/Twitter.node.ts @@ -1,7 +1,7 @@ import { IExecuteFunctions, - } from 'n8n-core'; +} from 'n8n-core'; import { IBinaryKeyData, @@ -22,11 +22,11 @@ import { import { ITweet, - } from './TweetInterface'; +} from './TweetInterface'; - import { - snakeCase, - } from 'change-case'; +import { + snakeCase, +} from 'change-case'; export class Twitter implements INodeType { description: INodeTypeDescription = { @@ -73,62 +73,55 @@ export class Twitter implements INodeType { const items = this.getInputData(); const returnData: IDataObject[] = []; const length = items.length as unknown as number; - const qs: IDataObject = {}; let responseData; const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; for (let i = 0; i < length; i++) { if (resource === 'tweet') { - //https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update + // https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update if (operation === 'create') { const text = this.getNodeParameter('text', i) as string; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const body: ITweet = { status: text, }; + if (additionalFields.attachmentsUi) { const mediaIds = []; const attachmentsUi = additionalFields.attachmentsUi as IDataObject; const uploadUri = 'https://upload.twitter.com/1.1/media/upload.json'; - if (attachmentsUi.attachmentsValues) { - const attachtments = attachmentsUi.attachmentsValues as IDataObject[]; - for (const attachment of attachtments) { - const body = { - media_data: attachment.data, - media_category: snakeCase(attachment.category as string).toUpperCase(), - }; - const response = await twitterApiRequest.call(this, 'POST', '', body, {}, uploadUri); - mediaIds.push(response.media_id); - } - } - if (attachmentsUi.attachmentsBinary) { - const attachtments = attachmentsUi.attachmentsBinary as IDataObject[]; + + if (attachmentsUi.attachment) { + const attachtments = attachmentsUi.attachment as IDataObject[]; for (const attachment of attachtments) { const binaryData = items[i].binary as IBinaryKeyData; - const propertyName = attachment.property as string; + const binaryPropertyName = attachment.binaryPropertyName as string; if (binaryData === undefined) { throw new Error('No binary data set. So file can not be written!'); } - if (!binaryData[propertyName]) { + if (!binaryData[binaryPropertyName]) { continue; } - const body = { - media_data: binaryData[propertyName].data, + const attachmentBody = { + media_data: binaryData[binaryPropertyName].data, media_category: snakeCase(attachment.category as string).toUpperCase(), }; - const response = await twitterApiRequest.call(this, 'POST', '', body, {}, uploadUri); + const response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); mediaIds.push(response.media_id_string); } } + body.media_ids = mediaIds.join(','); } + if (additionalFields.possiblySensitive) { body.possibly_sensitive = additionalFields.possibly_sensitive as boolean; } + if (additionalFields.locationFieldsUi) { const locationUi = additionalFields.locationFieldsUi as IDataObject; if (locationUi.locationFieldsValues) { @@ -137,6 +130,7 @@ export class Twitter implements INodeType { body.long = parseFloat(values.lalatitude as string); } } + responseData = await twitterApiRequest.call(this, 'POST', '/statuses/update.json', body); } } From 49ea7e27390a1f96ee4ffddabfeb070cd7008692 Mon Sep 17 00:00:00 2001 From: Patrick Wellever <239789+pwellever@users.noreply.github.com> Date: Thu, 4 Jun 2020 03:36:10 -0400 Subject: [PATCH 007/120] :sparkles: Make server listen address configurable (#618) --- docs/configuration.md | 3 +++ docs/server-setup.md | 1 + packages/cli/config/index.ts | 6 ++++++ packages/cli/src/Server.ts | 5 +++-- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index ce57ef92dfb..63b8c95f12b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -14,6 +14,9 @@ Sets how n8n should be made available. # The port n8n should be made available on N8N_PORT=5678 +# The IP address n8n should listen on +N8N_LISTEN_ADDRESS=0.0.0.0 + # This ones are currently only important for the webhook URL creation. # So if "WEBHOOK_TUNNEL_URL" got set they do get ignored. It is however # encouraged to set them correctly anyway in case they will become diff --git a/docs/server-setup.md b/docs/server-setup.md index f2c830c48f3..d34d076a6f0 100644 --- a/docs/server-setup.md +++ b/docs/server-setup.md @@ -105,6 +105,7 @@ services: - N8N_BASIC_AUTH_PASSWORD - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME} - N8N_PORT=5678 + - N8N_LISTEN_ADDRESS=0.0.0.0 - N8N_PROTOCOL=https - NODE_ENV=production - WEBHOOK_TUNNEL_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/ diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts index 69b31540642..a8b18862771 100644 --- a/packages/cli/config/index.ts +++ b/packages/cli/config/index.ts @@ -169,6 +169,12 @@ const config = convict({ env: 'N8N_PORT', doc: 'HTTP port n8n can be reached' }, + listen_address: { + format: String, + default: '0.0.0.0', + env: 'N8N_LISTEN_ADDRESS', + doc: 'IP address n8n should listen on' + }, protocol: { format: ['http', 'https'], default: 'http', diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 02b4afb29e2..a5beea41e23 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -1294,6 +1294,7 @@ class App { export async function start(): Promise { const PORT = config.get('port'); + const ADDRESS = config.get('listen_address'); const app = new App(); @@ -1312,9 +1313,9 @@ export async function start(): Promise { server = http.createServer(app.app); } - server.listen(PORT, async () => { + server.listen(PORT, ADDRESS, async () => { const versions = await GenericHelpers.getVersions(); - console.log(`n8n ready on port ${PORT}`); + console.log(`n8n ready on ${ADDRESS}, port ${PORT}`); console.log(`Version: ${versions.cli}`); }); } From 0ab6a70fa10746ff3f84285ff490e170c08d6d9b Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Thu, 4 Jun 2020 15:54:39 +0200 Subject: [PATCH 008/120] 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 00000000000..0a0efc70914 --- /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 91dfcdde859..3d66c2d8ab6 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 cb54ac24e93..3b8d17c3023 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 25eac04c202..67e07ad6bde 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 31cb4a8da58..f1ce8536a6f 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 c38385342357d619466b63c2e59944f1ebdcd90e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 16:55:24 +0200 Subject: [PATCH 009/120] :zap: Small improvement to Twitter-Node --- packages/nodes-base/nodes/Twitter/TweetDescription.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Twitter/TweetDescription.ts b/packages/nodes-base/nodes/Twitter/TweetDescription.ts index 46e1760e6fa..6434843bea5 100644 --- a/packages/nodes-base/nodes/Twitter/TweetDescription.ts +++ b/packages/nodes-base/nodes/Twitter/TweetDescription.ts @@ -85,7 +85,7 @@ export const tweetFields = [ displayName: 'Binary Property', name: 'binaryPropertyName', type: 'string', - default: '', + default: 'data', description: 'Name of the binary properties which contain data which should be added to tweet as attachment', }, { From 1c683fec699b27f35bc00494c3dc97051fd4dd78 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 16:58:45 +0200 Subject: [PATCH 010/120] :bug: Fix issue that ReadPdf does not lose binary data --- packages/nodes-base/nodes/ReadPdf.node.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nodes-base/nodes/ReadPdf.node.ts b/packages/nodes-base/nodes/ReadPdf.node.ts index 52f149da97a..2d41583c874 100644 --- a/packages/nodes-base/nodes/ReadPdf.node.ts +++ b/packages/nodes-base/nodes/ReadPdf.node.ts @@ -51,6 +51,7 @@ export class ReadPdf implements INodeType { const binaryData = Buffer.from(item.binary[binaryPropertyName].data, BINARY_ENCODING); return { + binary: item.binary, json: await pdf(binaryData) }; } From 20fdd70a9bdb2ec83f19cb51bbfda163032b048d Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:02:27 +0200 Subject: [PATCH 011/120] :books: Add version to breaking-changes file --- packages/cli/BREAKING-CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/BREAKING-CHANGES.md b/packages/cli/BREAKING-CHANGES.md index e5601334803..0af22307f35 100644 --- a/packages/cli/BREAKING-CHANGES.md +++ b/packages/cli/BREAKING-CHANGES.md @@ -3,7 +3,7 @@ This list shows all the versions which include breaking changes and how to upgrade. -## ??? +## 0.68.0 ### What changed? From 93c6107ef96969705a13f5a7c818be9266893e0f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:05:54 +0200 Subject: [PATCH 012/120] :bookmark: Release n8n-workflow@0.32.0 --- packages/workflow/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflow/package.json b/packages/workflow/package.json index fb851ce40b4..969270199b1 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.31.0", + "version": "0.32.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 7e88c35e3597ba5a322540bb333bb83402d1c0f9 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:07:09 +0200 Subject: [PATCH 013/120] :arrow_up: Set n8n-workflow@0.32.0 on n8n-core --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index c0616f5aeb1..dd3fa99fc5f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -45,7 +45,7 @@ "crypto-js": "3.1.9-1", "lodash.get": "^4.4.2", "mmmagic": "^0.5.2", - "n8n-workflow": "~0.31.0", + "n8n-workflow": "~0.32.0", "p-cancelable": "^2.0.0", "request": "^2.88.2", "request-promise-native": "^1.0.7" From 0abbcba6ca9b27c510c6c8706037281e1209091e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:07:44 +0200 Subject: [PATCH 014/120] :bookmark: Release n8n-core@0.35.0 --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index dd3fa99fc5f..259ce7d4cb6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.34.0", + "version": "0.35.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From d2d3316efcc7977176d1536a90ff59f87b9ff92b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:08:55 +0200 Subject: [PATCH 015/120] :arrow_up: Set n8n-core@0.35.0 and n8n-workflow@0.32.0 on n8n-nodes-base --- packages/nodes-base/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 31cb4a8da58..e14468d7e48 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -304,7 +304,7 @@ "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^24.9.0", - "n8n-workflow": "~0.31.0", + "n8n-workflow": "~0.32.0", "ts-jest": "^24.0.2", "tslint": "^5.17.0", "typescript": "~3.7.4" @@ -329,7 +329,7 @@ "moment-timezone": "^0.5.28", "mongodb": "^3.5.5", "mysql2": "^2.0.1", - "n8n-core": "~0.34.0", + "n8n-core": "~0.35.0", "nodemailer": "^6.4.6", "pdf-parse": "^1.1.1", "pg-promise": "^9.0.3", From 14cd01557d21378e3a6283ddd4303a6155bf67a6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:09:21 +0200 Subject: [PATCH 016/120] :bookmark: Release n8n-nodes-base@0.63.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 e14468d7e48..21ac01808e9 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.62.1", + "version": "0.63.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 66c3013e56e7e647e302e44d4348f077c0e747e2 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:10:31 +0200 Subject: [PATCH 017/120] :arrow_up: Set n8n-workflow@0.32.0 on n8n-editor-ui --- packages/editor-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index ec505a172cb..7e1b0f104c9 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -64,7 +64,7 @@ "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "n8n-workflow": "~0.31.0", + "n8n-workflow": "~0.32.0", "node-sass": "^4.12.0", "prismjs": "^1.17.1", "quill": "^2.0.0-dev.3", From 42d6630466912456f96d11be1f2be32bd1642222 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:11:39 +0200 Subject: [PATCH 018/120] :bookmark: Release n8n-editor-ui@0.46.0 --- packages/editor-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 7e1b0f104c9..8543d2fdd06 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.45.0", + "version": "0.46.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 320a63a1be3f7fadeb1bc66063427b55e2ff0220 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:14:23 +0200 Subject: [PATCH 019/120] :arrow_up: Set n8n-core@0.35.0, n8n-editor-ui@0.46.0, n8n-nodes-base@0.63.0 and n8n-workflow@0.32.0 on n8n --- packages/cli/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 0b4e2fa9cb8..49d969d01c0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -99,10 +99,10 @@ "lodash.get": "^4.4.2", "mongodb": "^3.5.5", "mysql2": "^2.0.1", - "n8n-core": "~0.34.0", - "n8n-editor-ui": "~0.45.0", - "n8n-nodes-base": "~0.62.1", - "n8n-workflow": "~0.31.0", + "n8n-core": "~0.35.0", + "n8n-editor-ui": "~0.46.0", + "n8n-nodes-base": "~0.63.0", + "n8n-workflow": "~0.32.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", "pg": "^7.11.0", From c7a3a8679743c629ab6558dfe9839e1460fa6a80 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:14:57 +0200 Subject: [PATCH 020/120] :bookmark: Release n8n@0.68.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 49d969d01c0..1f2dbde4379 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.67.3", + "version": "0.68.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 75ea0ea91c153b9d2a73b3939f983945953baf96 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:30:43 +0200 Subject: [PATCH 021/120] :bug: Add missing file --- packages/cli/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/package.json b/packages/cli/package.json index 1f2dbde4379..537dde163e3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -47,6 +47,7 @@ }, "files": [ "bin", + "templates", "dist", "oclif.manifest.json" ], From f8f123efde4dc9f9665d0b15b078b2898ceebd29 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:31:21 +0200 Subject: [PATCH 022/120] :bookmark: Release n8n@0.68.1 --- 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 537dde163e3..2d1fee766f9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.68.0", + "version": "0.68.1", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 381caa5103c9734bddf52d3fce62ab3f89f6feb1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:46:43 +0200 Subject: [PATCH 023/120] :bug: Fix typo --- packages/nodes-base/nodes/Keap/Keap.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Keap/Keap.node.ts b/packages/nodes-base/nodes/Keap/Keap.node.ts index f67c07bbc4d..3ba98cda7fb 100644 --- a/packages/nodes-base/nodes/Keap/Keap.node.ts +++ b/packages/nodes-base/nodes/Keap/Keap.node.ts @@ -104,7 +104,7 @@ import * as moment from 'moment-timezone'; export class Keap implements INodeType { description: INodeTypeDescription = { displayName: 'Keap', - name: ' keap', + name: 'keap', icon: 'file:keap.png', group: ['input'], version: 1, From e0a8bf1bd5bb8b9bb1c1cca89540cd429bf6d546 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:47:19 +0200 Subject: [PATCH 024/120] :bookmark: Release n8n-nodes-base@0.63.1 --- 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 21ac01808e9..a8980586341 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.63.0", + "version": "0.63.1", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From a12510db094bce7ecf6d33237ef42e3d0f2d80f0 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:49:37 +0200 Subject: [PATCH 025/120] :arrow_up: Set n8n-nodes-base@0.63.1 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 2d1fee766f9..0bdd020d45f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -102,7 +102,7 @@ "mysql2": "^2.0.1", "n8n-core": "~0.35.0", "n8n-editor-ui": "~0.46.0", - "n8n-nodes-base": "~0.63.0", + "n8n-nodes-base": "~0.63.1", "n8n-workflow": "~0.32.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", From afe0d16a9a3a65c0e1e3b1d33f79dde091701752 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 4 Jun 2020 17:50:26 +0200 Subject: [PATCH 026/120] :bookmark: Release n8n@0.68.2 --- 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 0bdd020d45f..325a73d874d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.68.1", + "version": "0.68.2", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From d3829c90c238d7f5ae64c9dd135d235a69481b3d Mon Sep 17 00:00:00 2001 From: ricardo Date: Sat, 6 Jun 2020 14:57:42 -0400 Subject: [PATCH 027/120] :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 00000000000..77b0a9504c7 --- /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 00000000000..9946cd802a1 --- /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 387a3d7bc04..8ddfd7c1fa0 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 64ba9f9bec5..00000000000 --- 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 a8980586341..0f854564392 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 5e93b37c6e800856c3d43053fdbe16b9e335cbe4 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 7 Jun 2020 17:29:13 -0400 Subject: [PATCH 028/120] :zap: Improvements --- packages/core/src/NodeExecuteFunctions.ts | 11 +- .../nodes/Github/GenericFunctions.ts | 2 +- packages/nodes-base/nodes/HttpRequest.node.ts | 1 + packages/nodes-base/nodes/OAuth.node.ts | 2 +- .../nodes/Twitter/GenericFunctions.ts | 32 +++ .../nodes/Twitter/TweetDescription.ts | 248 ++++++++++++++++-- .../nodes-base/nodes/Twitter/Twitter.node.ts | 143 +++++++++- packages/nodes-base/package.json | 1 + 8 files changed, 402 insertions(+), 38 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index f0d0d017512..d500d56f88d 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -219,12 +219,17 @@ export function requestOAuth1(this: IAllExecuteFunctions, credentialsType: strin //@ts-ignore url: requestOptions.url, method: requestOptions.method, - data: requestOptions.body, + data: { ...requestOptions.qs, ...requestOptions.body }, json: requestOptions.json, }; - //@ts-ignore - newRequestOptions.form = oauth.authorize(newRequestOptions as RequestOptions, token); + if (Object.keys(requestOptions.qs).length !== 0) { + //@ts-ignore + newRequestOptions.qs = oauth.authorize(newRequestOptions as RequestOptions, token); + } else { + //@ts-ignore + newRequestOptions.form = oauth.authorize(newRequestOptions as RequestOptions, token); + } return this.helpers.request!(newRequestOptions) .catch(async (error: IResponseError) => { diff --git a/packages/nodes-base/nodes/Github/GenericFunctions.ts b/packages/nodes-base/nodes/Github/GenericFunctions.ts index 592367019e7..de20d34062c 100644 --- a/packages/nodes-base/nodes/Github/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Github/GenericFunctions.ts @@ -50,7 +50,7 @@ export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, const baseUrl = credentials!.server || 'https://api.github.com'; options.uri = `${baseUrl}${endpoint}`; - + //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'githubOAuth2Api', options); } } catch (error) { diff --git a/packages/nodes-base/nodes/HttpRequest.node.ts b/packages/nodes-base/nodes/HttpRequest.node.ts index 499575a7284..417d80eeea3 100644 --- a/packages/nodes-base/nodes/HttpRequest.node.ts +++ b/packages/nodes-base/nodes/HttpRequest.node.ts @@ -801,6 +801,7 @@ export class HttpRequest implements INodeType { // Now that the options are all set make the actual http request if (oAuth2Api !== undefined) { + //@ts-ignore response = await this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions); } else { response = await this.helpers.request(requestOptions); diff --git a/packages/nodes-base/nodes/OAuth.node.ts b/packages/nodes-base/nodes/OAuth.node.ts index 685b07cc406..3966571cd1b 100644 --- a/packages/nodes-base/nodes/OAuth.node.ts +++ b/packages/nodes-base/nodes/OAuth.node.ts @@ -137,7 +137,7 @@ export class OAuth implements INodeType { uri: url, json: true, }; - + //@ts-ignore const responseData = await this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions); return [this.helpers.returnJsonArray(responseData)]; } else { diff --git a/packages/nodes-base/nodes/Twitter/GenericFunctions.ts b/packages/nodes-base/nodes/Twitter/GenericFunctions.ts index d13ea1c5487..1b793047c35 100644 --- a/packages/nodes-base/nodes/Twitter/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Twitter/GenericFunctions.ts @@ -42,3 +42,35 @@ export async function twitterApiRequest(this: IExecuteFunctions | IExecuteSingle throw error; } } + +export async function twitterApiRequestAllItems(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.count = 100; + do { + responseData = await twitterApiRequest.call(this, method, endpoint, body, query); + query.since_id = responseData.search_metadata.max_id; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData.search_metadata && + responseData.search_metadata.next_results + ); + + return returnData; +} + +export function chunks (buffer: Buffer, chunkSize: number) { + const result = []; + const len = buffer.length; + let i = 0; + + while (i < len) { + result.push(buffer.slice(i, i += chunkSize)); + } + + return result; +} + + diff --git a/packages/nodes-base/nodes/Twitter/TweetDescription.ts b/packages/nodes-base/nodes/Twitter/TweetDescription.ts index 46e1760e6fa..54d3ccbfad6 100644 --- a/packages/nodes-base/nodes/Twitter/TweetDescription.ts +++ b/packages/nodes-base/nodes/Twitter/TweetDescription.ts @@ -20,6 +20,11 @@ export const tweetOperations = [ value: 'create', description: 'Create a new tweet', }, + { + name: 'Search', + value: 'search', + description: 'Search tweets', + }, ], default: 'create', description: 'The operation to perform.', @@ -88,31 +93,31 @@ export const tweetFields = [ default: '', description: 'Name of the binary properties which contain data which should be added to tweet as attachment', }, - { - displayName: 'Category', - name: 'category', - type: 'options', - options: [ - { - name: 'Amplify Video', - value: 'amplifyVideo', - }, - { - name: 'Gif', - value: 'tweetGif', - }, - { - name: 'Image', - value: 'tweetImage', - }, - { - name: 'Video', - value: 'tweetVideo', - }, - ], - default: '', - description: 'The category that represents how the media will be used', - }, + // { + // displayName: 'Category', + // name: 'category', + // type: 'options', + // options: [ + // { + // name: 'Amplify Video', + // value: 'amplifyVideo', + // }, + // { + // name: 'Gif', + // value: 'tweetGif', + // }, + // { + // name: 'Image', + // value: 'tweetImage', + // }, + // { + // name: 'Video', + // value: 'tweetVideo', + // }, + // ], + // default: '', + // description: 'The category that represents how the media will be used', + // }, ], }, ], @@ -167,4 +172,197 @@ export const tweetFields = [ }, ] }, +/* -------------------------------------------------------------------------- */ +/* tweet:search */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Search Text', + name: 'searchText', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: true, + default: '', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'tweet', + ], + }, + }, + description: `A UTF-8, URL-encoded search query of 500 characters maximum,
+ including operators. Queries may additionally be limited by complexity.
+ Check the searching examples here.`, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'tweet', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'tweet', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'How many results to return.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'tweet', + ], + }, + }, + options: [ + { + displayName: 'Include Entities', + name: 'includeEntities', + type: 'boolean', + default: false, + description: 'The entities node will not be included when set to false', + }, + { + displayName: 'Lang', + name: 'lang', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLanguages', + }, + default: '', + description: 'Restricts tweets to the given language, given by an ISO 639-1 code. Language detection is best-effort.', + }, + { + displayName: 'Location', + name: 'locationFieldsUi', + type: 'fixedCollection', + placeholder: 'Add Location', + default: {}, + description: `Subscriber location information.n`, + options: [ + { + name: 'locationFieldsValues', + displayName: 'Location', + values: [ + { + displayName: 'Latitude', + name: 'latitude', + type: 'string', + required: true, + description: 'The location latitude.', + default: '', + }, + { + displayName: 'Longitude', + name: 'longitude', + type: 'string', + required: true, + description: 'The location longitude.', + default: '', + }, + { + displayName: 'Radius', + name: 'radius', + type: 'options', + options: [ + { + name: 'Milles', + value: 'mi', + }, + { + name: 'Kilometers', + value: 'km', + }, + ], + required: true, + description: 'Returns tweets by users located within a given radius of the given latitude/longitude.', + default: '', + }, + { + displayName: 'Distance', + name: 'distance', + type: 'number', + typeOptions: { + minValue: 0, + }, + required: true, + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Result Type', + name: 'resultType', + type: 'options', + options: [ + { + name: 'Mixed', + value: 'mixed', + description: 'Include both popular and real time results in the response.', + }, + { + name: 'Recent', + value: 'recent', + description: 'Return only the most recent results in the response', + }, + { + name: 'Popular', + value: 'popular', + description: 'Return only the most popular results in the response.' + }, + ], + default: 'mixed', + description: 'Specifies what type of search results you would prefer to receive', + }, + { + displayName: 'Until', + name: 'until', + type: 'dateTime', + default: '', + description: 'Returns tweets created before the given date', + }, + ], + }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Twitter/Twitter.node.ts b/packages/nodes-base/nodes/Twitter/Twitter.node.ts index 09b1dcde933..376e9d25d88 100644 --- a/packages/nodes-base/nodes/Twitter/Twitter.node.ts +++ b/packages/nodes-base/nodes/Twitter/Twitter.node.ts @@ -1,6 +1,8 @@ import { IExecuteFunctions, + ILoadOptionsFunctions, + BINARY_ENCODING, } from 'n8n-core'; import { @@ -9,6 +11,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + INodePropertyOptions, } from 'n8n-workflow'; import { @@ -17,16 +20,16 @@ import { } from './TweetDescription'; import { + chunks, twitterApiRequest, + twitterApiRequestAllItems, } from './GenericFunctions'; import { ITweet, } from './TweetInterface'; -import { - snakeCase, -} from 'change-case'; +const ISO6391 = require('iso-639-1'); export class Twitter implements INodeType { description: INodeTypeDescription = { @@ -69,6 +72,26 @@ export class Twitter implements INodeType { ], }; + methods = { + loadOptions: { + // Get all the available languages to display them to user so that he can + // select them easily + async getLanguages(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const languages = ISO6391.getAllNames(); + for (const language of languages) { + const languageName = language; + const languageId = ISO6391.getCode(language); + returnData.push({ + name: languageName, + value: languageId, + }); + } + return returnData; + }, + }, + }; + async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: IDataObject[] = []; @@ -106,11 +129,74 @@ export class Twitter implements INodeType { continue; } - const attachmentBody = { - media_data: binaryData[binaryPropertyName].data, - media_category: snakeCase(attachment.category as string).toUpperCase(), - }; - const response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); + let attachmentBody = {}; + let response: IDataObject = {}; + + if (binaryData[binaryPropertyName].mimeType.includes('image')) { + + const attachmentBody = { + media_data: binaryData[binaryPropertyName].data, + }; + + response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); + + } else { + + // https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-init + + attachmentBody = { + command: 'INIT', + total_bytes: Buffer.from(binaryData[binaryPropertyName].data, BINARY_ENCODING).byteLength, + media_type: binaryData[binaryPropertyName].mimeType, + }; + + response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); + + const mediaId = response.media_id_string; + + // break the data on 5mb chunks (max size that can be uploaded at once) + + const binaryParts = chunks(Buffer.from(binaryData[binaryPropertyName].data, BINARY_ENCODING), 5242880); + + let index = 0; + + for (const binaryPart of binaryParts) { + + //https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-append + + attachmentBody = { + name: binaryData[binaryPropertyName].fileName, + command: 'APPEND', + media_id: mediaId, + media_data: Buffer.from(binaryPart).toString('base64'), + segment_index: index, + }; + + response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); + + index++; + } + + //https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-finalize + + attachmentBody = { + command: 'FINALIZE', + media_id: mediaId, + }; + + response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); + + // data has not been uploaded yet, so wait for it to be ready + if (response.processing_info) { + const { check_after_secs } = (response.processing_info as IDataObject); + await new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, (check_after_secs as number) * 1000); + }); + } + } + mediaIds.push(response.media_id_string); } } @@ -133,6 +219,47 @@ export class Twitter implements INodeType { responseData = await twitterApiRequest.call(this, 'POST', '/statuses/update.json', body); } + // https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets + if (operation === 'search') { + const q = this.getNodeParameter('searchText', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const qs: IDataObject = { + q + }; + + if (additionalFields.includeEntities) { + qs.include_entities = additionalFields.includeEntities as boolean; + } + + if (additionalFields.resultType) { + qs.response_type = additionalFields.resultType as string; + } + + if (additionalFields.until) { + qs.until = additionalFields.until as string; + } + + if (additionalFields.lang) { + qs.lang = additionalFields.lang as string; + } + + if (additionalFields.locationFieldsUi) { + const locationUi = additionalFields.locationFieldsUi as IDataObject; + if (locationUi.locationFieldsValues) { + const values = locationUi.locationFieldsValues as IDataObject; + qs.geocode = `${values.latitude as string},${values.longitude as string},${values.distance}${values.radius}`; + } + } + + if (returnAll) { + responseData = await twitterApiRequestAllItems.call(this, 'statuses', 'GET', '/search/tweets.json', {}, qs); + } else { + qs.count = this.getNodeParameter('limit', 0) as number; + responseData = await twitterApiRequest.call(this, 'GET', '/search/tweets.json', {}, qs); + responseData = responseData.statuses; + } + } } if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 31cb4a8da58..596ba1e499d 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -321,6 +321,7 @@ "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", From 9cae58c78798f8d9d2c152e4c5b6e57a20654778 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 00:34:15 +0200 Subject: [PATCH 029/120] :bug: Fix issue that nodes can not be opened in readOnly-Mode --- .../src/components/mixins/nodeBase.ts | 114 +++++++++--------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/packages/editor-ui/src/components/mixins/nodeBase.ts b/packages/editor-ui/src/components/mixins/nodeBase.ts index 03ad84db00b..ba3661700a0 100644 --- a/packages/editor-ui/src/components/mixins/nodeBase.ts +++ b/packages/editor-ui/src/components/mixins/nodeBase.ts @@ -29,12 +29,6 @@ export const nodeBase = mixins(nodeIndex).extend({ isMacOs (): boolean { return /(ipad|iphone|ipod|mac)/i.test(navigator.platform); }, - isReadOnly (): boolean { - if (['NodeViewExisting', 'NodeViewNew'].includes(this.$route.name as string)) { - return false; - } - return true; - }, nodeName (): string { return NODE_NAME_PREFIX + this.nodeIndex; }, @@ -276,63 +270,71 @@ export const nodeBase = mixins(nodeIndex).extend({ this.instance.addEndpoint(this.nodeName, newEndpointData); }); - if (this.isReadOnly === false) { - // Make nodes draggable - this.instance.draggable(this.nodeName, { - grid: [10, 10], - start: (params: { e: MouseEvent }) => { - if (params.e && !this.$store.getters.isNodeSelected(this.data.name)) { - // Only the node which gets dragged directly gets an event, for all others it is - // undefined. So check if the currently dragged node is selected and if not clear - // the drag-selection. - this.instance.clearDragSelection(); - this.$store.commit('resetSelectedNodes'); + // TODO: This caused problems with displaying old information + // https://github.com/jsplumb/katavorio/wiki + // https://jsplumb.github.io/jsplumb/home.html + // Make nodes draggable + this.instance.draggable(this.nodeName, { + grid: [10, 10], + start: (params: { e: MouseEvent }) => { + if (this.isReadOnly === true) { + // Do not allow to move nodes in readOnly mode + return false; + } + + if (params.e && !this.$store.getters.isNodeSelected(this.data.name)) { + // Only the node which gets dragged directly gets an event, for all others it is + // undefined. So check if the currently dragged node is selected and if not clear + // the drag-selection. + this.instance.clearDragSelection(); + this.$store.commit('resetSelectedNodes'); + } + + this.$store.commit('addActiveAction', 'dragActive'); + return true; + }, + stop: (params: { e: MouseEvent }) => { + if (this.$store.getters.isActionActive('dragActive')) { + const moveNodes = this.$store.getters.getSelectedNodes.slice(); + const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name); + if (!selectedNodeNames.includes(this.data.name)) { + // If the current node is not in selected add it to the nodes which + // got moved manually + moveNodes.push(this.data); } - this.$store.commit('addActiveAction', 'dragActive'); - }, - stop: (params: { e: MouseEvent }) => { - if (this.$store.getters.isActionActive('dragActive')) { - const moveNodes = this.$store.getters.getSelectedNodes.slice(); - const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name); - if (!selectedNodeNames.includes(this.data.name)) { - // If the current node is not in selected add it to the nodes which - // got moved manually - moveNodes.push(this.data); + // This does for some reason just get called once for the node that got clicked + // even though "start" and "drag" gets called for all. So lets do for now + // some dirty DOM query to get the new positions till I have more time to + // create a proper solution + let newNodePositon: XYPositon; + moveNodes.forEach((node: INodeUi) => { + const nodeElement = `node-${this.getNodeIndex(node.name)}`; + const element = document.getElementById(nodeElement); + if (element === null) { + return; } - // This does for some reason just get called once for the node that got clicked - // even though "start" and "drag" gets called for all. So lets do for now - // some dirty DOM query to get the new positions till I have more time to - // create a proper solution - let newNodePositon: XYPositon; - moveNodes.forEach((node: INodeUi) => { - const nodeElement = `node-${this.getNodeIndex(node.name)}`; - const element = document.getElementById(nodeElement); - if (element === null) { - return; - } + newNodePositon = [ + parseInt(element.style.left!.slice(0, -2), 10), + parseInt(element.style.top!.slice(0, -2), 10), + ]; - newNodePositon = [ - parseInt(element.style.left!.slice(0, -2), 10), - parseInt(element.style.top!.slice(0, -2), 10), - ]; + const updateInformation = { + name: node.name, + properties: { + // @ts-ignore, draggable does not have definitions + position: newNodePositon, + }, + }; - const updateInformation = { - name: node.name, - properties: { - // @ts-ignore, draggable does not have definitions - position: newNodePositon, - }, - }; + this.$store.commit('updateNodeProperties', updateInformation); + }); + } + }, + filter: '.node-description, .node-description .node-name, .node-description .node-subtitle', + }); - this.$store.commit('updateNodeProperties', updateInformation); - }); - } - }, - filter: '.node-description, .node-description .node-name, .node-description .node-subtitle', - }); - } }, isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean { From 709de6fd5f575fc5b21315b89320723800eb3b40 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 00:54:45 +0200 Subject: [PATCH 030/120] :zap: Some small improvements to Twitter-Node --- .../nodes/Twitter/TweetDescription.ts | 52 +----- .../nodes-base/nodes/Twitter/Twitter.node.ts | 166 +++++++++--------- 2 files changed, 87 insertions(+), 131 deletions(-) diff --git a/packages/nodes-base/nodes/Twitter/TweetDescription.ts b/packages/nodes-base/nodes/Twitter/TweetDescription.ts index 648e54616e4..edd12bbf5e5 100644 --- a/packages/nodes-base/nodes/Twitter/TweetDescription.ts +++ b/packages/nodes-base/nodes/Twitter/TweetDescription.ts @@ -75,54 +75,10 @@ export const tweetFields = [ options: [ { displayName: 'Attachments', - name: 'attachmentsUi', - placeholder: 'Add Attachments', - type: 'fixedCollection', - typeOptions: { - multipleValues: true, - }, - options: [ - { - name: 'attachment', - displayName: 'Attachment', - values: [ - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - default: 'data', - description: 'Name of the binary properties which contain data which should be added to tweet as attachment', - }, - // { - // displayName: 'Category', - // name: 'category', - // type: 'options', - // options: [ - // { - // name: 'Amplify Video', - // value: 'amplifyVideo', - // }, - // { - // name: 'Gif', - // value: 'tweetGif', - // }, - // { - // name: 'Image', - // value: 'tweetImage', - // }, - // { - // name: 'Video', - // value: 'tweetVideo', - // }, - // ], - // default: '', - // description: 'The category that represents how the media will be used', - // }, - ], - }, - ], - default: '', - description: 'Array of supported attachments to add to the message.', + name: 'attachments', + type: 'string', + default: 'data', + description: 'Name of the binary properties which contain
data which should be added to tweet as attachment.
Multiple ones can be comma separated.', }, { displayName: 'Display Coordinates', diff --git a/packages/nodes-base/nodes/Twitter/Twitter.node.ts b/packages/nodes-base/nodes/Twitter/Twitter.node.ts index 376e9d25d88..60f95e3798f 100644 --- a/packages/nodes-base/nodes/Twitter/Twitter.node.ts +++ b/packages/nodes-base/nodes/Twitter/Twitter.node.ts @@ -109,96 +109,96 @@ export class Twitter implements INodeType { status: text, }; - if (additionalFields.attachmentsUi) { + if (additionalFields.attachments) { const mediaIds = []; - const attachmentsUi = additionalFields.attachmentsUi as IDataObject; + const attachments = additionalFields.attachments as string; const uploadUri = 'https://upload.twitter.com/1.1/media/upload.json'; - if (attachmentsUi.attachment) { - const attachtments = attachmentsUi.attachment as IDataObject[]; - for (const attachment of attachtments) { + const attachmentProperties: string[] = attachments.split(',').map((propertyName) => { + return propertyName.trim(); + }); - const binaryData = items[i].binary as IBinaryKeyData; - const binaryPropertyName = attachment.binaryPropertyName as string; + for (const binaryPropertyName of attachmentProperties) { - if (binaryData === undefined) { - throw new Error('No binary data set. So file can not be written!'); - } + const binaryData = items[i].binary as IBinaryKeyData; - if (!binaryData[binaryPropertyName]) { - continue; - } - - let attachmentBody = {}; - let response: IDataObject = {}; - - if (binaryData[binaryPropertyName].mimeType.includes('image')) { - - const attachmentBody = { - media_data: binaryData[binaryPropertyName].data, - }; - - response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); - - } else { - - // https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-init - - attachmentBody = { - command: 'INIT', - total_bytes: Buffer.from(binaryData[binaryPropertyName].data, BINARY_ENCODING).byteLength, - media_type: binaryData[binaryPropertyName].mimeType, - }; - - response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); - - const mediaId = response.media_id_string; - - // break the data on 5mb chunks (max size that can be uploaded at once) - - const binaryParts = chunks(Buffer.from(binaryData[binaryPropertyName].data, BINARY_ENCODING), 5242880); - - let index = 0; - - for (const binaryPart of binaryParts) { - - //https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-append - - attachmentBody = { - name: binaryData[binaryPropertyName].fileName, - command: 'APPEND', - media_id: mediaId, - media_data: Buffer.from(binaryPart).toString('base64'), - segment_index: index, - }; - - response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); - - index++; - } - - //https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-finalize - - attachmentBody = { - command: 'FINALIZE', - media_id: mediaId, - }; - - response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); - - // data has not been uploaded yet, so wait for it to be ready - if (response.processing_info) { - const { check_after_secs } = (response.processing_info as IDataObject); - await new Promise((resolve, reject) => { - setTimeout(() => { - resolve(); - }, (check_after_secs as number) * 1000); - }); - } - } - - mediaIds.push(response.media_id_string); + if (binaryData === undefined) { + throw new Error('No binary data set. So file can not be written!'); } + + if (!binaryData[binaryPropertyName]) { + continue; + } + + let attachmentBody = {}; + let response: IDataObject = {}; + + if (binaryData[binaryPropertyName].mimeType.includes('image')) { + + const attachmentBody = { + media_data: binaryData[binaryPropertyName].data, + }; + + response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); + + } else { + + // https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-init + + attachmentBody = { + command: 'INIT', + total_bytes: Buffer.from(binaryData[binaryPropertyName].data, BINARY_ENCODING).byteLength, + media_type: binaryData[binaryPropertyName].mimeType, + }; + + response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); + + const mediaId = response.media_id_string; + + // break the data on 5mb chunks (max size that can be uploaded at once) + + const binaryParts = chunks(Buffer.from(binaryData[binaryPropertyName].data, BINARY_ENCODING), 5242880); + + let index = 0; + + for (const binaryPart of binaryParts) { + + //https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-append + + attachmentBody = { + name: binaryData[binaryPropertyName].fileName, + command: 'APPEND', + media_id: mediaId, + media_data: Buffer.from(binaryPart).toString('base64'), + segment_index: index, + }; + + response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); + + index++; + } + + //https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-finalize + + attachmentBody = { + command: 'FINALIZE', + media_id: mediaId, + }; + + response = await twitterApiRequest.call(this, 'POST', '', attachmentBody, {}, uploadUri); + + // data has not been uploaded yet, so wait for it to be ready + if (response.processing_info) { + const { check_after_secs } = (response.processing_info as IDataObject); + await new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, (check_after_secs as number) * 1000); + }); + } + } + + mediaIds.push(response.media_id_string); } body.media_ids = mediaIds.join(','); From d2e738b5c7a9c7e8d09fda24047da236a071be38 Mon Sep 17 00:00:00 2001 From: Robarelli Date: Sun, 7 Jun 2020 23:34:10 -0600 Subject: [PATCH 031/120] 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 c6725279edd..c3f81484f9c 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 f8e538e5cb1..26ccdfa06ea 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 032/120] 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 c3f81484f9c..4f8cfccecce 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 26ccdfa06ea..5dc74570819 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 177c6f65eb8f578de7dd309ccfcd319cc150cc6f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 09:02:32 +0200 Subject: [PATCH 033/120] :zap: Remove no longer needed OAuth-Test-Files --- .../credentials/TestOAuth2Api.credentials.ts | 26 ---- packages/nodes-base/nodes/OAuth.node.ts | 147 ------------------ 2 files changed, 173 deletions(-) delete mode 100644 packages/nodes-base/credentials/TestOAuth2Api.credentials.ts delete mode 100644 packages/nodes-base/nodes/OAuth.node.ts diff --git a/packages/nodes-base/credentials/TestOAuth2Api.credentials.ts b/packages/nodes-base/credentials/TestOAuth2Api.credentials.ts deleted file mode 100644 index 2a350faecf9..00000000000 --- a/packages/nodes-base/credentials/TestOAuth2Api.credentials.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - ICredentialType, - NodePropertyTypes, -} from 'n8n-workflow'; - -const scopes = [ - 'https://www.googleapis.com/auth/calendar', - 'https://www.googleapis.com/auth/calendar.events', -]; - -export class TestOAuth2Api implements ICredentialType { - name = 'testOAuth2Api'; - extends = [ - 'googleOAuth2Api', - ]; - displayName = 'Test OAuth2 API'; - properties = [ - { - displayName: 'Scope', - name: 'scope', - type: 'string' as NodePropertyTypes, - default: '', - placeholder: 'asdf', - }, - ]; -} diff --git a/packages/nodes-base/nodes/OAuth.node.ts b/packages/nodes-base/nodes/OAuth.node.ts deleted file mode 100644 index 3966571cd1b..00000000000 --- a/packages/nodes-base/nodes/OAuth.node.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { OptionsWithUri } from 'request'; - -import { IExecuteFunctions } from 'n8n-core'; -import { - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; - -export class OAuth implements INodeType { - description: INodeTypeDescription = { - displayName: 'OAuth', - name: 'oauth', - icon: 'fa:code-branch', - group: ['input'], - version: 1, - description: 'Gets, sends data to Oauth API Endpoint and receives generic information.', - defaults: { - name: 'OAuth', - color: '#0033AA', - }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'oAuth2Api', - required: true, - } - ], - properties: [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - options: [ - { - name: 'Get', - value: 'get', - description: 'Returns the OAuth token data.', - }, - { - name: 'Request', - value: 'request', - description: 'Make an OAuth signed requ.', - }, - ], - default: 'get', - description: 'The operation to perform.', - }, - { - displayName: 'Request Method', - name: 'requestMethod', - type: 'options', - displayOptions: { - show: { - operation: [ - 'request', - ], - }, - }, - options: [ - { - name: 'DELETE', - value: 'DELETE' - }, - { - name: 'GET', - value: 'GET' - }, - { - name: 'HEAD', - value: 'HEAD' - }, - { - name: 'PATCH', - value: 'PATCH' - }, - { - name: 'POST', - value: 'POST' - }, - { - name: 'PUT', - value: 'PUT' - }, - ], - default: 'GET', - description: 'The request method to use.', - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - displayOptions: { - show: { - operation: [ - 'request', - ], - }, - }, - default: '', - placeholder: 'http://example.com/index.html', - description: 'The URL to make the request to.', - required: true, - }, - ] - }; - - async execute(this: IExecuteFunctions): Promise { - const credentials = this.getCredentials('oAuth2Api'); - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - - if (credentials.oauthTokenData === undefined) { - throw new Error('OAuth credentials not connected'); - } - - const operation = this.getNodeParameter('operation', 0) as string; - if (operation === 'get') { - // credentials.oauthTokenData has the refreshToken and accessToken available - // it would be nice to have credentials.getOAuthToken() which returns the accessToken - // and also handles an error case where if the token is to be refreshed, it does so - // without knowledge of the node. - - return [this.helpers.returnJsonArray(JSON.parse(credentials.oauthTokenData as string))]; - } else if (operation === 'request') { - const url = this.getNodeParameter('url', 0) as string; - const requestMethod = this.getNodeParameter('requestMethod', 0) as string; - - // Authorization Code Grant - const requestOptions: OptionsWithUri = { - headers: { - 'User-Agent': 'some-user', - }, - method: requestMethod, - uri: url, - json: true, - }; - //@ts-ignore - const responseData = await this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions); - return [this.helpers.returnJsonArray(responseData)]; - } else { - throw new Error('Unknown operation'); - } - } -} From 516a56ea320517816f0e8fa7888d516d0749365e Mon Sep 17 00:00:00 2001 From: Tanay Pant <7481165+tanay1337@users.noreply.github.com> Date: Mon, 8 Jun 2020 10:44:52 +0200 Subject: [PATCH 034/120] :books: Add breaking changes for 0.69.0 (#632) --- packages/cli/BREAKING-CHANGES.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/cli/BREAKING-CHANGES.md b/packages/cli/BREAKING-CHANGES.md index 0af22307f35..68be5ce947e 100644 --- a/packages/cli/BREAKING-CHANGES.md +++ b/packages/cli/BREAKING-CHANGES.md @@ -3,6 +3,21 @@ This list shows all the versions which include breaking changes and how to upgrade. +## 0.69.0 + +### What changed? + +We have simplified how attachments are handled by the Twitter node. Rather than clicking on `Add Attachments` and having to specify the `Catergory`, you can now add attachments by just clicking on `Add Field` and selecting `Attachments`. There's no longer an option to specify the type of attachment you are adding. + +### When is action necessary? + +If you have used the Attachments option in your Twitter nodes. + +### How to upgrade: + +You'll need to re-create the attachments for the Twitter node. + + ## 0.68.0 ### What changed? From 6375934cb83fea7b2d91d78f036dd3249e2157e1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 10:51:18 +0200 Subject: [PATCH 035/120] :bookmark: Release n8n-core@0.36.0 --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index 259ce7d4cb6..598ae14b23f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.35.0", + "version": "0.36.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From c3d2f1f5ea8d5737101e0b7d7f47438a833d1aa6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 10:52:24 +0200 Subject: [PATCH 036/120] :arrow_up: Set n8n-core@0.36.0 on n8n-nodes-base --- 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 3f28db9dec6..784affead36 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -330,7 +330,7 @@ "moment-timezone": "^0.5.28", "mongodb": "^3.5.5", "mysql2": "^2.0.1", - "n8n-core": "~0.35.0", + "n8n-core": "~0.36.0", "nodemailer": "^6.4.6", "pdf-parse": "^1.1.1", "pg-promise": "^9.0.3", From 768163a0b6d4e05432d74bcea130106db417f252 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 10:53:04 +0200 Subject: [PATCH 037/120] :bookmark: Release n8n-nodes-base@0.64.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 784affead36..5cf4bb3eb9e 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.63.1", + "version": "0.64.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 1159064d590c12c7db48023ca190ae111991a8fd Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 10:54:59 +0200 Subject: [PATCH 038/120] :arrow_up: Set n8n-workflow@0.33.0 on n8n-editor-ui --- packages/editor-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 8543d2fdd06..a89b0701112 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -64,7 +64,7 @@ "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "n8n-workflow": "~0.32.0", + "n8n-workflow": "~0.33.0", "node-sass": "^4.12.0", "prismjs": "^1.17.1", "quill": "^2.0.0-dev.3", From 734e6a8e870d3c25660bf42261f051b32ecac485 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 10:55:22 +0200 Subject: [PATCH 039/120] :bookmark: Release n8n-editor-ui@0.47.0 --- packages/editor-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index a89b0701112..48caca7524e 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.46.0", + "version": "0.47.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 7697a5703e7131afc694312349d544c20a2e8153 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 10:56:50 +0200 Subject: [PATCH 040/120] :arrow_up: Set n8n-core@0.36.0, n8n-editor-ui@0.47.0, n8n-nodes-base@0.64.0 and n8n-workflow@0.33.0 on n8n --- packages/cli/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 325a73d874d..f447b5828d6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -100,10 +100,10 @@ "lodash.get": "^4.4.2", "mongodb": "^3.5.5", "mysql2": "^2.0.1", - "n8n-core": "~0.35.0", - "n8n-editor-ui": "~0.46.0", - "n8n-nodes-base": "~0.63.1", - "n8n-workflow": "~0.32.0", + "n8n-core": "~0.36.0", + "n8n-editor-ui": "~0.47.0", + "n8n-nodes-base": "~0.64.0", + "n8n-workflow": "~0.33.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", "pg": "^7.11.0", From e6d70d6cc3ed11c8ced0b82f68e31610bbc58d56 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 10:57:23 +0200 Subject: [PATCH 041/120] :bookmark: Release n8n@0.69.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 f447b5828d6..1b043c54947 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.68.2", + "version": "0.69.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 679660440ecccb396d675bfcbce12b39c044239e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 8 Jun 2020 11:11:04 +0200 Subject: [PATCH 042/120] :bookmark: Release n8n-workflow@0.33.0 --- packages/workflow/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 969270199b1..068dc6d108b 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.32.0", + "version": "0.33.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 3f9bdd2e4150184774d1de28883bc5d6d1837077 Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Mon, 8 Jun 2020 14:40:23 +0200 Subject: [PATCH 043/120] 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 00000000000..775211cbc9c --- /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 969c392cdd3..eb414b82aa2 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 6c1d7be400e..bed4606042c 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 25028f88e8f..c2ed3f3e0a3 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 31cb4a8da58..d41b5bb4baa 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 46147e0d97795f5cf9b2e16b8cf1f9353475df06 Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Mon, 8 Jun 2020 16:58:32 +0200 Subject: [PATCH 044/120] OAuth2 credentials, genericFunctions modded, oauth2 ui changes --- .../PagerDutyOAuth2Api.credentials.ts | 39 +++++++++++++++++++ .../nodes/PagerDuty/GenericFunctions.ts | 26 +++++++++---- .../nodes/PagerDuty/PagerDuty.node.ts | 34 ++++++++++++++++ packages/nodes-base/package.json | 1 + 4 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 packages/nodes-base/credentials/PagerDutyOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/PagerDutyOAuth2Api.credentials.ts b/packages/nodes-base/credentials/PagerDutyOAuth2Api.credentials.ts new file mode 100644 index 00000000000..0bdd68b9c94 --- /dev/null +++ b/packages/nodes-base/credentials/PagerDutyOAuth2Api.credentials.ts @@ -0,0 +1,39 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class PagerDutyOAuth2Api implements ICredentialType { + name = 'pagerDutyOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'PagerDuty OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://app.pagerduty.com/oauth/authorize', + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://app.pagerduty.com/oauth/token', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: 'grant_type=authorization_code', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'header', + description: 'Method of authentication.', + }, + ]; +} diff --git a/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts index c360114144e..716b4e0d5e5 100644 --- a/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts +++ b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts @@ -19,16 +19,11 @@ import { export async function pagerDutyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('pagerDutyApi'); - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } + const authenticationMethod = this.getNodeParameter('authentication', 0); const options: OptionsWithUri = { headers: { - Accept: 'application/vnd.pagerduty+json;version=2', - Authorization: `Token token=${credentials.apiToken}`, + Accept: 'application/vnd.pagerduty+json;version=2' }, method, body, @@ -39,15 +34,30 @@ export async function pagerDutyApiRequest(this: IExecuteFunctions | IWebhookFunc arrayFormat: 'brackets', }, }; + if (!Object.keys(body).length) { delete options.form; } if (!Object.keys(query).length) { delete options.qs; } + options.headers = Object.assign({}, options.headers, headers); + try { - return await this.helpers.request!(options); + if (authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('pagerDutyApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + options.headers!['Authorization'] = `Token token=${credentials.apiToken}`; + + return await this.helpers.request!(options); + } else { + return await this.helpers.requestOAuth2!.call(this, 'pagerDutyOAuth2Api', options); + } } catch (error) { if (error.response && error.response.body && error.response.body.error && error.response.body.error.errors) { // Try to return the error prettier diff --git a/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts b/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts index d53e5921b88..f9b8b1e3c70 100644 --- a/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts +++ b/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts @@ -66,9 +66,43 @@ export class PagerDuty implements INodeType { { name: 'pagerDutyApi', required: true, + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'pagerDutyOAuth2Api', + 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', + }, { displayName: 'Resource', name: 'resource', diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3f28db9dec6..18314a94880 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -97,6 +97,7 @@ "dist/credentials/OAuth2Api.credentials.js", "dist/credentials/OpenWeatherMapApi.credentials.js", "dist/credentials/PagerDutyApi.credentials.js", + "dist/credentials/PagerDutyOAuth2Api.credentials.js", "dist/credentials/PayPalApi.credentials.js", "dist/credentials/PipedriveApi.credentials.js", "dist/credentials/Postgres.credentials.js", From ddb48dc9db81fdd49e7ce3a82bed152998a38eee Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Mon, 8 Jun 2020 17:50:18 +0200 Subject: [PATCH 045/120] Credentials, oauth2 supported genericfunctions, oauth2 UI mod --- .../WebflowOAuth2Api.credentials.ts | 50 +++++++++++++++++++ .../nodes/Webflow/GenericFunctions.ts | 25 +++++----- .../nodes/Webflow/WebflowTrigger.node.ts | 37 +++++++++++++- packages/nodes-base/package.json | 1 + 4 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 packages/nodes-base/credentials/WebflowOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/WebflowOAuth2Api.credentials.ts b/packages/nodes-base/credentials/WebflowOAuth2Api.credentials.ts new file mode 100644 index 00000000000..c19e4517417 --- /dev/null +++ b/packages/nodes-base/credentials/WebflowOAuth2Api.credentials.ts @@ -0,0 +1,50 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class WebflowOAuth2Api implements ICredentialType { + name = 'webflowOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Webflow OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://webflow.com/oauth/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.webflow.com/oauth/access_token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + description: 'For some services additional query parameters have to be set which can be defined here.', + placeholder: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + description: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Webflow/GenericFunctions.ts b/packages/nodes-base/nodes/Webflow/GenericFunctions.ts index 030e47f9039..cc10d218a94 100644 --- a/packages/nodes-base/nodes/Webflow/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Webflow/GenericFunctions.ts @@ -9,14 +9,10 @@ import { import { IDataObject } from 'n8n-workflow'; export async function webflowApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('webflowApi'); - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } + const authenticationMethod = this.getNodeParameter('authentication', 0); let options: OptionsWithUri = { headers: { - authorization: `Bearer ${credentials.accessToken}`, 'accept-version': '1.0.0', }, method, @@ -31,14 +27,19 @@ export async function webflowApiRequest(this: IHookFunctions | IExecuteFunctions } try { - return await this.helpers.request!(options); - } catch (error) { + if (authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('webflowApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } - let errorMessage = error.message; - if (error.response.body && error.response.body.err) { - errorMessage = error.response.body.err; + options.headers!['authorization'] = `Bearer ${credentials.accessToken}`; + + return await this.helpers.request!(options); + } else { + return await this.helpers.requestOAuth2!.call(this, 'webflowOAuth2Api', options); } - - throw new Error('Webflow Error: ' + errorMessage); + } catch (error) { + throw new Error('Webflow Error: ' + error.code + error.msg); } } diff --git a/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts index 709b9858cd5..73b2efef61d 100644 --- a/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts +++ b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts @@ -34,7 +34,25 @@ export class WebflowTrigger implements INodeType { { name: 'webflowApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'webflowOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], webhooks: [ { @@ -45,6 +63,23 @@ export class WebflowTrigger 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: 'Site', name: 'site', diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3f28db9dec6..95cca9994b2 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -123,6 +123,7 @@ "dist/credentials/UpleadApi.credentials.js", "dist/credentials/VeroApi.credentials.js", "dist/credentials/WebflowApi.credentials.js", + "dist/credentials/WebflowOAuth2Api.credentials.js", "dist/credentials/WooCommerceApi.credentials.js", "dist/credentials/WordpressApi.credentials.js", "dist/credentials/ZendeskApi.credentials.js", From 918fba81cc300e58b6f6e43974e650e77a2f26c4 Mon Sep 17 00:00:00 2001 From: ricardo Date: Mon, 8 Jun 2020 16:13:17 -0400 Subject: [PATCH 046/120] :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 60f95e3798f..94718973c5e 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 bdad41f4e88aa73c6736b62a764622e4c1ea3980 Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Tue, 9 Jun 2020 14:00:41 +0200 Subject: [PATCH 047/120] OAuth2 support, changed url to subdomain in credentials --- .../credentials/ZendeskApi.credentials.ts | 7 +- .../ZendeskOAuth2Api.credentials.ts | 74 +++++++++++++++++++ .../nodes/Zendesk/GenericFunctions.ts | 40 +++++++--- .../nodes-base/nodes/Zendesk/Zendesk.node.ts | 37 +++++++++- packages/nodes-base/package.json | 1 + 5 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 packages/nodes-base/credentials/ZendeskOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/ZendeskApi.credentials.ts b/packages/nodes-base/credentials/ZendeskApi.credentials.ts index 29048c1172e..4f285912b6b 100644 --- a/packages/nodes-base/credentials/ZendeskApi.credentials.ts +++ b/packages/nodes-base/credentials/ZendeskApi.credentials.ts @@ -8,10 +8,11 @@ export class ZendeskApi implements ICredentialType { displayName = 'Zendesk API'; properties = [ { - displayName: 'URL', - name: 'url', + displayName: 'Subdomain', + name: 'subdomain', type: 'string' as NodePropertyTypes, - default: '', + description: 'The subdomain of your Zendesk work environment.', + default: 'n8n', }, { displayName: 'Email', diff --git a/packages/nodes-base/credentials/ZendeskOAuth2Api.credentials.ts b/packages/nodes-base/credentials/ZendeskOAuth2Api.credentials.ts new file mode 100644 index 00000000000..b530404a0d5 --- /dev/null +++ b/packages/nodes-base/credentials/ZendeskOAuth2Api.credentials.ts @@ -0,0 +1,74 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class ZendeskOAuth2Api implements ICredentialType { + name = 'zendeskOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Zendesk OAuth2 API'; + properties = [ + { + displayName: 'Subdomain', + name: 'subdomain', + type: 'string' as NodePropertyTypes, + default: 'n8n', + description: 'The subdomain of your Zendesk work environment.', + required: true, + }, + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'string' as NodePropertyTypes, + default: 'https://{SUBDOMAIN_HERE}.zendesk.com/oauth/authorizations/new', + description: 'URL to get authorization code. Replace {SUBDOMAIN_HERE} with your subdomain.', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'string' as NodePropertyTypes, + default: 'https://{SUBDOMAIN_HERE}.zendesk.com/oauth/tokens', + description: 'URL to get access token. Replace {SUBDOMAIN_HERE} with your subdomain.', + required: true, + }, + { + displayName: 'Client ID', + name: 'clientId', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + }, + { + displayName: 'Client Secret', + name: 'clientSecret', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'string' as NodePropertyTypes, + default: 'write read', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + description: 'For some services additional query parameters have to be set which can be defined here.', + placeholder: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + description: 'Resource to consume.', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts index ffe371e1608..007741813f0 100644 --- a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts @@ -14,26 +14,48 @@ import { } from 'n8n-workflow'; export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('zendeskApi'); - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - const base64Key = Buffer.from(`${credentials.email}/token:${credentials.apiToken}`).toString('base64'); + const authenticationMethod = this.getNodeParameter('authentication', 0); + let options: OptionsWithUri = { - headers: { 'Authorization': `Basic ${base64Key}`}, + headers: {}, method, qs, body, - uri: uri ||`${credentials.url}/api/v2${resource}.json`, + //@ts-ignore + uri, json: true }; + options = Object.assign({}, options, option); if (Object.keys(options.body).length === 0) { delete options.body; } + try { - return await this.helpers.request!(options); - } catch (err) { + if (authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('zendeskApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const base64Key = Buffer.from(`${credentials.email}/token:${credentials.apiToken}`).toString('base64'); + options.uri = `https://${credentials.subdomain}.zendesk.com/api/v2${resource}.json`; + options.headers!['Authorization'] = `Basic ${base64Key}`; + + return await this.helpers.request!(options); + } else { + const credentials = this.getCredentials('zendeskOAuth2Api'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + options.uri = `https://${credentials.subdomain}.zendesk.com/api/v2${resource}.json`; + + return await this.helpers.requestOAuth2!.call(this, 'zendeskOAuth2Api', options); + } + } catch(err) { let errorMessage = err.message; if (err.response && err.response.body && err.response.body.error) { errorMessage = err.response.body.error; diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index 8a2586d8d8c..d0ea357b5ae 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -52,9 +52,44 @@ export class Zendesk implements INodeType { { name: 'zendeskApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'zendeskOAuth2Api', + 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 resource to operate on.', + }, { displayName: 'Resource', name: 'resource', diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3f28db9dec6..03f9d8c1b1c 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -126,6 +126,7 @@ "dist/credentials/WooCommerceApi.credentials.js", "dist/credentials/WordpressApi.credentials.js", "dist/credentials/ZendeskApi.credentials.js", + "dist/credentials/ZendeskOAuth2Api.credentials.js", "dist/credentials/ZohoOAuth2Api.credentials.js", "dist/credentials/ZulipApi.credentials.js" ], From 5bce911b0f03ef1f8a42a7a378df8f0788d8f891 Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Tue, 9 Jun 2020 15:53:17 +0200 Subject: [PATCH 048/120] Added OAuth2 UI for trigger --- .../nodes/Zendesk/ZendeskTrigger.node.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts b/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts index cc4e26dbd40..a5e1350fedb 100644 --- a/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts +++ b/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts @@ -42,7 +42,25 @@ export class ZendeskTrigger implements INodeType { { name: 'zendeskApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'zendeskOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], webhooks: [ { @@ -53,6 +71,23 @@ export class ZendeskTrigger implements INodeType { }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Access Token', + value: 'accessToken', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'accessToken', + description: 'The resource to operate on.', + }, { displayName: 'Service', name: 'service', From 62c835656671fab65187f2d861ffb5a98c7440db Mon Sep 17 00:00:00 2001 From: Erin McNulty Date: Tue, 9 Jun 2020 16:50:09 -0400 Subject: [PATCH 049/120] :twisted_rightwards_arrows: Fix Github Trigger node and add OAuth support (#643) * Fixed bug with small name change in credentials * Deleted Excess Commented Code * Changed the code to give user options --- .../nodes/Github/GithubTrigger.node.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Github/GithubTrigger.node.ts b/packages/nodes-base/nodes/Github/GithubTrigger.node.ts index 2a14b5d3db3..1360d7536a0 100644 --- a/packages/nodes-base/nodes/Github/GithubTrigger.node.ts +++ b/packages/nodes-base/nodes/Github/GithubTrigger.node.ts @@ -34,7 +34,25 @@ export class GithubTrigger implements INodeType { { name: 'githubApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'githubOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], webhooks: [ { @@ -45,6 +63,23 @@ export class GithubTrigger implements INodeType { }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Access Token', + value: 'accessToken', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'accessToken', + description: 'The resource to operate on.', + }, { displayName: 'Repository Owner', name: 'owner', From 751d6934d7a04e6a56cc2da08217a64b603be2c7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 9 Jun 2020 23:05:51 +0200 Subject: [PATCH 050/120] :bookmark: Release n8n-nodes-base@0.64.1 --- 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 5cf4bb3eb9e..822978eaf43 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.64.0", + "version": "0.64.1", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From e8737b5c4e3e79104a298d6b582784b67965f178 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 9 Jun 2020 23:07:04 +0200 Subject: [PATCH 051/120] :arrow_up: Set n8n-nodes-base@0.64.1 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 1b043c54947..3873eb6bdb5 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.0", + "n8n-nodes-base": "~0.64.1", "n8n-workflow": "~0.33.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", From 4eff72504d0491443998db82b497f47a7ccb61b3 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 9 Jun 2020 23:07:51 +0200 Subject: [PATCH 052/120] :bookmark: Release n8n@0.69.1 --- 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 3873eb6bdb5..f3a747bfe21 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.69.0", + "version": "0.69.1", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 68f9dd15609f07e91e18f71ef82f299356db5c4d Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Wed, 10 Jun 2020 09:50:50 +0200 Subject: [PATCH 053/120] OAuth2 support --- .../TypeformOAuth2Api.credentials.ts | 46 +++++++++++++++++++ .../nodes/Typeform/GenericFunctions.ts | 29 +++++++----- .../nodes/Typeform/TypeformTrigger.node.ts | 37 ++++++++++++++- packages/nodes-base/package.json | 1 + 4 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts b/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts new file mode 100644 index 00000000000..5465eee72c5 --- /dev/null +++ b/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts @@ -0,0 +1,46 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class TypeformOAuth2Api implements ICredentialType { + name = 'typeformOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.typeform.com/oauth/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.typeform.com/oauth/token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'string' as NodePropertyTypes, + default: 'webhooks:write,webhooks:read,forms:read,', + }, + { + 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/Typeform/GenericFunctions.ts b/packages/nodes-base/nodes/Typeform/GenericFunctions.ts index c5e9242465d..83ca713afe6 100644 --- a/packages/nodes-base/nodes/Typeform/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Typeform/GenericFunctions.ts @@ -45,18 +45,10 @@ export interface ITypeformAnswerField { * @returns {Promise} */ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: IDataObject): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('typeformApi'); - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - - query = query || {}; + const authenticationMethod = this.getNodeParameter('authentication', 0); const options: OptionsWithUri = { - headers: { - 'Authorization': `bearer ${credentials.accessToken}`, - }, + headers: {}, method, body, qs: query, @@ -64,8 +56,23 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa json: true, }; + query = query || {}; + try { - return await this.helpers.request!(options); + if (authenticationMethod === 'accessToken') { + + const credentials = this.getCredentials('typeformApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + options.headers!['Authorization'] = `bearer ${credentials.accessToken}`; + + return await this.helpers.request!(options); + } else { + return await this.helpers.requestOAuth2!.call(this, 'typeformOAuth2Api', options); + } } catch (error) { if (error.statusCode === 401) { // Return a clear error diff --git a/packages/nodes-base/nodes/Typeform/TypeformTrigger.node.ts b/packages/nodes-base/nodes/Typeform/TypeformTrigger.node.ts index b127a1f5943..9c8f6e16ffa 100644 --- a/packages/nodes-base/nodes/Typeform/TypeformTrigger.node.ts +++ b/packages/nodes-base/nodes/Typeform/TypeformTrigger.node.ts @@ -37,7 +37,25 @@ export class TypeformTrigger implements INodeType { { name: 'typeformApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'typeformOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], webhooks: [ { @@ -48,6 +66,23 @@ export class TypeformTrigger implements INodeType { }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Access Token', + value: 'accessToken', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'accessToken', + description: 'The resource to operate on.', + }, { displayName: 'Form', name: 'formId', diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3f28db9dec6..13b3f0c128f 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -119,6 +119,7 @@ "dist/credentials/TwilioApi.credentials.js", "dist/credentials/TwitterOAuth1Api.credentials.js", "dist/credentials/TypeformApi.credentials.js", + "dist/credentials/TypeformOAuth2Api.credentials.js", "dist/credentials/TogglApi.credentials.js", "dist/credentials/UpleadApi.credentials.js", "dist/credentials/VeroApi.credentials.js", From 934a4f16d16fff1d5c87db9d7df004838ea37980 Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Wed, 10 Jun 2020 09:51:12 +0200 Subject: [PATCH 054/120] Update TypeformOAuth2Api.credentials.ts --- packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts b/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts index 5465eee72c5..6e953915771 100644 --- a/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts @@ -9,6 +9,7 @@ export class TypeformOAuth2Api implements ICredentialType { extends = [ 'oAuth2Api', ]; + displayName = 'Typeform OAuth2 API'; properties = [ { displayName: 'Authorization URL', From 60fa44722d96e6944e34899db3d3464f0475747e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 10 Jun 2020 11:46:12 +0200 Subject: [PATCH 055/120] :zap: Remove not working and not possible option from Github-Node --- packages/nodes-base/nodes/Github/Github.node.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/nodes-base/nodes/Github/Github.node.ts b/packages/nodes-base/nodes/Github/Github.node.ts index 00133ec3025..035b3810911 100644 --- a/packages/nodes-base/nodes/Github/Github.node.ts +++ b/packages/nodes-base/nodes/Github/Github.node.ts @@ -244,11 +244,6 @@ export class Github implements INodeType { }, }, options: [ - { - name: 'Get Emails', - value: 'getEmails', - description: 'Returns the email addresses of a user', - }, { name: 'Get Repositories', value: 'getRepositories', From 9983ed6f8f8a358afd83600cd239e9a59f36de6e Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Wed, 10 Jun 2020 12:48:21 +0200 Subject: [PATCH 056/120] OAuth2: credentials, genericFunctions, UI --- .../SurveyMonkeyOAuth2Api.credentials.ts | 47 +++++++++++++++++++ .../nodes/SurveyMonkey/GenericFunctions.ts | 25 ++++++---- .../SurveyMonkey/SurveyMonkeyTrigger.node.ts | 44 ++++++++++++++++- packages/nodes-base/package.json | 1 + 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 packages/nodes-base/credentials/SurveyMonkeyOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/SurveyMonkeyOAuth2Api.credentials.ts b/packages/nodes-base/credentials/SurveyMonkeyOAuth2Api.credentials.ts new file mode 100644 index 00000000000..cbeafdf753b --- /dev/null +++ b/packages/nodes-base/credentials/SurveyMonkeyOAuth2Api.credentials.ts @@ -0,0 +1,47 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class SurveyMonkeyOAuth2Api implements ICredentialType { + name = 'surveyMonkeyOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'SurveyMonkey OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.surveymonkey.com/oauth/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.surveymonkey.com/oauth/token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'string' as NodePropertyTypes, + default: 'surveys_read,surveys_write,responses_read,responses_write,webhooks_read,webhooks_write', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body' + }, + ]; +} diff --git a/packages/nodes-base/nodes/SurveyMonkey/GenericFunctions.ts b/packages/nodes-base/nodes/SurveyMonkey/GenericFunctions.ts index 86f999b578c..bfca4dde1b0 100644 --- a/packages/nodes-base/nodes/SurveyMonkey/GenericFunctions.ts +++ b/packages/nodes-base/nodes/SurveyMonkey/GenericFunctions.ts @@ -14,19 +14,13 @@ import { } from 'n8n-workflow'; export async function surveyMonkeyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any - - const credentials = this.getCredentials('surveyMonkeyApi'); - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } + const authenticationMethod = this.getNodeParameter('authentication', 0); const endpoint = 'https://api.surveymonkey.com/v3'; let options: OptionsWithUri = { headers: { 'Content-Type': 'application/json', - 'Authorization': `bearer ${credentials.accessToken}`, }, method, body, @@ -34,6 +28,7 @@ export async function surveyMonkeyApiRequest(this: IExecuteFunctions | IWebhookF uri: uri || `${endpoint}${resource}`, json: true }; + if (!Object.keys(body).length) { delete options.body; } @@ -41,8 +36,22 @@ export async function surveyMonkeyApiRequest(this: IExecuteFunctions | IWebhookF delete options.qs; } options = Object.assign({}, options, option); + try { - return await this.helpers.request!(options); + if ( authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('surveyMonkeyApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + // @ts-ignore + options.headers['Authorization'] = `bearer ${credentials.accessToken}`; + + return await this.helpers.request!(options); + + } else { + return await this.helpers.requestOAuth2?.call(this, 'surveyMonkeyOAuth2Api', options); + } } catch (error) { const errorMessage = error.response.body.error.message; if (errorMessage !== undefined) { diff --git a/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts b/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts index efdc8dba5aa..c0f722dd8c9 100644 --- a/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts +++ b/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts @@ -49,6 +49,24 @@ export class SurveyMonkeyTrigger implements INodeType { { name: 'surveyMonkeyApi', required: true, + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'surveyMonkeyOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, }, ], webhooks: [ @@ -66,6 +84,23 @@ export class SurveyMonkeyTrigger 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: 'Type', name: 'objectType', @@ -453,11 +488,18 @@ export class SurveyMonkeyTrigger implements INodeType { async webhook(this: IWebhookFunctions): Promise { const event = this.getNodeParameter('event') as string; const objectType = this.getNodeParameter('objectType') as string; - const credentials = this.getCredentials('surveyMonkeyApi') as IDataObject; + const authenticationMethod = this.getNodeParameter('authentication') as string; + let credentials : IDataObject; const headerData = this.getHeaderData() as IDataObject; const req = this.getRequestObject(); const webhookName = this.getWebhookName(); + if (authenticationMethod === 'accessToken') { + credentials = this.getCredentials('surveyMonkeyApi') as IDataObject; + } else { + credentials = this.getCredentials('surveyMonkeyOAuth2Api') as IDataObject; + } + if (webhookName === 'setup') { // It is a create webhook confirmation request return {}; diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 822978eaf43..fe6c3ccb95d 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -113,6 +113,7 @@ "dist/credentials/SalesmateApi.credentials.js", "dist/credentials/SegmentApi.credentials.js", "dist/credentials/SurveyMonkeyApi.credentials.js", + "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js", "dist/credentials/TelegramApi.credentials.js", "dist/credentials/TodoistApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", From d6144838281c3a9004d2eb440b74b27126628c44 Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Wed, 10 Jun 2020 12:57:13 +0200 Subject: [PATCH 057/120] 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 c2ed3f3e0a3..38fd678d58e 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 058/120] :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 7501b4aa9bc..54016c5d55b 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 65edec601d8..bce82bf890c 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 9d195ccbad8..b2b84cd7c71 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 a36a2fd81e0..d302e3970a4 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 b49c0600b20..00000000000 --- 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 059/120] :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 00000000000..8c52f9372c1 --- /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 b9eb9b0f423..1ea81acbc89 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 6b179db7fd2..98616902548 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 50abcda83ed..7bdafe7605b 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 2a6df1e4e3d..63e51f078a8 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 060/120] 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 00000000000..e67c6a0c9e4 --- /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 00000000000..ff1df3b2657 --- /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 00000000000..cb0960f1e0b --- /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 822978eaf43..37b9adecf23 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 061/120] :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 ff1df3b2657..ee790863d75 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 cb0960f1e0b..8d1190c8d64 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 062/120] :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 ee790863d75..5a7eda2210f 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 8d1190c8d64..9c74b6a3d92 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 063/120] :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 90e4e8b1fd0..52fe2fcc848 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 064/120] :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 ddb74add0a5..fd695efb4b7 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 065/120] :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 9d859de3942..1c252141b38 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 066/120] :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 1c252141b38..66394fec299 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 067/120] :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 b2b84cd7c71..355415158ac 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 068/120] :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 e12e3840942..38e883e53f0 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 069/120] :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 fc11ef32fb3..1d1d4d8cc5f 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 555015c10da..29a80e434fa 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 31b271d6335..c2bb55040ec 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 070/120] :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 9946cd802a1..7d15e492ad5 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 8ddfd7c1fa0..f6291b10452 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 94718973c5e..a62f9fd95bd 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 071/120] :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 a3ba20f28a8..4f844e11882 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 072/120] :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 52fe2fcc848..042eb391050 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 073/120] :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 4f8cfccecce..9537c325213 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 074/120] :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 dbd7ab29295..d91a287b88f 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 190ff3de88d..953a45a6205 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 075/120] :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 182922492c5..8a3c76c2611 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 076/120] :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 f3a747bfe21..f221cd59c74 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 077/120] :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 f221cd59c74..1628a80b82c 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 078/120] :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 775211cbc9c..ce18b899df4 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 eb414b82aa2..d8d68abfb6e 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 bed4606042c..adf01d1ec31 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 38fd678d58e..47b2318b36f 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 7b90d3e2a9b..5b33ebcc981 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 079/120] :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 5b33ebcc981..f762717e761 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 080/120] :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 0a0efc70914..886424b6a65 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 3d66c2d8ab6..59362b50d64 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 3b8d17c3023..25dbeb9984f 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 67e07ad6bde..9fbd80a0f56 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.', }, { From 8f15c93e43847580b07f99686c3544fa5d9fe0d3 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 14 Jun 2020 16:27:28 -0400 Subject: [PATCH 081/120] :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 f762717e761..8aa05c1f5fe 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -118,6 +118,7 @@ "dist/credentials/SalesmateApi.credentials.js", "dist/credentials/SegmentApi.credentials.js", "dist/credentials/SurveyMonkeyApi.credentials.js", + "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js", "dist/credentials/TelegramApi.credentials.js", "dist/credentials/TodoistApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", From 0f6b82249b49647d2a3bf55973b7a643a2f4584a Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 14 Jun 2020 16:30:24 -0400 Subject: [PATCH 082/120] :zap: Improvements to SurveyMonkey-Node --- .../credentials/SurveyMonkeyOAuth2Api.credentials.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/credentials/SurveyMonkeyOAuth2Api.credentials.ts b/packages/nodes-base/credentials/SurveyMonkeyOAuth2Api.credentials.ts index cbeafdf753b..26949d879f1 100644 --- a/packages/nodes-base/credentials/SurveyMonkeyOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/SurveyMonkeyOAuth2Api.credentials.ts @@ -3,6 +3,14 @@ import { NodePropertyTypes, } from 'n8n-workflow'; +const scopes = [ + 'surveys_read', + 'collectors_read', + 'responses_read', + 'responses_read_detail', + 'webhooks_write', + 'webhooks_read', +]; export class SurveyMonkeyOAuth2Api implements ICredentialType { name = 'surveyMonkeyOAuth2Api'; @@ -28,8 +36,8 @@ export class SurveyMonkeyOAuth2Api implements ICredentialType { { displayName: 'Scope', name: 'scope', - type: 'string' as NodePropertyTypes, - default: 'surveys_read,surveys_write,responses_read,responses_write,webhooks_read,webhooks_write', + type: 'hidden' as NodePropertyTypes, + default: scopes.join(','), }, { displayName: 'Auth URI Query Parameters', From 15a9ff25c4abc64e87ae1a6cfbb48accdcaaaa4b Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 14 Jun 2020 17:47:28 -0400 Subject: [PATCH 083/120] :zap: Small improvement --- 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 8aa05c1f5fe..7566f70f06a 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -132,6 +132,7 @@ "dist/credentials/WooCommerceApi.credentials.js", "dist/credentials/WordpressApi.credentials.js", "dist/credentials/ZendeskApi.credentials.js", + "dist/credentials/ZendeskOAuth2Api.credentials.js", "dist/credentials/ZohoOAuth2Api.credentials.js", "dist/credentials/ZulipApi.credentials.js" ], From 8d778bf06ec860aec89610b05c173ebf2eb0457d Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 14 Jun 2020 18:04:22 -0400 Subject: [PATCH 084/120] :zap: Improvements to Zendesk-Node --- .../credentials/ZendeskOAuth2Api.credentials.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/credentials/ZendeskOAuth2Api.credentials.ts b/packages/nodes-base/credentials/ZendeskOAuth2Api.credentials.ts index b530404a0d5..06428456e62 100644 --- a/packages/nodes-base/credentials/ZendeskOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/ZendeskOAuth2Api.credentials.ts @@ -3,6 +3,10 @@ import { NodePropertyTypes, } from 'n8n-workflow'; +const scopes = [ + 'read', + 'write', +]; export class ZendeskOAuth2Api implements ICredentialType { name = 'zendeskOAuth2Api'; @@ -15,7 +19,8 @@ export class ZendeskOAuth2Api implements ICredentialType { displayName: 'Subdomain', name: 'subdomain', type: 'string' as NodePropertyTypes, - default: 'n8n', + default: '', + placeholder: 'n8n', description: 'The subdomain of your Zendesk work environment.', required: true, }, @@ -52,8 +57,8 @@ export class ZendeskOAuth2Api implements ICredentialType { { displayName: 'Scope', name: 'scope', - type: 'string' as NodePropertyTypes, - default: 'write read', + type: 'hidden' as NodePropertyTypes, + default: scopes.join(' '), }, { displayName: 'Auth URI Query Parameters', From 2ef5c8c83645933fabd74bc708ec352416fc05db Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 14 Jun 2020 18:19:35 -0400 Subject: [PATCH 085/120] :zap: Small improvement --- packages/nodes-base/nodes/Zendesk/GenericFunctions.ts | 2 +- packages/nodes-base/nodes/Zendesk/Zendesk.node.ts | 8 ++++---- packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts index 007741813f0..271c10259f3 100644 --- a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts @@ -32,7 +32,7 @@ export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions } try { - if (authenticationMethod === 'accessToken') { + if (authenticationMethod === 'apiToken') { const credentials = this.getCredentials('zendeskApi'); if (credentials === undefined) { diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index d0ea357b5ae..d0c72c335f7 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -55,7 +55,7 @@ export class Zendesk implements INodeType { displayOptions: { show: { authentication: [ - 'accessToken', + 'apiToken', ], }, }, @@ -79,15 +79,15 @@ export class Zendesk implements INodeType { type: 'options', options: [ { - name: 'Access Token', - value: 'accessToken', + name: 'API Token', + value: 'apiToken', }, { name: 'OAuth2', value: 'oAuth2', }, ], - default: 'accessToken', + default: 'apiToken', description: 'The resource to operate on.', }, { diff --git a/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts b/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts index a5e1350fedb..421d9e4b32f 100644 --- a/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts +++ b/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts @@ -45,7 +45,7 @@ export class ZendeskTrigger implements INodeType { displayOptions: { show: { authentication: [ - 'accessToken', + 'apiToken', ], }, }, @@ -77,15 +77,15 @@ export class ZendeskTrigger implements INodeType { type: 'options', options: [ { - name: 'Access Token', - value: 'accessToken', + name: 'API Token', + value: 'apiToken', }, { name: 'OAuth2', value: 'oAuth2', }, ], - default: 'accessToken', + default: 'apiToken', description: 'The resource to operate on.', }, { From a8a2260c586e68d0cf5393b1d51bda404f24dfa0 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 14 Jun 2020 19:30:05 -0400 Subject: [PATCH 086/120] :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 7566f70f06a..9cb463d8fd3 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -125,6 +125,7 @@ "dist/credentials/TwilioApi.credentials.js", "dist/credentials/TwitterOAuth1Api.credentials.js", "dist/credentials/TypeformApi.credentials.js", + "dist/credentials/TypeformOAuth2Api.credentials.js", "dist/credentials/TogglApi.credentials.js", "dist/credentials/UpleadApi.credentials.js", "dist/credentials/VeroApi.credentials.js", From c4bbb961a2b41709d525acff7b54e29e1a034596 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 14 Jun 2020 19:31:45 -0400 Subject: [PATCH 087/120] :zap: Improvements to Typeform-Node --- .../credentials/TypeformOAuth2Api.credentials.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts b/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts index 6e953915771..a876e87ed08 100644 --- a/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/TypeformOAuth2Api.credentials.ts @@ -3,6 +3,12 @@ import { NodePropertyTypes, } from 'n8n-workflow'; +const scopes = [ + 'webhooks:write', + 'webhooks:read', + 'forms:read', +]; + export class TypeformOAuth2Api implements ICredentialType { name = 'typeformOAuth2Api'; @@ -28,8 +34,8 @@ export class TypeformOAuth2Api implements ICredentialType { { displayName: 'Scope', name: 'scope', - type: 'string' as NodePropertyTypes, - default: 'webhooks:write,webhooks:read,forms:read,', + type: 'hidden' as NodePropertyTypes, + default: scopes.join(','), }, { displayName: 'Auth URI Query Parameters', From 7682fd79e61a46b525f8f68f547f28eba4954b99 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 14 Jun 2020 21:36:56 -0400 Subject: [PATCH 088/120] :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 9cb463d8fd3..649bde48372 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -102,6 +102,7 @@ "dist/credentials/OAuth2Api.credentials.js", "dist/credentials/OpenWeatherMapApi.credentials.js", "dist/credentials/PagerDutyApi.credentials.js", + "dist/credentials/PagerDutyOAuth2Api.credentials.js", "dist/credentials/PayPalApi.credentials.js", "dist/credentials/PipedriveApi.credentials.js", "dist/credentials/Postgres.credentials.js", From acfc4fc39dac367c82facb9d61e0efabe56d257d Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 14 Jun 2020 21:39:30 -0400 Subject: [PATCH 089/120] :zap: Improvements to PagerDuty-Node --- .../credentials/PagerDutyOAuth2Api.credentials.ts | 8 +++++++- packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts | 2 +- packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/nodes-base/credentials/PagerDutyOAuth2Api.credentials.ts b/packages/nodes-base/credentials/PagerDutyOAuth2Api.credentials.ts index 0bdd68b9c94..28b44011db4 100644 --- a/packages/nodes-base/credentials/PagerDutyOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/PagerDutyOAuth2Api.credentials.ts @@ -26,7 +26,13 @@ export class PagerDutyOAuth2Api implements ICredentialType { displayName: 'Auth URI Query Parameters', name: 'authQueryParameters', type: 'hidden' as NodePropertyTypes, - default: 'grant_type=authorization_code', + default: '', + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: '', }, { displayName: 'Authentication', diff --git a/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts index 716b4e0d5e5..f4832b923c7 100644 --- a/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts +++ b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts @@ -45,7 +45,7 @@ export async function pagerDutyApiRequest(this: IExecuteFunctions | IWebhookFunc options.headers = Object.assign({}, options.headers, headers); try { - if (authenticationMethod === 'accessToken') { + if (authenticationMethod === 'apiToken') { const credentials = this.getCredentials('pagerDutyApi'); if (credentials === undefined) { diff --git a/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts b/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts index f9b8b1e3c70..8d62b8fc6fe 100644 --- a/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts +++ b/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts @@ -69,7 +69,7 @@ export class PagerDuty implements INodeType { displayOptions: { show: { authentication: [ - 'accessToken', + 'apiToken', ], }, }, @@ -93,15 +93,15 @@ export class PagerDuty implements INodeType { type: 'options', options: [ { - name: 'Access Token', - value: 'accessToken', + name: 'API Token', + value: 'apiToken', }, { name: 'OAuth2', value: 'oAuth2', }, ], - default: 'accessToken', + default: 'apiToken', }, { displayName: 'Resource', From 136596f46596afbaf17c0373ff1085cea17845e7 Mon Sep 17 00:00:00 2001 From: shraddha shaligram Date: Mon, 15 Jun 2020 15:47:44 -0700 Subject: [PATCH 090/120] add Google tasks API --- .../GoogleTasksOAuth2Api.credentials.ts | 17 + .../nodes/Google/Calendar/EventDescription.ts | 627 ++++++++---------- .../Google/Calendar/GoogleCalendar.node.ts | 244 ++++--- .../nodes/Google/Task/GenericFunctions.ts | 83 +++ .../nodes/Google/Task/GoogleTasks.node.ts | 290 ++++++++ .../nodes/Google/Task/TaskDescription.ts | 539 +++++++++++++++ .../nodes/Google/Task/googleTasks.png | Bin 0 -> 2540 bytes .../nodes/MessageBird/MessageBird.node.ts | 108 ++- packages/nodes-base/package.json | 2 + 9 files changed, 1429 insertions(+), 481 deletions(-) create mode 100644 packages/nodes-base/credentials/GoogleTasksOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Google/Task/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts create mode 100644 packages/nodes-base/nodes/Google/Task/TaskDescription.ts create mode 100644 packages/nodes-base/nodes/Google/Task/googleTasks.png diff --git a/packages/nodes-base/credentials/GoogleTasksOAuth2Api.credentials.ts b/packages/nodes-base/credentials/GoogleTasksOAuth2Api.credentials.ts new file mode 100644 index 00000000000..db307999201 --- /dev/null +++ b/packages/nodes-base/credentials/GoogleTasksOAuth2Api.credentials.ts @@ -0,0 +1,17 @@ +import { ICredentialType, NodePropertyTypes } from 'n8n-workflow'; + +const scopes = ['https://www.googleapis.com/auth/tasks']; + +export class GoogleTasksOAuth2Api implements ICredentialType { + name = 'googleTasksOAuth2Api'; + extends = ['googleOAuth2Api']; + displayName = 'Google Tasks OAuth2 API'; + properties = [ + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: scopes.join(' ') + } + ]; +} diff --git a/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts b/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts index e92582a847f..a7800334e5c 100644 --- a/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts +++ b/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const eventOperations = [ { @@ -7,67 +7,60 @@ export const eventOperations = [ type: 'options', displayOptions: { show: { - resource: [ - 'event', - ], - }, + resource: ['event'] + } }, options: [ { name: 'Create', value: 'create', - description: 'Add a event to calendar', + description: 'Add a event to calendar' }, { name: 'Delete', value: 'delete', - description: 'Delete an event', + description: 'Delete an event' }, { name: 'Get', value: 'get', - description: 'Retrieve an event', + description: 'Retrieve an event' }, { name: 'Get All', value: 'getAll', - description: 'Retrieve all events from a calendar', + description: 'Retrieve all events from a calendar' }, { name: 'Update', value: 'update', - description: 'Update an event', - }, + description: 'Update an event' + } ], default: 'create', - description: 'The operation to perform.', - }, + description: 'The operation to perform.' + } ] as INodeProperties[]; export const eventFields = [ - -/* -------------------------------------------------------------------------- */ -/* event:create */ -/* -------------------------------------------------------------------------- */ + /* -------------------------------------------------------------------------- */ + /* event:create */ + /* -------------------------------------------------------------------------- */ { displayName: 'Calendar', name: 'calendar', type: 'options', typeOptions: { - loadOptionsMethod: 'getCalendars', + loadOptionsMethod: 'getCalendars' }, required: true, displayOptions: { show: { - operation: [ - 'create', - ], - resource: [ - 'event', - ], - }, + operation: ['create'], + resource: ['event'] + } }, - default: '', + default: '' }, { displayName: 'Start', @@ -76,16 +69,12 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: [ - 'create', - ], - resource: [ - 'event', - ], - }, + operation: ['create'], + resource: ['event'] + } }, default: '', - description: 'Start time of the event.', + description: 'Start time of the event.' }, { displayName: 'End', @@ -94,16 +83,12 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: [ - 'create', - ], - resource: [ - 'event', - ], - }, + operation: ['create'], + resource: ['event'] + } }, default: '', - description: 'End time of the event.', + description: 'End time of the event.' }, { displayName: 'Use Default Reminders', @@ -111,15 +96,11 @@ export const eventFields = [ type: 'boolean', displayOptions: { show: { - operation: [ - 'create', - ], - resource: [ - 'event', - ], - }, + operation: ['create'], + resource: ['event'] + } }, - default: true, + default: true }, { displayName: 'Additional Fields', @@ -129,13 +110,9 @@ export const eventFields = [ default: {}, displayOptions: { show: { - operation: [ - 'create', - ], - resource: [ - 'event', - ], - }, + operation: ['create'], + resource: ['event'] + } }, options: [ { @@ -145,15 +122,15 @@ export const eventFields = [ options: [ { name: 'Yes', - value: 'yes', + value: 'yes' }, { name: 'No', - value: 'no', - }, + value: 'no' + } ], default: 'no', - description: 'Wheater the event is all day or not', + description: 'Wheater the event is all day or not' }, { displayName: 'Attendees', @@ -161,55 +138,57 @@ export const eventFields = [ type: 'string', typeOptions: { multipleValues: true, - multipleValueButtonText: 'Add Attendee', + multipleValueButtonText: 'Add Attendee' }, default: '', - description: 'The attendees of the event', + description: 'The attendees of the event' }, { displayName: 'Color', name: 'color', type: 'options', typeOptions: { - loadOptionsMethod: 'getColors', + loadOptionsMethod: 'getColors' }, default: '', - description: 'The color of the event.', + description: 'The color of the event.' }, { displayName: 'Guests Can Invite Others', name: 'guestsCanInviteOthers', type: 'boolean', default: true, - description: 'Whether attendees other than the organizer can invite others to the event', + description: + 'Whether attendees other than the organizer can invite others to the event' }, { displayName: 'Guests Can Modify', name: 'guestsCanModify', type: 'boolean', default: false, - description: 'Whether attendees other than the organizer can modify the event', + description: + 'Whether attendees other than the organizer can modify the event' }, { displayName: 'Guests Can See Other Guests', name: 'guestsCanSeeOtherGuests', type: 'boolean', default: true, - description: `Whether attendees other than the organizer can see who the event's attendees are.`, + description: `Whether attendees other than the organizer can see who the event's attendees are.` }, { displayName: 'ID', name: 'id', type: 'string', default: '', - description: 'Opaque identifier of the event', + description: 'Opaque identifier of the event' }, { displayName: 'Location', name: 'location', type: 'string', default: '', - description: 'Geographic location of the event as free-form text.', + description: 'Geographic location of the event as free-form text.' }, { displayName: 'Max Attendees', @@ -217,7 +196,7 @@ export const eventFields = [ type: 'number', default: 0, description: `The maximum number of attendees to include in the response.
- If there are more than the specified number of attendees, only the participant is returned`, + If there are more than the specified number of attendees, only the participant is returned` }, { displayName: 'Repeat Frecuency', @@ -226,37 +205,37 @@ export const eventFields = [ options: [ { name: 'Daily', - value: 'Daily', + value: 'Daily' }, { name: 'Weekly', - value: 'weekly', + value: 'weekly' }, { name: 'Monthly', - value: 'monthly', + value: 'monthly' }, { name: 'Yearly', - value: 'yearly', - }, + value: 'yearly' + } ], - default: '', + default: '' }, { displayName: 'Repeat Until', name: 'repeatUntil', type: 'dateTime', - default: '', + default: '' }, { displayName: 'Repeat How Many Times?', name: 'repeatHowManyTimes', type: 'number', typeOptions: { - minValue: 1, + minValue: 1 }, - default: 1, + default: 1 }, { displayName: 'Send Updates', @@ -266,28 +245,31 @@ export const eventFields = [ { name: 'All', value: 'all', - description: ' Notifications are sent to all guests', + description: ' Notifications are sent to all guests' }, { name: 'External Only', value: 'externalOnly', - description: 'Notifications are sent to non-Google Calendar guests only', + description: + 'Notifications are sent to non-Google Calendar guests only' }, { name: 'None', value: 'none', - description: ' No notifications are sent. This value should only be used for migration use case', - }, + description: + ' No notifications are sent. This value should only be used for migration use case' + } ], - description: 'Whether to send notifications about the creation of the new event', - default: '', + description: + 'Whether to send notifications about the creation of the new event', + default: '' }, { displayName: 'Summary', name: 'summary', type: 'string', default: '', - description: 'Title of the event.', + description: 'Title of the event.' }, { displayName: 'Show Me As', @@ -297,26 +279,27 @@ export const eventFields = [ { name: 'Available', value: 'transparent', - description: 'The event does not block time on the calendar', + description: 'The event does not block time on the calendar' }, { name: 'Busy', value: 'opaque', - description: ' The event does block time on the calendar.', - }, + description: ' The event does block time on the calendar.' + } ], default: 'opaque', - description: 'Whether the event blocks time on the calendar', + description: 'Whether the event blocks time on the calendar' }, { displayName: 'Timezone', name: 'timezone', type: 'options', typeOptions: { - loadOptionsMethod: 'getTimezones', + loadOptionsMethod: 'getTimezones' }, default: '', - description: 'The timezone the event will have set. By default events are schedule on timezone set in n8n.' + description: + 'The timezone the event will have set. By default events are schedule on timezone set in n8n.' }, { displayName: 'Visibility', @@ -326,28 +309,32 @@ export const eventFields = [ { name: 'Confidential', value: 'confidential', - description: 'The event is private. This value is provided for compatibility reasons.', + description: + 'The event is private. This value is provided for compatibility reasons.' }, { name: 'Default', value: 'default', - description: ' Uses the default visibility for events on the calendar.', + description: + ' Uses the default visibility for events on the calendar.' }, { name: 'Private', value: 'private', - description: 'The event is private and only event attendees may view event details.', + description: + 'The event is private and only event attendees may view event details.' }, { name: 'Public', value: 'public', - description: 'The event is public and event details are visible to all readers of the calendar.', - }, + description: + 'The event is public and event details are visible to all readers of the calendar.' + } ], default: 'default', - description: 'Visibility of the event.', - }, - ], + description: 'Visibility of the event.' + } + ] }, { displayName: 'Reminders', @@ -356,21 +343,15 @@ export const eventFields = [ default: '', placeholder: 'Add Reminder', typeOptions: { - multipleValues: true, + multipleValues: true }, required: false, displayOptions: { show: { - resource: [ - 'event', - ], - operation: [ - 'create', - ], - useDefaultReminders: [ - false, - ], - }, + resource: ['event'], + operation: ['create'], + useDefaultReminders: [false] + } }, options: [ { @@ -384,14 +365,14 @@ export const eventFields = [ options: [ { name: 'Email', - value: 'email', + value: 'email' }, { name: 'Popup', - value: 'popup', - }, + value: 'popup' + } ], - default: '', + default: '' }, { displayName: 'Minutes Before', @@ -399,37 +380,33 @@ export const eventFields = [ type: 'number', typeOptions: { minValue: 0, - maxValue: 40320, + maxValue: 40320 }, - default: 0, - }, - ], + default: 0 + } + ] } ], - description: `If the event doesn't use the default reminders, this lists the reminders specific to the event`, + description: `If the event doesn't use the default reminders, this lists the reminders specific to the event` }, -/* -------------------------------------------------------------------------- */ -/* event:delete */ -/* -------------------------------------------------------------------------- */ + /* -------------------------------------------------------------------------- */ + /* event:delete */ + /* -------------------------------------------------------------------------- */ { displayName: 'Calendar', name: 'calendar', type: 'options', typeOptions: { - loadOptionsMethod: 'getCalendars', + loadOptionsMethod: 'getCalendars' }, required: true, displayOptions: { show: { - operation: [ - 'delete', - ], - resource: [ - 'event', - ], - }, + operation: ['delete'], + resource: ['event'] + } }, - default: '', + default: '' }, { displayName: 'Event ID', @@ -438,15 +415,11 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: [ - 'delete', - ], - resource: [ - 'event', - ], - }, + operation: ['delete'], + resource: ['event'] + } }, - default: '', + default: '' }, { displayName: 'Options', @@ -456,13 +429,9 @@ export const eventFields = [ default: {}, displayOptions: { show: { - operation: [ - 'delete', - ], - resource: [ - 'event', - ], - }, + operation: ['delete'], + resource: ['event'] + } }, options: [ { @@ -473,46 +442,45 @@ export const eventFields = [ { name: 'All', value: 'all', - description: ' Notifications are sent to all guests', + description: ' Notifications are sent to all guests' }, { name: 'External Only', value: 'externalOnly', - description: 'Notifications are sent to non-Google Calendar guests only', + description: + 'Notifications are sent to non-Google Calendar guests only' }, { name: 'None', value: 'none', - description: ' No notifications are sent. This value should only be used for migration use case', - }, + description: + ' No notifications are sent. This value should only be used for migration use case' + } ], - description: 'Whether to send notifications about the creation of the new event', - default: '', - }, - ], + description: + 'Whether to send notifications about the creation of the new event', + default: '' + } + ] }, -/* -------------------------------------------------------------------------- */ -/* event:get */ -/* -------------------------------------------------------------------------- */ + /* -------------------------------------------------------------------------- */ + /* event:get */ + /* -------------------------------------------------------------------------- */ { displayName: 'Calendar', name: 'calendar', type: 'options', typeOptions: { - loadOptionsMethod: 'getCalendars', + loadOptionsMethod: 'getCalendars' }, required: true, displayOptions: { show: { - operation: [ - 'get', - ], - resource: [ - 'event', - ], - }, + operation: ['get'], + resource: ['event'] + } }, - default: '', + default: '' }, { displayName: 'Event ID', @@ -521,15 +489,11 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: [ - 'get', - ], - resource: [ - 'event', - ], - }, + operation: ['get'], + resource: ['event'] + } }, - default: '', + default: '' }, { displayName: 'Options', @@ -539,13 +503,9 @@ export const eventFields = [ default: {}, displayOptions: { show: { - operation: [ - 'get', - ], - resource: [ - 'event', - ], - }, + operation: ['get'], + resource: ['event'] + } }, options: [ { @@ -554,42 +514,38 @@ export const eventFields = [ type: 'number', default: 0, description: `The maximum number of attendees to include in the response.
- If there are more than the specified number of attendees, only the participant is returned`, + If there are more than the specified number of attendees, only the participant is returned` }, { displayName: 'Timezone', name: 'timeZone', type: 'options', typeOptions: { - loadOptionsMethod: 'getTimezones', + loadOptionsMethod: 'getTimezones' }, default: '', - description: `Time zone used in the response. The default is the time zone of the calendar.`, - }, - ], + description: `Time zone used in the response. The default is the time zone of the calendar.` + } + ] }, -/* -------------------------------------------------------------------------- */ -/* event:getAll */ -/* -------------------------------------------------------------------------- */ + /* -------------------------------------------------------------------------- */ + /* event:getAll */ + /* -------------------------------------------------------------------------- */ { displayName: 'Calendar', name: 'calendar', type: 'options', typeOptions: { - loadOptionsMethod: 'getCalendars', + loadOptionsMethod: 'getCalendars' }, required: true, displayOptions: { show: { - operation: [ - 'getAll', - ], - resource: [ - 'event', - ], - }, + operation: ['getAll'], + resource: ['event'] + } }, - default: '', + default: '' }, { displayName: 'Return All', @@ -597,16 +553,13 @@ export const eventFields = [ type: 'boolean', displayOptions: { show: { - operation: [ - 'getAll', - ], - resource: [ - 'event', - ], - }, + operation: ['getAll'], + resource: ['event'] + } }, default: false, - description: 'If all results should be returned or only up to a given limit.', + description: + 'If all results should be returned or only up to a given limit.' }, { displayName: 'Limit', @@ -614,23 +567,17 @@ export const eventFields = [ type: 'number', displayOptions: { show: { - operation: [ - 'getAll', - ], - resource: [ - 'event', - ], - returnAll: [ - false, - ], - }, + operation: ['getAll'], + resource: ['event'], + returnAll: [false] + } }, typeOptions: { minValue: 1, - maxValue: 500, + maxValue: 500 }, default: 100, - description: 'How many results to return.', + description: 'How many results to return.' }, { displayName: 'Options', @@ -640,13 +587,9 @@ export const eventFields = [ default: {}, displayOptions: { show: { - operation: [ - 'getAll', - ], - resource: [ - 'event', - ], - }, + operation: ['getAll'], + resource: ['event'] + } }, options: [ { @@ -654,7 +597,8 @@ export const eventFields = [ name: 'iCalUID', type: 'string', default: '', - description: 'Specifies event ID in the iCalendar format to be included in the response', + description: + 'Specifies event ID in the iCalendar format to be included in the response' }, { displayName: 'Max Attendees', @@ -662,7 +606,7 @@ export const eventFields = [ type: 'number', default: 0, description: `The maximum number of attendees to include in the response.
- If there are more than the specified number of attendees, only the participant is returned`, + If there are more than the specified number of attendees, only the participant is returned` }, { displayName: 'Order By', @@ -672,37 +616,40 @@ export const eventFields = [ { name: 'Start Time', value: 'startTime', - description: 'Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEvents is True)', + description: + 'Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEvents is True)' }, { name: 'Updated', value: 'updated', - description: 'Order by last modification time (ascending).', - }, + description: 'Order by last modification time (ascending).' + } ], default: '', - description: 'The order of the events returned in the result.', + description: 'The order of the events returned in the result.' }, { displayName: 'Query', name: 'query', type: 'string', default: '', - description: 'Free text search terms to find events that match these terms in any field, except for extended properties.', + description: + 'Free text search terms to find events that match these terms in any field, except for extended properties.' }, { displayName: 'Show Deleted', name: 'showDeleted', type: 'boolean', default: false, - description: 'Whether to include deleted events (with status equals "cancelled") in the result.', + description: + 'Whether to include deleted events (with status equals "cancelled") in the result.' }, { displayName: 'Show Hidden Invitations', name: 'showHiddenInvitations', type: 'boolean', default: false, - description: 'Whether to include hidden invitations in the result.', + description: 'Whether to include hidden invitations in the result.' }, { displayName: 'Single Events', @@ -710,31 +657,31 @@ export const eventFields = [ type: 'boolean', default: false, description: `Whether to expand recurring events into instances and only return single one-off
- events and instances of recurring events, but not the underlying recurring events themselves.`, + events and instances of recurring events, but not the underlying recurring events themselves.` }, { displayName: 'Time Max', name: 'timeMax', type: 'dateTime', default: '', - description: `Upper bound (exclusive) for an event's start time to filter by`, + description: `Upper bound (exclusive) for an event's start time to filter by` }, { displayName: 'Time Min', name: 'timeMin', type: 'dateTime', default: '', - description: `Lower bound (exclusive) for an event's end time to filter by`, + description: `Lower bound (exclusive) for an event's end time to filter by` }, { displayName: 'Timezone', name: 'timeZone', type: 'options', typeOptions: { - loadOptionsMethod: 'getTimezones', + loadOptionsMethod: 'getTimezones' }, default: '', - description: `Time zone used in the response. The default is the time zone of the calendar.`, + description: `Time zone used in the response. The default is the time zone of the calendar.` }, { displayName: 'Updated Min', @@ -742,32 +689,28 @@ export const eventFields = [ type: 'dateTime', default: '', description: `Lower bound for an event's last modification time (as a RFC3339 timestamp) to filter by. - When specified, entries deleted since this time will always be included regardless of showDeleted`, - }, - ], + When specified, entries deleted since this time will always be included regardless of showDeleted` + } + ] }, -/* -------------------------------------------------------------------------- */ -/* event:update */ -/* -------------------------------------------------------------------------- */ + /* -------------------------------------------------------------------------- */ + /* event:update */ + /* -------------------------------------------------------------------------- */ { displayName: 'Calendar', name: 'calendar', type: 'options', typeOptions: { - loadOptionsMethod: 'getCalendars', + loadOptionsMethod: 'getCalendars' }, required: true, displayOptions: { show: { - operation: [ - 'update', - ], - resource: [ - 'event', - ], - }, + operation: ['update'], + resource: ['event'] + } }, - default: '', + default: '' }, { displayName: 'Event ID', @@ -776,15 +719,11 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: [ - 'update', - ], - resource: [ - 'event', - ], - }, + operation: ['update'], + resource: ['event'] + } }, - default: '', + default: '' }, { displayName: 'Use Default Reminders', @@ -792,15 +731,11 @@ export const eventFields = [ type: 'boolean', displayOptions: { show: { - operation: [ - 'update', - ], - resource: [ - 'event', - ], - }, + operation: ['update'], + resource: ['event'] + } }, - default: true, + default: true }, { displayName: 'Update Fields', @@ -810,13 +745,9 @@ export const eventFields = [ default: {}, displayOptions: { show: { - operation: [ - 'update', - ], - resource: [ - 'event', - ], - }, + operation: ['update'], + resource: ['event'] + } }, options: [ { @@ -826,15 +757,15 @@ export const eventFields = [ options: [ { name: 'Yes', - value: 'yes', + value: 'yes' }, { name: 'No', - value: 'no', - }, + value: 'no' + } ], default: 'no', - description: 'Wheater the event is all day or not', + description: 'Wheater the event is all day or not' }, { displayName: 'Attendees', @@ -842,62 +773,64 @@ export const eventFields = [ type: 'string', typeOptions: { multipleValues: true, - multipleValueButtonText: 'Add Attendee', + multipleValueButtonText: 'Add Attendee' }, default: '', - description: 'The attendees of the event', + description: 'The attendees of the event' }, { displayName: 'Color', name: 'color', type: 'options', typeOptions: { - loadOptionsMethod: 'getColors', + loadOptionsMethod: 'getColors' }, default: '', - description: 'The color of the event.', + description: 'The color of the event.' }, { displayName: 'End', name: 'end', type: 'dateTime', default: '', - description: 'End time of the event.', + description: 'End time of the event.' }, { displayName: 'Guests Can Invite Others', name: 'guestsCanInviteOthers', type: 'boolean', default: true, - description: 'Whether attendees other than the organizer can invite others to the event', + description: + 'Whether attendees other than the organizer can invite others to the event' }, { displayName: 'Guests Can Modify', name: 'guestsCanModify', type: 'boolean', default: false, - description: 'Whether attendees other than the organizer can modify the event', + description: + 'Whether attendees other than the organizer can modify the event' }, { displayName: 'Guests Can See Other Guests', name: 'guestsCanSeeOtherGuests', type: 'boolean', default: true, - description: `Whether attendees other than the organizer can see who the event's attendees are.`, + description: `Whether attendees other than the organizer can see who the event's attendees are.` }, { displayName: 'ID', name: 'id', type: 'string', default: '', - description: 'Opaque identifier of the event', + description: 'Opaque identifier of the event' }, { displayName: 'Location', name: 'location', type: 'string', default: '', - description: 'Geographic location of the event as free-form text.', + description: 'Geographic location of the event as free-form text.' }, { displayName: 'Max Attendees', @@ -905,7 +838,7 @@ export const eventFields = [ type: 'number', default: 0, description: `The maximum number of attendees to include in the response.
- If there are more than the specified number of attendees, only the participant is returned`, + If there are more than the specified number of attendees, only the participant is returned` }, { displayName: 'Repeat Frecuency', @@ -914,44 +847,44 @@ export const eventFields = [ options: [ { name: 'Daily', - value: 'Daily', + value: 'Daily' }, { name: 'Weekly', - value: 'weekly', + value: 'weekly' }, { name: 'Monthly', - value: 'monthly', + value: 'monthly' }, { name: 'Yearly', - value: 'yearly', - }, + value: 'yearly' + } ], - default: '', + default: '' }, { displayName: 'Repeat Until', name: 'repeatUntil', type: 'dateTime', - default: '', + default: '' }, { displayName: 'Repeat How Many Times?', name: 'repeatHowManyTimes', type: 'number', typeOptions: { - minValue: 1, + minValue: 1 }, - default: 1, + default: 1 }, { displayName: 'Start', name: 'start', type: 'dateTime', default: '', - description: 'Start time of the event.', + description: 'Start time of the event.' }, { displayName: 'Send Updates', @@ -961,28 +894,31 @@ export const eventFields = [ { name: 'All', value: 'all', - description: ' Notifications are sent to all guests', + description: ' Notifications are sent to all guests' }, { name: 'External Only', value: 'externalOnly', - description: 'Notifications are sent to non-Google Calendar guests only', + description: + 'Notifications are sent to non-Google Calendar guests only' }, { name: 'None', value: 'none', - description: ' No notifications are sent. This value should only be used for migration use case', - }, + description: + ' No notifications are sent. This value should only be used for migration use case' + } ], - description: 'Whether to send notifications about the creation of the new event', - default: '', + description: + 'Whether to send notifications about the creation of the new event', + default: '' }, { displayName: 'Summary', name: 'summary', type: 'string', default: '', - description: 'Title of the event.', + description: 'Title of the event.' }, { displayName: 'Show Me As', @@ -992,26 +928,27 @@ export const eventFields = [ { name: 'Available', value: 'transparent', - description: 'The event does not block time on the calendar', + description: 'The event does not block time on the calendar' }, { name: 'Busy', value: 'opaque', - description: ' The event does block time on the calendar.', - }, + description: ' The event does block time on the calendar.' + } ], default: 'opaque', - description: 'Whether the event blocks time on the calendar', + description: 'Whether the event blocks time on the calendar' }, { displayName: 'Timezone', name: 'timezone', type: 'options', typeOptions: { - loadOptionsMethod: 'getTimezones', + loadOptionsMethod: 'getTimezones' }, default: '', - description: 'The timezone the event will have set. By default events are schedule on n8n timezone ' + description: + 'The timezone the event will have set. By default events are schedule on n8n timezone ' }, { displayName: 'Visibility', @@ -1021,28 +958,32 @@ export const eventFields = [ { name: 'Confidential', value: 'confidential', - description: 'The event is private. This value is provided for compatibility reasons.', + description: + 'The event is private. This value is provided for compatibility reasons.' }, { name: 'Default', value: 'default', - description: ' Uses the default visibility for events on the calendar.', + description: + ' Uses the default visibility for events on the calendar.' }, { name: 'Public', value: 'public', - description: 'The event is public and event details are visible to all readers of the calendar.', + description: + 'The event is public and event details are visible to all readers of the calendar.' }, { name: 'Private', value: 'private', - description: 'The event is private and only event attendees may view event details.', - }, + description: + 'The event is private and only event attendees may view event details.' + } ], default: 'default', - description: 'Visibility of the event.', - }, - ], + description: 'Visibility of the event.' + } + ] }, { displayName: 'Reminders', @@ -1051,21 +992,15 @@ export const eventFields = [ default: '', placeholder: 'Add Reminder', typeOptions: { - multipleValues: true, + multipleValues: true }, required: false, displayOptions: { show: { - resource: [ - 'event', - ], - operation: [ - 'update', - ], - useDefaultReminders: [ - false, - ], - }, + resource: ['event'], + operation: ['update'], + useDefaultReminders: [false] + } }, options: [ { @@ -1079,14 +1014,14 @@ export const eventFields = [ options: [ { name: 'Email', - value: 'email', + value: 'email' }, { name: 'Popup', - value: 'popup', - }, + value: 'popup' + } ], - default: '', + default: '' }, { displayName: 'Minutes Before', @@ -1094,13 +1029,13 @@ export const eventFields = [ type: 'number', typeOptions: { minValue: 0, - maxValue: 40320, + maxValue: 40320 }, - default: 0, - }, - ], + default: 0 + } + ] } ], - description: `If the event doesn't use the default reminders, this lists the reminders specific to the event`, - }, + description: `If the event doesn't use the default reminders, this lists the reminders specific to the event` + } ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts index 1f185935d7b..53d3d9de838 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts @@ -1,6 +1,4 @@ -import { - IExecuteFunctions, -} from 'n8n-core'; +import { IExecuteFunctions } from 'n8n-core'; import { IDataObject, @@ -8,22 +6,14 @@ import { INodeTypeDescription, INodeType, ILoadOptionsFunctions, - INodePropertyOptions, + INodePropertyOptions } from 'n8n-workflow'; -import { - googleApiRequest, - googleApiRequestAllItems, -} from './GenericFunctions'; +import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions'; -import { - eventOperations, - eventFields, -} from './EventDescription'; +import { eventOperations, eventFields } from './EventDescription'; -import { - IEvent, -} from './EventInterface'; +import { IEvent } from './EventInterface'; import * as moment from 'moment-timezone'; @@ -38,15 +28,15 @@ export class GoogleCalendar implements INodeType { description: 'Consume Google Calendar API.', defaults: { name: 'Google Calendar', - color: '#3E87E4', + color: '#3E87E4' }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'googleCalendarOAuth2Api', - required: true, - }, + required: true + } ], properties: [ { @@ -56,70 +46,85 @@ export class GoogleCalendar implements INodeType { options: [ { name: 'Event', - value: 'event', - }, + value: 'event' + } ], default: 'event', - description: 'The resource to operate on.', + description: 'The resource to operate on.' }, ...eventOperations, - ...eventFields, - ], + ...eventFields + ] }; methods = { loadOptions: { // Get all the calendars to display them to user so that he can // select them easily - async getCalendars(this: ILoadOptionsFunctions): Promise { + async getCalendars( + this: ILoadOptionsFunctions + ): Promise { const returnData: INodePropertyOptions[] = []; - const calendars = await googleApiRequestAllItems.call(this, 'items', 'GET', '/calendar/v3/users/me/calendarList'); + const calendars = await googleApiRequestAllItems.call( + this, + 'items', + 'GET', + '/calendar/v3/users/me/calendarList' + ); for (const calendar of calendars) { const calendarName = calendar.summary; const calendarId = calendar.id; returnData.push({ name: calendarName, - value: calendarId, + value: calendarId }); } return returnData; }, // Get all the colors to display them to user so that he can // select them easily - async getColors(this: ILoadOptionsFunctions): Promise { + async getColors( + this: ILoadOptionsFunctions + ): Promise { const returnData: INodePropertyOptions[] = []; - const { calendar } = await googleApiRequest.call(this, 'GET', '/calendar/v3/colors'); - for (const key of Object.keys(calendar)) { + const { calendar } = await googleApiRequest.call( + this, + 'GET', + '/calendar/v3/colors' + ); + for (const key of Object.keys(calendar)) { const colorName = calendar[key].background; const colorId = key; returnData.push({ name: `${colorName} - ${colorId}`, - value: colorId, + value: colorId }); } return returnData; }, // Get all the timezones to display them to user so that he can // select them easily - async getTimezones(this: ILoadOptionsFunctions): Promise { + async getTimezones( + this: ILoadOptionsFunctions + ): Promise { const returnData: INodePropertyOptions[] = []; for (const timezone of moment.tz.names()) { const timezoneName = timezone; const timezoneId = timezone; returnData.push({ name: timezoneName, - value: timezoneId, + value: timezoneId }); } return returnData; - }, - }, + } + } }; async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: IDataObject[] = []; - const length = items.length as unknown as number; + const length = (items.length as unknown) as number; const qs: IDataObject = {}; let responseData; const resource = this.getNodeParameter('resource', 0) as string; @@ -131,8 +136,14 @@ export class GoogleCalendar implements INodeType { const calendarId = this.getNodeParameter('calendar', i) as string; const start = this.getNodeParameter('start', i) as string; const end = this.getNodeParameter('end', i) as string; - const useDefaultReminders = this.getNodeParameter('useDefaultReminders', i) as boolean; - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const useDefaultReminders = this.getNodeParameter( + 'useDefaultReminders', + i + ) as boolean; + const additionalFields = this.getNodeParameter( + 'additionalFields', + i + ) as IDataObject; if (additionalFields.maxAttendees) { qs.maxAttendees = additionalFields.maxAttendees as number; } @@ -145,17 +156,19 @@ export class GoogleCalendar implements INodeType { const body: IEvent = { start: { dateTime: start, - timeZone: additionalFields.timeZone || this.getTimezone(), + timeZone: additionalFields.timeZone || this.getTimezone() }, end: { dateTime: end, - timeZone: additionalFields.timeZone || this.getTimezone(), + timeZone: additionalFields.timeZone || this.getTimezone() } }; if (additionalFields.attendees) { - body.attendees = (additionalFields.attendees as string[]).map(attendee => { - return { email: attendee }; - }); + body.attendees = (additionalFields.attendees as string[]).map( + attendee => { + return { email: attendee }; + } + ); } if (additionalFields.color) { body.colorId = additionalFields.color as string; @@ -188,9 +201,12 @@ export class GoogleCalendar implements INodeType { body.visibility = additionalFields.visibility as string; } if (!useDefaultReminders) { - const reminders = (this.getNodeParameter('remindersUi', i) as IDataObject).remindersValues as IDataObject[]; + const reminders = (this.getNodeParameter( + 'remindersUi', + i + ) as IDataObject).remindersValues as IDataObject[]; body.reminders = { - useDefault: false, + useDefault: false }; if (reminders) { body.reminders.overrides = reminders; @@ -198,32 +214,54 @@ export class GoogleCalendar implements INodeType { } if (additionalFields.allday) { body.start = { - date: moment(start).utc().format('YYYY-MM-DD'), + date: moment(start) + .utc() + .format('YYYY-MM-DD') }; body.end = { - date: moment(end).utc().format('YYYY-MM-DD'), + date: moment(end) + .utc() + .format('YYYY-MM-DD') }; } //exampel: RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=10;UNTIL=20110701T170000Z //https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html body.recurrence = []; - if (additionalFields.repeatHowManyTimes - && additionalFields.repeatUntil) { - throw new Error(`You can set either 'Repeat How Many Times' or 'Repeat Until' but not both`); + if ( + additionalFields.repeatHowManyTimes && + additionalFields.repeatUntil + ) { + throw new Error( + `You can set either 'Repeat How Many Times' or 'Repeat Until' but not both` + ); } if (additionalFields.repeatFrecuency) { - body.recurrence?.push(`FREQ=${(additionalFields.repeatFrecuency as string).toUpperCase()};`); + body.recurrence?.push( + `FREQ=${(additionalFields.repeatFrecuency as string).toUpperCase()};` + ); } if (additionalFields.repeatHowManyTimes) { - body.recurrence?.push(`COUNT=${additionalFields.repeatHowManyTimes};`); + body.recurrence?.push( + `COUNT=${additionalFields.repeatHowManyTimes};` + ); } if (additionalFields.repeatUntil) { - body.recurrence?.push(`UNTIL=${moment(additionalFields.repeatUntil as string).utc().format('YYYYMMDDTHHmmss')}Z`); + body.recurrence?.push( + `UNTIL=${moment(additionalFields.repeatUntil as string) + .utc() + .format('YYYYMMDDTHHmmss')}Z` + ); } if (body.recurrence.length !== 0) { body.recurrence = [`RRULE:${body.recurrence.join('')}`]; } - responseData = await googleApiRequest.call(this, 'POST', `/calendar/v3/calendars/${calendarId}/events`, body, qs); + responseData = await googleApiRequest.call( + this, + 'POST', + `/calendar/v3/calendars/${calendarId}/events`, + body, + qs + ); } //https://developers.google.com/calendar/v3/reference/events/delete if (operation === 'delete') { @@ -233,8 +271,13 @@ export class GoogleCalendar implements INodeType { if (options.sendUpdates) { qs.sendUpdates = options.sendUpdates as number; } - responseData = await googleApiRequest.call(this, 'DELETE', `/calendar/v3/calendars/${calendarId}/events/${eventId}`, {}); - responseData = { success: true }; + responseData = await googleApiRequest.call( + this, + 'DELETE', + `/calendar/v3/calendars/${calendarId}/events/${eventId}`, + {} + ); + responseData = { success: true }; } //https://developers.google.com/calendar/v3/reference/events/get if (operation === 'get') { @@ -247,7 +290,13 @@ export class GoogleCalendar implements INodeType { if (options.timeZone) { qs.timeZone = options.timeZone as string; } - responseData = await googleApiRequest.call(this, 'GET', `/calendar/v3/calendars/${calendarId}/events/${eventId}`, {}, qs); + responseData = await googleApiRequest.call( + this, + 'GET', + `/calendar/v3/calendars/${calendarId}/events/${eventId}`, + {}, + qs + ); } //https://developers.google.com/calendar/v3/reference/events/list if (operation === 'getAll') { @@ -288,10 +337,23 @@ export class GoogleCalendar implements INodeType { qs.updatedMin = options.updatedMin as string; } if (returnAll) { - responseData = await googleApiRequestAllItems.call(this, 'items', 'GET', `/calendar/v3/calendars/${calendarId}/events`, {}, qs); + responseData = await googleApiRequestAllItems.call( + this, + 'items', + 'GET', + `/calendar/v3/calendars/${calendarId}/events`, + {}, + qs + ); } else { qs.maxResults = this.getNodeParameter('limit', i) as number; - responseData = await googleApiRequest.call(this, 'GET', `/calendar/v3/calendars/${calendarId}/events`, {}, qs); + responseData = await googleApiRequest.call( + this, + 'GET', + `/calendar/v3/calendars/${calendarId}/events`, + {}, + qs + ); responseData = responseData.items; } } @@ -299,8 +361,14 @@ export class GoogleCalendar implements INodeType { if (operation === 'update') { const calendarId = this.getNodeParameter('calendar', i) as string; const eventId = this.getNodeParameter('eventId', i) as string; - const useDefaultReminders = this.getNodeParameter('useDefaultReminders', i) as boolean; - const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const useDefaultReminders = this.getNodeParameter( + 'useDefaultReminders', + i + ) as boolean; + const updateFields = this.getNodeParameter( + 'updateFields', + i + ) as IDataObject; if (updateFields.maxAttendees) { qs.maxAttendees = updateFields.maxAttendees as number; } @@ -314,19 +382,21 @@ export class GoogleCalendar implements INodeType { if (updateFields.start) { body.start = { dateTime: updateFields.start, - timeZone: updateFields.timeZone || this.getTimezone(), + timeZone: updateFields.timeZone || this.getTimezone() }; } if (updateFields.end) { body.end = { dateTime: updateFields.end, - timeZone: updateFields.timeZone || this.getTimezone(), + timeZone: updateFields.timeZone || this.getTimezone() }; } if (updateFields.attendees) { - body.attendees = (updateFields.attendees as string[]).map(attendee => { - return { email: attendee }; - }); + body.attendees = (updateFields.attendees as string[]).map( + attendee => { + return { email: attendee }; + } + ); } if (updateFields.color) { body.colorId = updateFields.color as string; @@ -359,46 +429,64 @@ export class GoogleCalendar implements INodeType { body.visibility = updateFields.visibility as string; } if (!useDefaultReminders) { - const reminders = (this.getNodeParameter('remindersUi', i) as IDataObject).remindersValues as IDataObject[]; + const reminders = (this.getNodeParameter( + 'remindersUi', + i + ) as IDataObject).remindersValues as IDataObject[]; body.reminders = { - useDefault: false, + useDefault: false }; if (reminders) { body.reminders.overrides = reminders; } } - if (updateFields.allday - && updateFields.start - && updateFields.end) { + if (updateFields.allday && updateFields.start && updateFields.end) { body.start = { - date: moment(updateFields.start as string).utc().format('YYYY-MM-DD'), + date: moment(updateFields.start as string) + .utc() + .format('YYYY-MM-DD') }; body.end = { - date: moment(updateFields.end as string).utc().format('YYYY-MM-DD'), + date: moment(updateFields.end as string) + .utc() + .format('YYYY-MM-DD') }; } //exampel: RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=10;UNTIL=20110701T170000Z //https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html body.recurrence = []; - if (updateFields.repeatHowManyTimes - && updateFields.repeatUntil) { - throw new Error(`You can set either 'Repeat How Many Times' or 'Repeat Until' but not both`); + if (updateFields.repeatHowManyTimes && updateFields.repeatUntil) { + throw new Error( + `You can set either 'Repeat How Many Times' or 'Repeat Until' but not both` + ); } if (updateFields.repeatFrecuency) { - body.recurrence?.push(`FREQ=${(updateFields.repeatFrecuency as string).toUpperCase()};`); + body.recurrence?.push( + `FREQ=${(updateFields.repeatFrecuency as string).toUpperCase()};` + ); } if (updateFields.repeatHowManyTimes) { body.recurrence?.push(`COUNT=${updateFields.repeatHowManyTimes};`); } if (updateFields.repeatUntil) { - body.recurrence?.push(`UNTIL=${moment(updateFields.repeatUntil as string).utc().format('YYYYMMDDTHHmmss')}Z`); + body.recurrence?.push( + `UNTIL=${moment(updateFields.repeatUntil as string) + .utc() + .format('YYYYMMDDTHHmmss')}Z` + ); } if (body.recurrence.length !== 0) { body.recurrence = [`RRULE:${body.recurrence.join('')}`]; } else { delete body.recurrence; } - responseData = await googleApiRequest.call(this, 'PATCH', `/calendar/v3/calendars/${calendarId}/events/${eventId}`, body, qs); + responseData = await googleApiRequest.call( + this, + 'PATCH', + `/calendar/v3/calendars/${calendarId}/events/${eventId}`, + body, + qs + ); } } } diff --git a/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts new file mode 100644 index 00000000000..19652d1d4af --- /dev/null +++ b/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts @@ -0,0 +1,83 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions +} from 'n8n-core'; + +import { IDataObject } from 'n8n-workflow'; + +export async function googleApiRequest( + this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + method: string, + resource: string, + body: any = {}, + qs: IDataObject = {}, + uri?: string, + headers: IDataObject = {} +): Promise { + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json' + }, + method, + body, + qs, + uri: uri || `https://www.googleapis.com${resource}`, + json: true + }; + try { + if (Object.keys(headers).length !== 0) { + options.headers = Object.assign({}, options.headers, headers); + } + if (Object.keys(body).length === 0) { + delete options.body; + } + //@ts-ignore + return await this.helpers.requestOAuth2.call( + this, + 'googleTasksOAuth2Api', + options + ); + } catch (error) { + if (error.response && error.response.body && error.response.body.message) { + // Try to return the error prettier + throw new Error( + `Google Tasks error response [${error.statusCode}]: ${error.response.body.message}` + ); + } + throw error; + } +} + +export async function googleApiRequestAllItems( + this: IExecuteFunctions | ILoadOptionsFunctions, + propertyName: string, + method: string, + endpoint: string, + body: any = {}, + query: IDataObject = {} +): Promise { + 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; +} diff --git a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts new file mode 100644 index 00000000000..450b3893768 --- /dev/null +++ b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts @@ -0,0 +1,290 @@ +import { IExecuteFunctions } from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeTypeDescription, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions +} from 'n8n-workflow'; +import { taskOperations, taskFields } from './TaskDescription'; + +import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions'; + +export class GoogleTasks implements INodeType { + description: INodeTypeDescription = { + displayName: 'Google Tasks', + name: 'googleTasks', + icon: 'file:googleTasks.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Google Tasks API.', + defaults: { + name: 'Google Tasks', + color: '#3E87E4' + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'googleTasksOAuth2Api', + required: true + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Task', + value: 'task' + } + ], + default: 'task', + description: 'The resource to operate on.' + }, + ...taskOperations, + ...taskFields + ] + }; + methods = { + loadOptions: { + // Get all the tasklists to display them to user so that he can select them easily + + async getTasks( + this: ILoadOptionsFunctions + ): Promise { + const returnData: INodePropertyOptions[] = []; + const tasks = await googleApiRequestAllItems.call( + this, + 'items', + 'GET', + '/tasks/v1/users/@me/lists' + ); + for (const task of tasks) { + const taskName = task.title; + const taskId = task.id; + returnData.push({ + name: taskName, + value: taskId + }); + } + return returnData; + } + } + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = (items.length as unknown) as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + let body: IDataObject = {}; + for (let i = 0; i < length; i++) { + if (resource === 'task') { + if (operation === 'create') { + body = {}; + //https://developers.google.com/tasks/v1/reference/tasks/insert + const taskId = this.getNodeParameter('task', i) as string; + const additionalFields = this.getNodeParameter( + 'additionalFields', + i + ) as IDataObject; + + if (additionalFields.parent) + qs.parent = additionalFields.parent as string; + + if (additionalFields.previous) + qs.previous = additionalFields.previous as string; + + if (additionalFields.links) { + body.links = (additionalFields.links as string[]).map(link => { + return { link: link }; + }); + } + if (additionalFields.status) + body.status = additionalFields.status as string; + + if (additionalFields.notes) + body.notes = additionalFields.notes as string; + + if (additionalFields.title) + body.title = additionalFields.title as string; + + if (additionalFields.dueDate) + body.dueDate = additionalFields.dueDate as string; + + if (additionalFields.completed) + body.completed = additionalFields.completed as string; + + if (additionalFields.deleted) + body.deleted = additionalFields.deleted as boolean; + + if (additionalFields.hidden) + body.hidden = additionalFields.hidden as boolean; + + if (additionalFields.position) + body.position = additionalFields.position as string; + + if (additionalFields.selfLink) + body.selfLink = additionalFields.selfLink as string; + + responseData = await googleApiRequest.call( + this, + 'POST', + `/tasks/v1/lists/${taskId}/tasks`, + body, + qs + ); + } + if (operation == 'delete') { + //https://developers.google.com/tasks/v1/reference/tasks/delete + const taskListId = this.getNodeParameter('task', i) as string; + const taskId = this.getNodeParameter('taskId', i) as string; + + responseData = await googleApiRequest.call( + this, + 'DELETE', + `/tasks/v1/lists/${taskListId}/tasks/${taskId}`, + {} + ); + responseData = { success: true }; + } + if (operation === 'get') { + //https://developers.google.com/tasks/v1/reference/tasks/get + const taskListId = this.getNodeParameter('task', i) as string; + const taskId = this.getNodeParameter('taskId', i) as string; + responseData = await googleApiRequest.call( + this, + 'GET', + `/tasks/v1/lists/${taskListId}/tasks/${taskId}`, + {}, + qs + ); + } + if (operation === 'getAll') { + //https://developers.google.com/tasks/v1/reference/tasks/list + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const taskListId = this.getNodeParameter('task', i) as string; + const options = this.getNodeParameter( + 'additionalFields', + i + ) as IDataObject; + if (options.completedMax) { + qs.completedMax = options.completedMax as string; + } + if (options.completedMin) { + qs.completedMin = options.completedMin as string; + } + if (options.dueMin) { + qs.dueMin = options.dueMin as string; + } + if (options.dueMax) { + qs.dueMax = options.dueMax as string; + } + if (options.pageToken) { + qs.pageToken = options.pageToken as string; + } + if (options.showCompleted) { + qs.showCompleted = options.showCompleted as boolean; + } + if (options.showDeleted) { + qs.showDeleted = options.showDeleted as boolean; + } + if (options.showHidden) { + qs.showHidden = options.showHidden as boolean; + } + + if (options.updatedMin) { + qs.updatedMin = options.updatedMin as string; + } + if (returnAll) { + responseData = await googleApiRequestAllItems.call( + this, + 'items', + 'GET', + `/tasks/v1/lists/${taskListId}/tasks`, + {}, + qs + ); + } else { + qs.maxResults = this.getNodeParameter('limit', i) as number; + responseData = await googleApiRequest.call( + this, + 'GET', + `/tasks/v1/lists/${taskListId}/tasks`, + {}, + qs + ); + responseData = responseData.items; + } + } + if (operation == 'update') { + body = {}; + //https://developers.google.com/tasks/v1/reference/tasks/patch + const taskListId = this.getNodeParameter('task', i) as string; + const taskId = this.getNodeParameter('taskId', i) as string; + const updateFields = this.getNodeParameter( + 'updateFields', + i + ) as IDataObject; + + if (updateFields.parent) qs.parent = updateFields.parent as string; + + if (updateFields.previous) + qs.previous = updateFields.previous as string; + + if (updateFields.links) { + body.links = (updateFields.links as string[]).map(link => { + return { link: link }; + }); + } + if (updateFields.status) body.status = updateFields.status as string; + + if (updateFields.notes) body.notes = updateFields.notes as string; + + if (updateFields.title) body.title = updateFields.title as string; + + if (updateFields.dueDate) + body.dueDate = updateFields.dueDate as string; + + if (updateFields.completed) + body.completed = updateFields.completed as string; + + if (updateFields.deleted) + body.deleted = updateFields.deleted as boolean; + + if (updateFields.hidden) body.hidden = updateFields.hidden as boolean; + + if (updateFields.position) + body.position = updateFields.position as string; + + if (updateFields.selfLink) + body.selfLink = updateFields.selfLink as string; + + responseData = await googleApiRequest.call( + this, + 'PATCH', + `/tasks/v1/lists/${taskListId}/tasks/${taskId}`, + body, + qs + ); + } + } + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Google/Task/TaskDescription.ts b/packages/nodes-base/nodes/Google/Task/TaskDescription.ts new file mode 100644 index 00000000000..24c5b9018bc --- /dev/null +++ b/packages/nodes-base/nodes/Google/Task/TaskDescription.ts @@ -0,0 +1,539 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const taskOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: ['task'] + } + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Add a task to tasklist' + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a task' + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a task' + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all tasks from a tasklist' + }, + { + name: 'Update', + value: 'update', + description: 'Update a task' + } + ], + default: 'create', + description: 'The operation to perform.' + } +] as INodeProperties[]; + +export const taskFields = [ + /* -------------------------------------------------------------------------- */ + /* task:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'TaskList', + name: 'task', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTasks' + }, + required: true, + displayOptions: { + show: { + operation: ['create'], + resource: ['task'] + } + }, + default: '' + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: ['create'], + resource: ['task'] + } + }, + options: [ + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'needs Action', + value: 'needsAction' + }, + { + name: 'completed', + value: 'completed' + } + ], + default: '', + description: 'Current status of the task.' + }, + { + displayName: 'Links', + name: 'links', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Link' + }, + default: '', + description: 'The links to insert in the task.' + }, + + { + displayName: 'Notes', + name: 'notes', + type: 'string', + default: '', + description: 'Additional Notes.' + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the task.' + }, + { + displayName: 'Due Date', + name: 'dueDate', + type: 'dateTime', + default: '', + description: 'Due date of the task.' + }, + { + displayName: 'Completion date', + name: 'completed', + type: 'dateTime', + default: '', + description: `Completion date of the task (as a RFC 3339 timestamp). This field is omitted if the task has not been completed.` + }, + + { + displayName: 'Deleted status', + name: 'deleted', + type: 'boolean', + default: false, + description: 'Flag indicating whether the task has been deleted.' + }, + { + displayName: 'Hidden', + name: 'hidden', + type: 'boolean', + default: false, + description: 'Flag indicating whether the task is hidden.' + }, + { + displayName: 'Parent', + name: 'parent', + type: 'string', + default: '', + description: + 'Parent task identifier.This field is omitted if it is a top-level task.' + }, + { + displayName: 'Position', + name: 'position', + type: 'string', + default: '', + description: + 'Parent task identifier.This field is omitted if it is a top-level task.' + }, + { + displayName: 'Self Link', + name: 'selfLink', + type: 'string', + default: '', + description: + 'URL pointing to this task. Used to retrieve, update, or delete this task.' + }, + { + displayName: 'Previous', + name: 'previous', + type: 'string', + default: '', + description: + 'Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.' + } + ] + }, + /* -------------------------------------------------------------------------- */ + /* task:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'TaskList', + name: 'task', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTasks' + }, + required: true, + displayOptions: { + show: { + operation: ['delete'], + resource: ['task'] + } + }, + default: '' + }, + { + displayName: 'Task ID', + name: 'taskId', + type: 'string', + required: true, + displayOptions: { + show: { + operation: ['delete'], + resource: ['task'] + } + }, + default: '' + }, + /* -------------------------------------------------------------------------- */ + /* task:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'TaskList', + name: 'task', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTasks' + }, + required: true, + displayOptions: { + show: { + operation: ['get'], + resource: ['task'] + } + }, + default: '' + }, + { + displayName: 'Task ID', + name: 'taskId', + type: 'string', + required: true, + displayOptions: { + show: { + operation: ['get'], + resource: ['task'] + } + }, + default: '' + }, + /* -------------------------------------------------------------------------- */ + /* task:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'TaskList', + name: 'task', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTasks' + }, + required: true, + displayOptions: { + show: { + operation: ['getAll'], + resource: ['task'] + } + }, + default: '' + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: ['getAll'], + resource: ['task'] + } + }, + default: false, + description: + 'If all results should be returned or only up to a given limit.' + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: ['getAll'], + resource: ['task'], + returnAll: [false] + } + }, + typeOptions: { + minValue: 1, + maxValue: 100 + }, + default: 20, + description: 'How many results to return.' + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: ['getAll'], + resource: ['task'] + } + }, + + options: [ + { + displayName: 'Completed Max', + name: 'completedMax', + type: 'dateTime', + default: '', + description: + 'Upper bound for a task completion date (as a RFC 3339 timestamp) to filter by.' + }, + { + displayName: 'Completed Min', + name: 'completedMin', + type: 'dateTime', + default: '', + description: + 'Lower bound for a task completion date (as a RFC 3339 timestamp) to filter by.' + }, + { + displayName: 'Due Min', + name: 'dueMin', + type: 'dateTime', + default: '', + description: + 'Lower bound for a task due date (as a RFC 3339 timestamp) to filter by.' + }, + { + displayName: 'Due Max', + name: 'dueMax', + type: 'dateTime', + default: '', + description: + 'Upper bound for a task due date (as a RFC 3339 timestamp) to filter by.' + }, + + { + displayName: 'Page Token', + name: 'pageToken', + type: 'string', + default: '', + description: 'Token specifying the result page to return.' + }, + + { + displayName: 'Show Completed', + name: 'showCompleted', + type: 'boolean', + default: true, + description: + 'Flag indicating whether completed tasks are returned in the result' + }, + { + displayName: 'Show Deleted', + name: 'showDeleted', + type: 'boolean', + default: false, + description: + 'Flag indicating whether deleted tasks are returned in the result' + }, + { + displayName: 'Show Hidden', + name: 'showHidden', + type: 'boolean', + default: false, + description: + 'Flag indicating whether hidden tasks are returned in the result' + }, + { + displayName: 'updated Min', + name: 'updatedMin', + type: 'string', + + description: + 'Lower bound for a task last modification time (as a RFC 3339 timestamp) to filter by.' + } + ] + }, + /* -------------------------------------------------------------------------- */ + /* task:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'TaskList', + name: 'task', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTasks' + }, + required: true, + displayOptions: { + show: { + operation: ['update'], + resource: ['task'] + } + }, + default: '' + }, + { + displayName: 'Task ID', + name: 'taskId', + type: 'string', + required: true, + displayOptions: { + show: { + operation: ['update'], + resource: ['task'] + } + }, + default: '' + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Update Field', + default: {}, + displayOptions: { + show: { + operation: ['update'], + resource: ['task'] + } + }, + options: [ + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'needs Action', + value: 'needsAction' + }, + { + name: 'completed', + value: 'completed' + } + ], + default: '', + description: 'Current status of the task.' + }, + { + displayName: 'Links', + name: 'links', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Link' + }, + default: '', + description: 'The links to insert in the task.' + }, + + { + displayName: 'Notes', + name: 'notes', + type: 'string', + default: '', + description: 'Additional Notes.' + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the task.' + }, + { + displayName: 'Due Date', + name: 'dueDate', + type: 'dateTime', + default: '', + description: 'Due date of the task.' + }, + { + displayName: 'Completion date', + name: 'completed', + type: 'dateTime', + default: '', + description: `Completion date of the task (as a RFC 3339 timestamp). This field is omitted if the task has not been completed.` + }, + + { + displayName: 'Deleted status', + name: 'deleted', + type: 'boolean', + default: false, + description: 'Flag indicating whether the task has been deleted.' + }, + { + displayName: 'Hidden', + name: 'hidden', + type: 'boolean', + default: false, + description: 'Flag indicating whether the task is hidden.' + }, + { + displayName: 'Parent', + name: 'parent', + type: 'string', + default: '', + description: + 'Parent task identifier.This field is omitted if it is a top-level task.' + }, + { + displayName: 'Position', + name: 'position', + type: 'string', + default: '', + description: + 'Parent task identifier.This field is omitted if it is a top-level task.' + }, + { + displayName: 'Self Link', + name: 'selfLink', + type: 'string', + default: '', + description: + 'URL pointing to this task. Used to retrieve, update, or delete this task.' + }, + { + displayName: 'Previous', + name: 'previous', + type: 'string', + default: '', + description: + 'Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.' + } + ] + } +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Google/Task/googleTasks.png b/packages/nodes-base/nodes/Google/Task/googleTasks.png new file mode 100644 index 0000000000000000000000000000000000000000..f0339e5bfeef3f8b4f46a91af01ee4b437e1d341 GIT binary patch literal 2540 zcmZ{mXE@x862|{ny&XZc=q)zEvic%8s|UMc^}f*}h`NXtqDG4oH5{^{Eu!~MlxQ0q zD@2bHZIuu;$Cdl-ez?!PGw(Aq&u>0YJi<_$o`#bK004Sj9k}t0EB{U^ikqr*HS2R@ zAQyE5bpWVIp}le-zv=nVI>rV75G(=!5ibGY;${@_9RU0x0I+R;LuCN~+q2wOycGokd;HPED7qhMg;r|+Mwqyqq|16{bfiR1y~d)-P_=h7bADE8Tf8sTu%KSS0XTK(o2@$nvkjEizga! zOrH@TfmEF4aCv;n=UQdUuX~Ln#PRX*dD!W{6fa&ew}N@a7v$f*Bb>=as)zc2T%Bejg)6sEaz>Zd93H9#%osE-FWhlg(@)U?`(PF ziMHMxTk!-pz>va__j5?MS8&^?ykkkBmApSUTUYXk^kQ|xaP2vPtNgnGvcjya^RyS7 z1tFAc-3y+owcQCl$xLS^JNv0E7^BzQoodY@9NYoE{o`K^T^26F@r6q48Z-*sd&8%kN#M9K%y}_t4lvxJM|YGpjT{qI)|}-I#2HZ>JZ8iEnWDYtyvGXh z->!Uy*0l%Hy&hzFxVq4UG{3+9T}q{9DhUU=Sz|#BaCwPcDV3-~CFqMLWo{%xdVDIq zp@JFgtZZ<(jd?teY8Ju+#(3c+GNEK)=N^NNRd{~y;Akov#8U9KWHL$=+3HwgrW}lB zlWZ4*GB)4oX*e}ygRMlmahEzd<@)q&hMQU_^Ek5+vPoiVjP?C0yK%*}gS};Z*tjRq zV@(Uz{#Lm}7xsfDVwEutY&grO$28Y>uHrzl1kS8vY~EnP+Wy)+C$cvy4PxmY>h;>A<(_94wk9-8-8Kz9Oo<(POt^6Gb&6A}MtwxBqZz(Kt0B#SFVln=HJ# zBs=Pv=$vBzte}xcR%G>ZU7|2gjM+YuRPyb-jbe?0+x06#h-c~7C9FiZtD*yzvrsot zgKxN&gRFh46Fjain7cF0s^81ui>Y%Q-*MBy#0?p4leQlPN5>;x3u$R0%yX%pwqq{R zgXg-}5n|B^8g@~-tg0Oc4xZ=V8|K?Dl*V%}Z($pgW)0pEUr%I}*m7zoQG&(lC(UqF z>)Xda0_VCL;EJK|Wbdi~*o^3NGV4}X+J%6)qLy%q^xGsw{i#dFmX{gP3hv!036Fxj;PPz;dp0N(d!+W&znBD> zPOHObYl5b)dD#IZ;wL%|NgCBdE?V<^RY0iBAvDFmWCMn4>-g}Yp^|o@*pqH|hK}a; zW4|Llfzn~sXrJw>9`ls>T+%Sr0`r5Gz)%-|${5hjWqnKyAq9zFlEz&ohr0URd4akE z_5r4QHi9Ch*dKJC1!at$D*9SnX0`efHQHJ!>=7u2$H;1FvJ9aC&6R2#(T!dOMUF42 z8Z5lG6tvXPxs=i7vCRoA2qI-FbSaYjOuKc>h2^UxP1D)93Jjj1s|oIHIfTZ2!y?bZ zXJV2Yn1t-L=!b_He}}^1nP#r~nbj0X?PCwwamqwr|tP(boNa8e?JSd$QyU=pe7S3}BV+E2edTqJsBrwgx-9oQ`Jtp9P*SyX zS}S-p?RoFYJJD}P?BQLQ(2efFy@7gKFO`Udx0`IO#M-cDvIAMglQ^M<>adA2WH!?K zQ=?Et$dtL4-(cQI$}79YW5$Wur_GUz)9!z=X~Q4^FQfwK2Le+!KJ*j_Y-POXEPu6+ zaX4usoW?8#ZaWMxs(L$;q+Gec(*OKnadzm>EP+!^lA?@QPuMcJo+%KJ_5SogsO;BW zYEhyLfh%Yyp7P7}(P?s~74P_7-=^)U^!hI^>F_ZN{fd0pp!sv9h>wShLO3GlLffVN z%%_nchmjIhI*hXi;lCP~3nv*lVN!lgZl`tg4hj2dn)^L=@N-f^`#9YgAPtd*h(qMW yrDRN`p-NDwk`(kF1fm3iFs(&v{}15d_1Mih=>G%uEh?8c0HCXB2>+mAANe0Tk+m5B literal 0 HcmV?d00001 diff --git a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts index 9c74b6a3d92..5a7418a708f 100644 --- a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts +++ b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts @@ -1,17 +1,13 @@ -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 = { @@ -55,20 +51,18 @@ export class MessageBird implements INodeType { type: 'options', displayOptions: { show: { - resource: [ - 'sms', - ], - }, + resource: ['sms'] + } }, options: [ { 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.' }, // ---------------------------------- @@ -83,15 +77,11 @@ 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.', + description: 'The number from which to send the message.' }, { displayName: 'To', @@ -103,10 +93,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.' }, { @@ -117,15 +107,11 @@ export class MessageBird implements INodeType { required: true, displayOptions: { show: { - operation: [ - 'send', - ], - resource: [ - 'sms', - ], - }, + operation: ['send'], + resource: ['sms'] + } }, - description: 'The message to be send.', + description: 'The message to be send.' }, { displayName: 'Additional Fields', @@ -139,7 +125,8 @@ export class MessageBird implements INodeType { name: 'createdDatetime', type: 'dateTime', 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', @@ -148,26 +135,27 @@ export class MessageBird implements INodeType { options: [ { name: 'Auto', - value: 'auto', + value: 'auto' }, { name: 'Plain', - value: 'plain', + value: 'plain' }, { name: 'Unicode', - value: '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', @@ -175,7 +163,8 @@ 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', @@ -185,36 +174,39 @@ export class MessageBird implements INodeType { options: [ { name: 'Flash', - value: 1, + value: 1 }, { name: 'Normal', - value: 0, - }, + value: 0 + } ], default: 1, - description: 'Indicated the message type. 1 is a normal message, 0 is a flash message.', + 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.' }, { displayName: 'Scheduled Date-time', name: 'scheduledDatetime', type: 'dateTime', default: '', - 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', @@ -232,17 +224,19 @@ export class MessageBird implements INodeType { { 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', 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', @@ -250,13 +244,13 @@ export class MessageBird implements INodeType { type: 'number', 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 { diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 7566f70f06a..dc4b084109f 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -61,6 +61,7 @@ "dist/credentials/GoogleDriveOAuth2Api.credentials.js", "dist/credentials/GoogleOAuth2Api.credentials.js", "dist/credentials/GoogleSheetsOAuth2Api.credentials.js", + "dist/credentials/GoogleTasksOAuth2Api.credentials.js", "dist/credentials/GumroadApi.credentials.js", "dist/credentials/HarvestApi.credentials.js", "dist/credentials/HelpScoutOAuth2Api.credentials.js", @@ -194,6 +195,7 @@ "dist/nodes/Google/Calendar/GoogleCalendar.node.js", "dist/nodes/Google/Drive/GoogleDrive.node.js", "dist/nodes/Google/Sheet/GoogleSheets.node.js", + "dist/nodes/Google/Task/GoogleTasks.node.js", "dist/nodes/GraphQL/GraphQL.node.js", "dist/nodes/Gumroad/GumroadTrigger.node.js", "dist/nodes/Harvest/Harvest.node.js", From cd2dac76753e66481c366990f97f0282174a4122 Mon Sep 17 00:00:00 2001 From: ricardo Date: Mon, 15 Jun 2020 20:27:16 -0400 Subject: [PATCH 091/120] :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 649bde48372..dcfaeb2269d 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -131,6 +131,7 @@ "dist/credentials/UpleadApi.credentials.js", "dist/credentials/VeroApi.credentials.js", "dist/credentials/WebflowApi.credentials.js", + "dist/credentials/WebflowOAuth2Api.credentials.js", "dist/credentials/WooCommerceApi.credentials.js", "dist/credentials/WordpressApi.credentials.js", "dist/credentials/ZendeskApi.credentials.js", From 346ae8efc96e7f6f86522ba01b31a56170c6c405 Mon Sep 17 00:00:00 2001 From: ricardo Date: Mon, 15 Jun 2020 20:31:18 -0400 Subject: [PATCH 092/120] :zap: Improvements to Webflow-Node --- .../credentials/WebflowOAuth2Api.credentials.ts | 2 +- .../nodes-base/nodes/Webflow/GenericFunctions.ts | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/nodes-base/credentials/WebflowOAuth2Api.credentials.ts b/packages/nodes-base/credentials/WebflowOAuth2Api.credentials.ts index c19e4517417..ba5501910ce 100644 --- a/packages/nodes-base/credentials/WebflowOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/WebflowOAuth2Api.credentials.ts @@ -28,7 +28,7 @@ export class WebflowOAuth2Api implements ICredentialType { { displayName: 'Scope', name: 'scope', - type: 'string' as NodePropertyTypes, + type: 'hidden' as NodePropertyTypes, default: '', }, { diff --git a/packages/nodes-base/nodes/Webflow/GenericFunctions.ts b/packages/nodes-base/nodes/Webflow/GenericFunctions.ts index cc10d218a94..783b6748085 100644 --- a/packages/nodes-base/nodes/Webflow/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Webflow/GenericFunctions.ts @@ -1,4 +1,7 @@ -import { OptionsWithUri } from 'request'; +import { + OptionsWithUri, +} from 'request'; + import { IExecuteFunctions, IExecuteSingleFunctions, @@ -6,7 +9,10 @@ import { ILoadOptionsFunctions, IWebhookFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; + +import { + IDataObject, + } from 'n8n-workflow'; export async function webflowApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const authenticationMethod = this.getNodeParameter('authentication', 0); @@ -40,6 +46,9 @@ export async function webflowApiRequest(this: IHookFunctions | IExecuteFunctions return await this.helpers.requestOAuth2!.call(this, 'webflowOAuth2Api', options); } } catch (error) { - throw new Error('Webflow Error: ' + error.code + error.msg); + if (error.response.body.err) { + throw new Error(`Webflow Error: [${error.statusCode}]: ${error.response.body.err}`); + } + return error; } } From 9a449284aedb956bbe9aad94eb64f66f2392e510 Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Tue, 16 Jun 2020 11:45:47 +0200 Subject: [PATCH 093/120] Drift OAuth2 support --- .../credentials/DriftOAuth2Api.credentials.ts | 41 +++++++++++++++++++ packages/nodes-base/nodes/Drift/Drift.node.ts | 35 ++++++++++++++++ .../nodes/Drift/GenericFunctions.ts | 33 ++++++++------- packages/nodes-base/package.json | 1 + 4 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 packages/nodes-base/credentials/DriftOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/DriftOAuth2Api.credentials.ts b/packages/nodes-base/credentials/DriftOAuth2Api.credentials.ts new file mode 100644 index 00000000000..5b509c5981a --- /dev/null +++ b/packages/nodes-base/credentials/DriftOAuth2Api.credentials.ts @@ -0,0 +1,41 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class DriftOAuth2Api implements ICredentialType { + name = 'driftOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Drift OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'ttps://dev.drift.com/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://driftapi.com/oauth2/token', + required: true, + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Drift/Drift.node.ts b/packages/nodes-base/nodes/Drift/Drift.node.ts index 53a9a2695e6..46afa3d5190 100644 --- a/packages/nodes-base/nodes/Drift/Drift.node.ts +++ b/packages/nodes-base/nodes/Drift/Drift.node.ts @@ -37,9 +37,44 @@ export class Drift implements INodeType { { name: 'driftApi', required: true, + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'driftOAuth2Api', + 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 resource to operate on.', + }, { displayName: 'Resource', name: 'resource', diff --git a/packages/nodes-base/nodes/Drift/GenericFunctions.ts b/packages/nodes-base/nodes/Drift/GenericFunctions.ts index 47b38a86e35..c28d6bd5a21 100644 --- a/packages/nodes-base/nodes/Drift/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Drift/GenericFunctions.ts @@ -12,25 +12,15 @@ import { } from 'n8n-workflow'; export async function driftApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any - - const credentials = this.getCredentials('driftApi'); - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - - const endpoint = 'https://driftapi.com'; - let options: OptionsWithUri = { - headers: { - Authorization: `Bearer ${credentials.accessToken}`, - }, + headers: {}, method, body, qs: query, - uri: uri || `${endpoint}${resource}`, + uri: uri || `https://driftapi.com${resource}`, json: true }; + if (!Object.keys(body).length) { delete options.form; } @@ -38,8 +28,23 @@ export async function driftApiRequest(this: IExecuteFunctions | IWebhookFunction delete options.qs; } options = Object.assign({}, options, option); + + const authenticationMethod = this.getNodeParameter('authentication', 0); + try { - return await this.helpers.request!(options); + if (authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('driftApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + options.headers!['Authorization'] = `Bearer ${credentials.accessToken}`; + + return await this.helpers.request!(options); + } else { + return await this.helpers.requestOAuth2!.call(this, 'driftOAuth2Api', options); + } } catch (error) { if (error.response) { const errorMessage = error.message || (error.response.body && error.response.body.message ); diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index dcfaeb2269d..6c8c541998c 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -47,6 +47,7 @@ "dist/credentials/CalendlyApi.credentials.js", "dist/credentials/DisqusApi.credentials.js", "dist/credentials/DriftApi.credentials.js", + "dist/credentials/DriftOAuth2Api.credentials.js", "dist/credentials/DropboxApi.credentials.js", "dist/credentials/EventbriteApi.credentials.js", "dist/credentials/FacebookGraphApi.credentials.js", From 04f1f262486125399997f404e597c3047c877ac2 Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Tue, 16 Jun 2020 14:16:03 +0200 Subject: [PATCH 094/120] Fixed deprecated API calls & Added OAuth2 support --- .../EventbriteOAuth2Api.credentials.ts | 60 +++++++++++++++++++ .../Eventbrite/EventbriteTrigger.node.ts | 44 +++++++++++++- .../nodes/Eventbrite/GenericFunctions.ts | 23 ++++--- packages/nodes-base/package.json | 1 + 4 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 packages/nodes-base/credentials/EventbriteOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/EventbriteOAuth2Api.credentials.ts b/packages/nodes-base/credentials/EventbriteOAuth2Api.credentials.ts new file mode 100644 index 00000000000..ff50a115d5c --- /dev/null +++ b/packages/nodes-base/credentials/EventbriteOAuth2Api.credentials.ts @@ -0,0 +1,60 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class EventbriteOAuth2Api implements ICredentialType { + name = 'eventbriteOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Eventbrite OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://www.eventbrite.com/oauth/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://www.eventbrite.com/oauth/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: '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/Eventbrite/EventbriteTrigger.node.ts b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts index fdeae599c23..8299ae866df 100644 --- a/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts +++ b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts @@ -35,7 +35,25 @@ export class EventbriteTrigger implements INodeType { { name: 'eventbriteApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'eventbriteOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], webhooks: [ { @@ -46,6 +64,23 @@ export class EventbriteTrigger implements INodeType { }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Access Token', + value: 'accessToken', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'accessToken', + description: 'The resource to operate on.', + }, { displayName: 'Organization', name: 'organization', @@ -192,10 +227,12 @@ export class EventbriteTrigger implements INodeType { default: { async checkExists(this: IHookFunctions): Promise { const webhookData = this.getWorkflowStaticData('node'); + const organisation = this.getNodeParameter('organization') as string; + if (webhookData.webhookId === undefined) { return false; } - const endpoint = `/webhooks/${webhookData.webhookId}/`; + const endpoint = `/organizations/${organisation}/webhooks/`; try { await eventbriteApiRequest.call(this, 'GET', endpoint); } catch (e) { @@ -206,9 +243,10 @@ export class EventbriteTrigger implements INodeType { async create(this: IHookFunctions): Promise { const webhookUrl = this.getNodeWebhookUrl('default'); const webhookData = this.getWorkflowStaticData('node'); + const organisation = this.getNodeParameter('organization') as string; const event = this.getNodeParameter('event') as string; const actions = this.getNodeParameter('actions') as string[]; - const endpoint = `/webhooks/`; + const endpoint = `/organizations/${organisation}/webhooks/`; const body: IDataObject = { endpoint_url: webhookUrl, actions: actions.join(','), diff --git a/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts b/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts index 285c89b1f8c..5bbb517c5d1 100644 --- a/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts @@ -9,13 +9,8 @@ import { import { IDataObject } from 'n8n-workflow'; export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('eventbriteApi'); - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - let options: OptionsWithUri = { - headers: { 'Authorization': `Bearer ${credentials.apiKey}`}, + headers: {}, method, qs, body, @@ -27,14 +22,26 @@ export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFuncti delete options.body; } + const authenticationMethod = this.getNodeParameter('authentication', 0); + try { - return await this.helpers.request!(options); + if (authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('eventbriteApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + options.headers!['Authorization'] = `Bearer ${credentials.apiKey}`; + + return await this.helpers.request!(options); + } else { + return await this.helpers.requestOAuth2!.call(this, 'eventbriteOAuth2Api', options); + } } catch (error) { let errorMessage = error.message; if (error.response.body && error.response.body.error_description) { errorMessage = error.response.body.error_description; } - throw new Error('Eventbrite Error: ' + errorMessage); } } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index dcfaeb2269d..ba9899b2d12 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -49,6 +49,7 @@ "dist/credentials/DriftApi.credentials.js", "dist/credentials/DropboxApi.credentials.js", "dist/credentials/EventbriteApi.credentials.js", + "dist/credentials/EventbriteOAuth2Api.credentials.js", "dist/credentials/FacebookGraphApi.credentials.js", "dist/credentials/FreshdeskApi.credentials.js", "dist/credentials/FileMaker.credentials.js", From 755079c3607d06f2f4bbb780a003b12776f6754b Mon Sep 17 00:00:00 2001 From: Rupenieks Date: Tue, 16 Jun 2020 14:16:53 +0200 Subject: [PATCH 095/120] Update EventbriteOAuth2Api.credentials.ts --- .../EventbriteOAuth2Api.credentials.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/nodes-base/credentials/EventbriteOAuth2Api.credentials.ts b/packages/nodes-base/credentials/EventbriteOAuth2Api.credentials.ts index ff50a115d5c..46a9df4266a 100644 --- a/packages/nodes-base/credentials/EventbriteOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/EventbriteOAuth2Api.credentials.ts @@ -40,21 +40,8 @@ export class EventbriteOAuth2Api 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', - description: 'Resource to consume.', + type: 'hidden' as NodePropertyTypes, + default: 'body' }, ]; } From 717c43b21fc6378e94d544cccb87198b144564ea Mon Sep 17 00:00:00 2001 From: shraddha shaligram Date: Tue, 16 Jun 2020 12:13:34 -0700 Subject: [PATCH 096/120] minor improvements --- packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts index 450b3893768..db751cb258c 100644 --- a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts +++ b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts @@ -106,7 +106,7 @@ export class GoogleTasks implements INodeType { if (additionalFields.links) { body.links = (additionalFields.links as string[]).map(link => { - return { link: link }; + return { link }; }); } if (additionalFields.status) @@ -144,7 +144,7 @@ export class GoogleTasks implements INodeType { qs ); } - if (operation == 'delete') { + if (operation === 'delete') { //https://developers.google.com/tasks/v1/reference/tasks/delete const taskListId = this.getNodeParameter('task', i) as string; const taskId = this.getNodeParameter('taskId', i) as string; @@ -226,7 +226,7 @@ export class GoogleTasks implements INodeType { responseData = responseData.items; } } - if (operation == 'update') { + if (operation === 'update') { body = {}; //https://developers.google.com/tasks/v1/reference/tasks/patch const taskListId = this.getNodeParameter('task', i) as string; From c25d62474a6743cc5cfffd6b77e0192fd67c6e0d Mon Sep 17 00:00:00 2001 From: shraddha shaligram Date: Wed, 17 Jun 2020 12:49:37 -0700 Subject: [PATCH 097/120] fix formatting and syntax --- .../nodes/Google/Task/GoogleTasks.node.ts | 84 ++-- .../nodes/Google/Task/TaskDescription.ts | 446 +++++++++--------- 2 files changed, 250 insertions(+), 280 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts index db751cb258c..61bf3e64714 100644 --- a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts +++ b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts @@ -8,10 +8,10 @@ import { ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow'; -import { taskOperations, taskFields } from './TaskDescription'; - import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions'; +import { taskOperations, taskFields } from './TaskDescription'; + export class GoogleTasks implements INodeType { description: INodeTypeDescription = { displayName: 'Google Tasks', @@ -98,43 +98,40 @@ export class GoogleTasks implements INodeType { i ) as IDataObject; - if (additionalFields.parent) + if (additionalFields.parent) { qs.parent = additionalFields.parent as string; - - if (additionalFields.previous) - qs.previous = additionalFields.previous as string; - - if (additionalFields.links) { - body.links = (additionalFields.links as string[]).map(link => { - return { link }; - }); } - if (additionalFields.status) + if (additionalFields.previous) { + qs.previous = additionalFields.previous as string; + } + + if (additionalFields.status) { body.status = additionalFields.status as string; + } - if (additionalFields.notes) + if (additionalFields.notes) { body.notes = additionalFields.notes as string; + } - if (additionalFields.title) + if (additionalFields.title) { body.title = additionalFields.title as string; + } - if (additionalFields.dueDate) + if (additionalFields.dueDate) { body.dueDate = additionalFields.dueDate as string; + } - if (additionalFields.completed) + if (additionalFields.completed) { body.completed = additionalFields.completed as string; + } - if (additionalFields.deleted) + if (additionalFields.deleted) { body.deleted = additionalFields.deleted as boolean; + } - if (additionalFields.hidden) - body.hidden = additionalFields.hidden as boolean; - - if (additionalFields.position) - body.position = additionalFields.position as string; - - if (additionalFields.selfLink) + if (additionalFields.selfLink) { body.selfLink = additionalFields.selfLink as string; + } responseData = await googleApiRequest.call( this, @@ -236,38 +233,37 @@ export class GoogleTasks implements INodeType { i ) as IDataObject; - if (updateFields.parent) qs.parent = updateFields.parent as string; - - if (updateFields.previous) + if (updateFields.previous) { qs.previous = updateFields.previous as string; - - if (updateFields.links) { - body.links = (updateFields.links as string[]).map(link => { - return { link: link }; - }); } - if (updateFields.status) body.status = updateFields.status as string; - if (updateFields.notes) body.notes = updateFields.notes as string; + if (updateFields.status) { + body.status = updateFields.status as string; + } - if (updateFields.title) body.title = updateFields.title as string; + if (updateFields.notes) { + body.notes = updateFields.notes as string; + } - if (updateFields.dueDate) + if (updateFields.title) { + body.title = updateFields.title as string; + } + + if (updateFields.dueDate) { body.dueDate = updateFields.dueDate as string; + } - if (updateFields.completed) + if (updateFields.completed) { body.completed = updateFields.completed as string; + } - if (updateFields.deleted) + if (updateFields.deleted) { body.deleted = updateFields.deleted as boolean; + } - if (updateFields.hidden) body.hidden = updateFields.hidden as boolean; - - if (updateFields.position) - body.position = updateFields.position as string; - - if (updateFields.selfLink) + if (updateFields.selfLink) { body.selfLink = updateFields.selfLink as string; + } responseData = await googleApiRequest.call( this, diff --git a/packages/nodes-base/nodes/Google/Task/TaskDescription.ts b/packages/nodes-base/nodes/Google/Task/TaskDescription.ts index 5b8df27097b..4bf04ae8e4e 100644 --- a/packages/nodes-base/nodes/Google/Task/TaskDescription.ts +++ b/packages/nodes-base/nodes/Google/Task/TaskDescription.ts @@ -7,38 +7,40 @@ export const taskOperations = [ type: 'options', displayOptions: { show: { - resource: ['task'] - } + resource: [ + 'task', + ], + }, }, options: [ { name: 'Create', value: 'create', - description: 'Add a task to tasklist' + description: 'Add a task to tasklist', }, { name: 'Delete', value: 'delete', - description: 'Delete a task' + description: 'Delete a task', }, { name: 'Get', value: 'get', - description: 'Retrieve a task' + description: 'Retrieve a task', }, { name: 'Get All', value: 'getAll', - description: 'Retrieve all tasks from a tasklist' + description: 'Retrieve all tasks from a tasklist', }, { name: 'Update', value: 'update', - description: 'Update a task' + description: 'Update a task', } ], default: 'create', - description: 'The operation to perform.' + description: 'The operation to perform.', } ] as INodeProperties[]; @@ -51,16 +53,20 @@ export const taskFields = [ name: 'task', type: 'options', typeOptions: { - loadOptionsMethod: 'getTasks' + loadOptionsMethod: 'getTasks', }, required: true, displayOptions: { show: { - operation: ['create'], - resource: ['task'] + operation: [ + 'create', + ], + resource: [ + 'task', + ], } }, - default: '' + default: '', }, { displayName: 'Additional Fields', @@ -70,11 +76,64 @@ export const taskFields = [ default: {}, displayOptions: { show: { - operation: ['create'], - resource: ['task'] + operation: [ + 'create', + ], + resource: [ + 'task', + ], } }, options: [ + { + displayName: 'Completion date', + name: 'completed', + type: 'dateTime', + default: '', + description: `Completion date of the task (as a RFC 3339 timestamp). This field is omitted if the task has not been completed.`, + }, + { + displayName: 'Deleted status', + name: 'deleted', + type: 'boolean', + default: false, + description: 'Flag indicating whether the task has been deleted.', + }, + { + displayName: 'Due Date', + name: 'dueDate', + type: 'dateTime', + default: '', + description: 'Due date of the task.', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + default: '', + description: 'Additional Notes.', + }, + { + displayName: 'Parent', + name: 'parent', + type: 'string', + default: '', + description: 'Parent task identifier. If the task is created at the top level, this parameter is omitted.', + }, + { + displayName: 'Previous', + name: 'previous', + type: 'string', + default: '', + description: 'Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.', + }, + { + displayName: 'Self Link', + name: 'selfLink', + type: 'string', + default: '', + description: 'URL pointing to this task. Used to retrieve, update, or delete this task.', + }, { displayName: 'Status', name: 'status', @@ -82,103 +141,24 @@ export const taskFields = [ options: [ { name: 'needs Action', - value: 'needsAction' + value: 'needsAction', }, { name: 'completed', - value: 'completed' + value: 'completed', } ], default: '', - description: 'Current status of the task.' - }, - { - displayName: 'Links', - name: 'links', - type: 'string', - typeOptions: { - multipleValues: true, - multipleValueButtonText: 'Add Link' - }, - default: '', - description: 'The links to insert in the task.' - }, - - { - displayName: 'Notes', - name: 'notes', - type: 'string', - default: '', - description: 'Additional Notes.' + description: 'Current status of the task.', }, { displayName: 'Title', name: 'title', type: 'string', default: '', - description: 'Title of the task.' - }, - { - displayName: 'Due Date', - name: 'dueDate', - type: 'dateTime', - default: '', - description: 'Due date of the task.' - }, - { - displayName: 'Completion date', - name: 'completed', - type: 'dateTime', - default: '', - description: `Completion date of the task (as a RFC 3339 timestamp). This field is omitted if the task has not been completed.` + description: 'Title of the task.', }, - { - displayName: 'Deleted status', - name: 'deleted', - type: 'boolean', - default: false, - description: 'Flag indicating whether the task has been deleted.' - }, - { - displayName: 'Hidden', - name: 'hidden', - type: 'boolean', - default: false, - description: 'Flag indicating whether the task is hidden.' - }, - { - displayName: 'Parent', - name: 'parent', - type: 'string', - default: '', - description: - 'Parent task identifier.This field is omitted if it is a top-level task.' - }, - { - displayName: 'Position', - name: 'position', - type: 'string', - default: '', - description: - 'Parent task identifier.This field is omitted if it is a top-level task.' - }, - { - displayName: 'Self Link', - name: 'selfLink', - type: 'string', - default: '', - description: - 'URL pointing to this task. Used to retrieve, update, or delete this task.' - }, - { - displayName: 'Previous', - name: 'previous', - type: 'string', - default: '', - description: - 'Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.' - } ] }, /* -------------------------------------------------------------------------- */ @@ -189,16 +169,20 @@ export const taskFields = [ name: 'task', type: 'options', typeOptions: { - loadOptionsMethod: 'getTasks' + loadOptionsMethod: 'getTasks', }, required: true, displayOptions: { show: { - operation: ['delete'], - resource: ['task'] + operation: [ + 'delete', + ], + resource: [ + 'task', + ], } }, - default: '' + default: '', }, { displayName: 'Task ID', @@ -207,11 +191,15 @@ export const taskFields = [ required: true, displayOptions: { show: { - operation: ['delete'], - resource: ['task'] + operation: [ + 'delete', + ], + resource: [ + 'task', + ], } }, - default: '' + default: '', }, /* -------------------------------------------------------------------------- */ /* task:get */ @@ -221,16 +209,20 @@ export const taskFields = [ name: 'task', type: 'options', typeOptions: { - loadOptionsMethod: 'getTasks' + loadOptionsMethod: 'getTasks', }, required: true, displayOptions: { show: { - operation: ['get'], - resource: ['task'] + operation: [ + 'get', + ], + resource: [ + 'task', + ], } }, - default: '' + default: '', }, { displayName: 'Task ID', @@ -239,11 +231,15 @@ export const taskFields = [ required: true, displayOptions: { show: { - operation: ['get'], - resource: ['task'] + operation: [ + 'get', + ], + resource: [ + 'task', + ], } }, - default: '' + default: '', }, /* -------------------------------------------------------------------------- */ /* task:getAll */ @@ -253,16 +249,20 @@ export const taskFields = [ name: 'task', type: 'options', typeOptions: { - loadOptionsMethod: 'getTasks' + loadOptionsMethod: 'getTasks', }, required: true, displayOptions: { show: { - operation: ['getAll'], - resource: ['task'] + operation: [ + 'getAll', + ], + resource: [ + 'task', + ], } }, - default: '' + default: '', }, { displayName: 'Return All', @@ -270,13 +270,16 @@ export const taskFields = [ type: 'boolean', displayOptions: { show: { - operation: ['getAll'], - resource: ['task'] + operation: [ + 'getAll', + ], + resource: [ + 'task', + ], } }, default: false, - description: - 'If all results should be returned or only up to a given limit.' + description: 'If all results should be returned or only up to a given limit.', }, { displayName: 'Limit', @@ -284,9 +287,15 @@ export const taskFields = [ type: 'number', displayOptions: { show: { - operation: ['getAll'], - resource: ['task'], - returnAll: [false] + operation: [ + 'getAll', + ], + resource: [ + 'task', + ], + returnAll: [ + false, + ], } }, typeOptions: { @@ -294,7 +303,7 @@ export const taskFields = [ maxValue: 100 }, default: 20, - description: 'How many results to return.' + description: 'How many results to return.', }, { displayName: 'Additional Fields', @@ -304,8 +313,12 @@ export const taskFields = [ default: {}, displayOptions: { show: { - operation: ['getAll'], - resource: ['task'] + operation: [ + 'getAll', + ], + resource: [ + 'task', + ], } }, @@ -315,40 +328,35 @@ export const taskFields = [ name: 'completedMax', type: 'dateTime', default: '', - description: - 'Upper bound for a task completion date (as a RFC 3339 timestamp) to filter by.' + description: 'Upper bound for a task completion date (as a RFC 3339 timestamp) to filter by.', }, { displayName: 'Completed Min', name: 'completedMin', type: 'dateTime', default: '', - description: - 'Lower bound for a task completion date (as a RFC 3339 timestamp) to filter by.' + description: 'Lower bound for a task completion date (as a RFC 3339 timestamp) to filter by.', }, { displayName: 'Due Min', name: 'dueMin', type: 'dateTime', default: '', - description: - 'Lower bound for a task due date (as a RFC 3339 timestamp) to filter by.' + description: 'Lower bound for a task due date (as a RFC 3339 timestamp) to filter by.', }, { displayName: 'Due Max', name: 'dueMax', type: 'dateTime', default: '', - description: - 'Upper bound for a task due date (as a RFC 3339 timestamp) to filter by.' + description: 'Upper bound for a task due date (as a RFC 3339 timestamp) to filter by.', }, - { displayName: 'Page Token', name: 'pageToken', type: 'string', default: '', - description: 'Token specifying the result page to return.' + description: 'Token specifying the result page to return.', }, { @@ -356,33 +364,29 @@ export const taskFields = [ name: 'showCompleted', type: 'boolean', default: true, - description: - 'Flag indicating whether completed tasks are returned in the result' + description: 'Flag indicating whether completed tasks are returned in the result', }, { displayName: 'Show Deleted', name: 'showDeleted', type: 'boolean', default: false, - description: - 'Flag indicating whether deleted tasks are returned in the result' + description: 'Flag indicating whether deleted tasks are returned in the result', }, { displayName: 'Show Hidden', name: 'showHidden', type: 'boolean', default: false, - description: - 'Flag indicating whether hidden tasks are returned in the result' + description: 'Flag indicating whether hidden tasks are returned in the result', }, { displayName: 'Updated Min', name: 'updatedMin', type: 'string', - - description: - 'Lower bound for a task last modification time (as a RFC 3339 timestamp) to filter by.' - } + default: '', + description: 'Lower bound for a task last modification time (as a RFC 3339 timestamp) to filter by.', + }, ] }, /* -------------------------------------------------------------------------- */ @@ -393,16 +397,20 @@ export const taskFields = [ name: 'task', type: 'options', typeOptions: { - loadOptionsMethod: 'getTasks' + loadOptionsMethod: 'getTasks', }, required: true, displayOptions: { show: { - operation: ['update'], - resource: ['task'] + operation: [ + 'update', + ], + resource: [ + 'task', + ], } }, - default: '' + default: '', }, { displayName: 'Task ID', @@ -411,11 +419,15 @@ export const taskFields = [ required: true, displayOptions: { show: { - operation: ['update'], - resource: ['task'] + operation: [ + 'update', + ], + resource: [ + 'task', + ], } }, - default: '' + default: '', }, { displayName: 'Update Fields', @@ -425,67 +437,21 @@ export const taskFields = [ default: {}, displayOptions: { show: { - operation: ['update'], - resource: ['task'] + operation: [ + 'update', + ], + resource: [ + 'task', + ], } }, options: [ - { - displayName: 'Status', - name: 'status', - type: 'options', - options: [ - { - name: 'needs Action', - value: 'needsAction' - }, - { - name: 'completed', - value: 'completed' - } - ], - default: '', - description: 'Current status of the task.' - }, - { - displayName: 'Links', - name: 'links', - type: 'string', - typeOptions: { - multipleValues: true, - multipleValueButtonText: 'Add Link' - }, - default: '', - description: 'The links to insert in the task.' - }, - - { - displayName: 'Notes', - name: 'notes', - type: 'string', - default: '', - description: 'Additional Notes.' - }, - { - displayName: 'Title', - name: 'title', - type: 'string', - default: '', - description: 'Title of the task.' - }, - { - displayName: 'Due Date', - name: 'dueDate', - type: 'dateTime', - default: '', - description: 'Due date of the task.' - }, { displayName: 'Completion date', name: 'completed', type: 'dateTime', default: '', - description: `Completion date of the task (as a RFC 3339 timestamp). This field is omitted if the task has not been completed.` + description: `Completion date of the task (as a RFC 3339 timestamp). This field is omitted if the task has not been completed.`, }, { @@ -493,47 +459,55 @@ export const taskFields = [ name: 'deleted', type: 'boolean', default: false, - description: 'Flag indicating whether the task has been deleted.' + description: 'Flag indicating whether the task has been deleted.', }, { - displayName: 'Hidden', - name: 'hidden', - type: 'boolean', - default: false, - description: 'Flag indicating whether the task is hidden.' - }, - { - displayName: 'Parent', - name: 'parent', + displayName: 'Notes', + name: 'notes', type: 'string', default: '', - description: - 'Parent task identifier.This field is omitted if it is a top-level task.' - }, - { - displayName: 'Position', - name: 'position', - type: 'string', - default: '', - description: - 'Parent task identifier.This field is omitted if it is a top-level task.' - }, - { - displayName: 'Self Link', - name: 'selfLink', - type: 'string', - default: '', - description: - 'URL pointing to this task. Used to retrieve, update, or delete this task.' + description: 'Additional Notes.', }, { displayName: 'Previous', name: 'previous', type: 'string', default: '', - description: - 'Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.' - } + description: 'Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.', + }, + { + displayName: 'Self Link', + name: 'selfLink', + type: 'string', + default: '', + description: 'URL pointing to this task. Used to retrieve, update, or delete this task.', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'needs Update', + value: 'needsAction', + }, + { + name: 'completed', + value: 'completed', + } + ], + default: '', + description: 'Current status of the task.', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the task.', + }, + + ] } ] as INodeProperties[]; From d261eb5a12c2ab4b7027b6247628e2ca68781ec0 Mon Sep 17 00:00:00 2001 From: ricardo Date: Wed, 17 Jun 2020 17:15:54 -0400 Subject: [PATCH 098/120] :zap: Improvements to Google Tasks node --- .../GoogleTasksOAuth2Api.credentials.ts | 11 ++- .../nodes/Google/Task/GenericFunctions.ts | 19 ++-- .../nodes/Google/Task/GoogleTasks.node.ts | 47 +++++----- .../nodes/Google/Task/TaskDescription.ts | 87 +++++++------------ 4 files changed, 77 insertions(+), 87 deletions(-) diff --git a/packages/nodes-base/credentials/GoogleTasksOAuth2Api.credentials.ts b/packages/nodes-base/credentials/GoogleTasksOAuth2Api.credentials.ts index db307999201..ac1242be2b6 100644 --- a/packages/nodes-base/credentials/GoogleTasksOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/GoogleTasksOAuth2Api.credentials.ts @@ -1,6 +1,11 @@ -import { ICredentialType, NodePropertyTypes } from 'n8n-workflow'; +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; -const scopes = ['https://www.googleapis.com/auth/tasks']; +const scopes = [ + 'https://www.googleapis.com/auth/tasks', +]; export class GoogleTasksOAuth2Api implements ICredentialType { name = 'googleTasksOAuth2Api'; @@ -12,6 +17,6 @@ export class GoogleTasksOAuth2Api implements ICredentialType { name: 'scope', type: 'hidden' as NodePropertyTypes, default: scopes.join(' ') - } + }, ]; } diff --git a/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts index 19652d1d4af..c3a50315114 100644 --- a/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts @@ -1,12 +1,16 @@ -import { OptionsWithUri } from 'request'; +import { + OptionsWithUri, +} from 'request'; import { IExecuteFunctions, IExecuteSingleFunctions, - ILoadOptionsFunctions + ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { + IDataObject, +} from 'n8n-workflow'; export async function googleApiRequest( this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, @@ -27,6 +31,7 @@ export async function googleApiRequest( uri: uri || `https://www.googleapis.com${resource}`, json: true }; + try { if (Object.keys(headers).length !== 0) { options.headers = Object.assign({}, options.headers, headers); @@ -41,10 +46,14 @@ export async function googleApiRequest( options ); } catch (error) { - if (error.response && error.response.body && error.response.body.message) { + if (error.response && error.response.body && error.response.body.error) { + + let errors = error.response.body.error.errors; + + errors = errors.map((e: IDataObject) => e.message); // Try to return the error prettier throw new Error( - `Google Tasks error response [${error.statusCode}]: ${error.response.body.message}` + `Google Tasks error response [${error.statusCode}]: ${errors.join('|')}` ); } throw error; diff --git a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts index 61bf3e64714..8e2f06dcf5a 100644 --- a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts +++ b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts @@ -1,16 +1,25 @@ -import { IExecuteFunctions } from 'n8n-core'; +import { + IExecuteFunctions, + } from 'n8n-core'; import { IDataObject, - INodeExecutionData, - INodeTypeDescription, - INodeType, ILoadOptionsFunctions, - INodePropertyOptions + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, } from 'n8n-workflow'; -import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions'; -import { taskOperations, taskFields } from './TaskDescription'; +import { + googleApiRequest, + googleApiRequestAllItems, +} from './GenericFunctions'; + +import { + taskOperations, + taskFields, +} from './TaskDescription'; export class GoogleTasks implements INodeType { description: INodeTypeDescription = { @@ -129,10 +138,6 @@ export class GoogleTasks implements INodeType { body.deleted = additionalFields.deleted as boolean; } - if (additionalFields.selfLink) { - body.selfLink = additionalFields.selfLink as string; - } - responseData = await googleApiRequest.call( this, 'POST', @@ -186,9 +191,6 @@ export class GoogleTasks implements INodeType { if (options.dueMax) { qs.dueMax = options.dueMax as string; } - if (options.pageToken) { - qs.pageToken = options.pageToken as string; - } if (options.showCompleted) { qs.showCompleted = options.showCompleted as boolean; } @@ -198,10 +200,10 @@ export class GoogleTasks implements INodeType { if (options.showHidden) { qs.showHidden = options.showHidden as boolean; } - if (options.updatedMin) { qs.updatedMin = options.updatedMin as string; } + if (returnAll) { responseData = await googleApiRequestAllItems.call( this, @@ -261,10 +263,6 @@ export class GoogleTasks implements INodeType { body.deleted = updateFields.deleted as boolean; } - if (updateFields.selfLink) { - body.selfLink = updateFields.selfLink as string; - } - responseData = await googleApiRequest.call( this, 'PATCH', @@ -274,12 +272,11 @@ export class GoogleTasks implements INodeType { ); } } - } - - if (Array.isArray(responseData)) { - returnData.push.apply(returnData, responseData as IDataObject[]); - } else if (responseData !== undefined) { - returnData.push(responseData as IDataObject); + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } } return [this.helpers.returnJsonArray(returnData)]; } diff --git a/packages/nodes-base/nodes/Google/Task/TaskDescription.ts b/packages/nodes-base/nodes/Google/Task/TaskDescription.ts index 4bf04ae8e4e..8030f0a7f33 100644 --- a/packages/nodes-base/nodes/Google/Task/TaskDescription.ts +++ b/packages/nodes-base/nodes/Google/Task/TaskDescription.ts @@ -1,4 +1,6 @@ -import { INodeProperties } from 'n8n-workflow'; +import { + INodeProperties, +} from 'n8n-workflow'; export const taskOperations = [ { @@ -46,7 +48,7 @@ export const taskOperations = [ export const taskFields = [ /* -------------------------------------------------------------------------- */ - /* task:create */ + /* task:create */ /* -------------------------------------------------------------------------- */ { displayName: 'TaskList', @@ -64,7 +66,7 @@ export const taskFields = [ resource: [ 'task', ], - } + }, }, default: '', }, @@ -86,14 +88,14 @@ export const taskFields = [ }, options: [ { - displayName: 'Completion date', + displayName: 'Completion Date', name: 'completed', type: 'dateTime', default: '', description: `Completion date of the task (as a RFC 3339 timestamp). This field is omitted if the task has not been completed.`, }, { - displayName: 'Deleted status', + displayName: 'Deleted', name: 'deleted', type: 'boolean', default: false, @@ -127,24 +129,17 @@ export const taskFields = [ default: '', description: 'Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.', }, - { - displayName: 'Self Link', - name: 'selfLink', - type: 'string', - default: '', - description: 'URL pointing to this task. Used to retrieve, update, or delete this task.', - }, { displayName: 'Status', name: 'status', type: 'options', options: [ { - name: 'needs Action', + name: 'Needs Action', value: 'needsAction', }, { - name: 'completed', + name: 'Completed', value: 'completed', } ], @@ -158,11 +153,10 @@ export const taskFields = [ default: '', description: 'Title of the task.', }, - - ] + ], }, /* -------------------------------------------------------------------------- */ - /* task:delete */ + /* task:delete */ /* -------------------------------------------------------------------------- */ { displayName: 'TaskList', @@ -180,7 +174,7 @@ export const taskFields = [ resource: [ 'task', ], - } + }, }, default: '', }, @@ -197,12 +191,12 @@ export const taskFields = [ resource: [ 'task', ], - } + }, }, default: '', }, /* -------------------------------------------------------------------------- */ - /* task:get */ + /* task:get */ /* -------------------------------------------------------------------------- */ { displayName: 'TaskList', @@ -237,12 +231,12 @@ export const taskFields = [ resource: [ 'task', ], - } + }, }, default: '', }, /* -------------------------------------------------------------------------- */ - /* task:getAll */ + /* task:getAll */ /* -------------------------------------------------------------------------- */ { displayName: 'TaskList', @@ -260,7 +254,7 @@ export const taskFields = [ resource: [ 'task', ], - } + }, }, default: '', }, @@ -276,7 +270,7 @@ export const taskFields = [ resource: [ 'task', ], - } + }, }, default: false, description: 'If all results should be returned or only up to a given limit.', @@ -296,7 +290,7 @@ export const taskFields = [ returnAll: [ false, ], - } + }, }, typeOptions: { minValue: 1, @@ -319,9 +313,8 @@ export const taskFields = [ resource: [ 'task', ], - } + }, }, - options: [ { displayName: 'Completed Max', @@ -351,14 +344,6 @@ export const taskFields = [ default: '', description: 'Upper bound for a task due date (as a RFC 3339 timestamp) to filter by.', }, - { - displayName: 'Page Token', - name: 'pageToken', - type: 'string', - default: '', - description: 'Token specifying the result page to return.', - }, - { displayName: 'Show Completed', name: 'showCompleted', @@ -383,14 +368,14 @@ export const taskFields = [ { displayName: 'Updated Min', name: 'updatedMin', - type: 'string', + type: 'dateTime', default: '', description: 'Lower bound for a task last modification time (as a RFC 3339 timestamp) to filter by.', }, ] }, /* -------------------------------------------------------------------------- */ - /* task:update */ + /* task:update */ /* -------------------------------------------------------------------------- */ { displayName: 'TaskList', @@ -408,7 +393,7 @@ export const taskFields = [ resource: [ 'task', ], - } + }, }, default: '', }, @@ -425,7 +410,7 @@ export const taskFields = [ resource: [ 'task', ], - } + }, }, default: '', }, @@ -447,7 +432,7 @@ export const taskFields = [ }, options: [ { - displayName: 'Completion date', + displayName: 'Completion Date', name: 'completed', type: 'dateTime', default: '', @@ -455,7 +440,7 @@ export const taskFields = [ }, { - displayName: 'Deleted status', + displayName: 'Deleted', name: 'deleted', type: 'boolean', default: false, @@ -465,6 +450,9 @@ export const taskFields = [ displayName: 'Notes', name: 'notes', type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, default: '', description: 'Additional Notes.', }, @@ -475,24 +463,17 @@ export const taskFields = [ default: '', description: 'Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.', }, - { - displayName: 'Self Link', - name: 'selfLink', - type: 'string', - default: '', - description: 'URL pointing to this task. Used to retrieve, update, or delete this task.', - }, { displayName: 'Status', name: 'status', type: 'options', options: [ { - name: 'needs Update', + name: 'Needs Update', value: 'needsAction', }, { - name: 'completed', + name: 'Completed', value: 'completed', } ], @@ -506,8 +487,6 @@ export const taskFields = [ default: '', description: 'Title of the task.', }, - - - ] - } + ], + }, ] as INodeProperties[]; From b4da818dbd055bc5ba4b2b1e8e762ef2873de3d1 Mon Sep 17 00:00:00 2001 From: ricardo Date: Wed, 17 Jun 2020 17:46:34 -0400 Subject: [PATCH 099/120] :zap: Fixed formatting issues on Google Calendar and MessageBird --- .../nodes/Google/Calendar/EventDescription.ts | 532 ++++++++++-------- .../nodes/Google/Calendar/EventInterface.ts | 4 +- .../Google/Calendar/GoogleCalendar.node.ts | 32 +- .../nodes/MessageBird/MessageBird.node.ts | 134 ++--- 4 files changed, 394 insertions(+), 308 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts b/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts index a7800334e5c..44af95a1515 100644 --- a/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts +++ b/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts @@ -1,4 +1,6 @@ -import { INodeProperties } from 'n8n-workflow'; +import { + INodeProperties, +} from 'n8n-workflow'; export const eventOperations = [ { @@ -7,34 +9,36 @@ export const eventOperations = [ type: 'options', displayOptions: { show: { - resource: ['event'] - } + resource: [ + 'event', + ], + }, }, options: [ { name: 'Create', value: 'create', - description: 'Add a event to calendar' + description: 'Add a event to calendar', }, { name: 'Delete', value: 'delete', - description: 'Delete an event' + description: 'Delete an event', }, { name: 'Get', value: 'get', - description: 'Retrieve an event' + description: 'Retrieve an event', }, { name: 'Get All', value: 'getAll', - description: 'Retrieve all events from a calendar' + description: 'Retrieve all events from a calendar', }, { name: 'Update', value: 'update', - description: 'Update an event' + description: 'Update an event', } ], default: 'create', @@ -56,9 +60,13 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: ['create'], - resource: ['event'] - } + operation: [ + 'create' + ], + resource: [ + 'event' + ], + }, }, default: '' }, @@ -69,9 +77,13 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: ['create'], - resource: ['event'] - } + operation: [ + 'create', + ], + resource: [ + 'event', + ], + }, }, default: '', description: 'Start time of the event.' @@ -83,9 +95,13 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: ['create'], - resource: ['event'] - } + operation: [ + 'create', + ], + resource: [ + 'event', + ], + }, }, default: '', description: 'End time of the event.' @@ -96,9 +112,13 @@ export const eventFields = [ type: 'boolean', displayOptions: { show: { - operation: ['create'], - resource: ['event'] - } + operation: [ + 'create', + ], + resource: [ + 'event', + ], + }, }, default: true }, @@ -110,9 +130,13 @@ export const eventFields = [ default: {}, displayOptions: { show: { - operation: ['create'], - resource: ['event'] - } + operation: [ + 'create', + ], + resource: [ + 'event', + ], + }, }, options: [ { @@ -122,12 +146,12 @@ export const eventFields = [ options: [ { name: 'Yes', - value: 'yes' + value: 'yes', }, { name: 'No', - value: 'no' - } + value: 'no', + }, ], default: 'no', description: 'Wheater the event is all day or not' @@ -138,57 +162,55 @@ export const eventFields = [ type: 'string', typeOptions: { multipleValues: true, - multipleValueButtonText: 'Add Attendee' + multipleValueButtonText: 'Add Attendee', }, default: '', - description: 'The attendees of the event' + description: 'The attendees of the event', }, { displayName: 'Color', name: 'color', type: 'options', typeOptions: { - loadOptionsMethod: 'getColors' + loadOptionsMethod: 'getColors', }, default: '', - description: 'The color of the event.' + description: 'The color of the event.', }, { displayName: 'Guests Can Invite Others', name: 'guestsCanInviteOthers', type: 'boolean', default: true, - description: - 'Whether attendees other than the organizer can invite others to the event' + description: 'Whether attendees other than the organizer can invite others to the event', }, { displayName: 'Guests Can Modify', name: 'guestsCanModify', type: 'boolean', default: false, - description: - 'Whether attendees other than the organizer can modify the event' + description: 'Whether attendees other than the organizer can modify the event', }, { displayName: 'Guests Can See Other Guests', name: 'guestsCanSeeOtherGuests', type: 'boolean', default: true, - description: `Whether attendees other than the organizer can see who the event's attendees are.` + description: `Whether attendees other than the organizer can see who the event's attendees are.`, }, { displayName: 'ID', name: 'id', type: 'string', default: '', - description: 'Opaque identifier of the event' + description: 'Opaque identifier of the event', }, { displayName: 'Location', name: 'location', type: 'string', default: '', - description: 'Geographic location of the event as free-form text.' + description: 'Geographic location of the event as free-form text.', }, { displayName: 'Max Attendees', @@ -196,7 +218,7 @@ export const eventFields = [ type: 'number', default: 0, description: `The maximum number of attendees to include in the response.
- If there are more than the specified number of attendees, only the participant is returned` + If there are more than the specified number of attendees, only the participant is returned`, }, { displayName: 'Repeat Frecuency', @@ -205,28 +227,28 @@ export const eventFields = [ options: [ { name: 'Daily', - value: 'Daily' + value: 'Daily', }, { name: 'Weekly', - value: 'weekly' + value: 'weekly', }, { name: 'Monthly', - value: 'monthly' + value: 'monthly', }, { name: 'Yearly', - value: 'yearly' + value: 'yearly', } ], - default: '' + default: '', }, { displayName: 'Repeat Until', name: 'repeatUntil', type: 'dateTime', - default: '' + default: '', }, { displayName: 'Repeat How Many Times?', @@ -245,31 +267,28 @@ export const eventFields = [ { name: 'All', value: 'all', - description: ' Notifications are sent to all guests' + description: 'Notifications are sent to all guests' }, { name: 'External Only', value: 'externalOnly', - description: - 'Notifications are sent to non-Google Calendar guests only' + description: 'Notifications are sent to non-Google Calendar guests only', }, { name: 'None', value: 'none', - description: - ' No notifications are sent. This value should only be used for migration use case' + description: 'No notifications are sent. This value should only be used for migration use case', } ], - description: - 'Whether to send notifications about the creation of the new event', - default: '' + description: 'Whether to send notifications about the creation of the new event', + default: '', }, { displayName: 'Summary', name: 'summary', type: 'string', default: '', - description: 'Title of the event.' + description: 'Title of the event.', }, { displayName: 'Show Me As', @@ -279,27 +298,26 @@ export const eventFields = [ { name: 'Available', value: 'transparent', - description: 'The event does not block time on the calendar' + description: 'The event does not block time on the calendar', }, { name: 'Busy', value: 'opaque', - description: ' The event does block time on the calendar.' + description: ' The event does block time on the calendar.', } ], default: 'opaque', - description: 'Whether the event blocks time on the calendar' + description: 'Whether the event blocks time on the calendar', }, { displayName: 'Timezone', name: 'timezone', type: 'options', typeOptions: { - loadOptionsMethod: 'getTimezones' + loadOptionsMethod: 'getTimezones', }, default: '', - description: - 'The timezone the event will have set. By default events are schedule on timezone set in n8n.' + description: 'The timezone the event will have set. By default events are schedule on timezone set in n8n.', }, { displayName: 'Visibility', @@ -309,32 +327,28 @@ export const eventFields = [ { name: 'Confidential', value: 'confidential', - description: - 'The event is private. This value is provided for compatibility reasons.' + description: 'The event is private. This value is provided for compatibility reasons.', }, { name: 'Default', value: 'default', - description: - ' Uses the default visibility for events on the calendar.' + description: 'Uses the default visibility for events on the calendar.', }, { name: 'Private', value: 'private', - description: - 'The event is private and only event attendees may view event details.' + description: 'The event is private and only event attendees may view event details.', }, { name: 'Public', value: 'public', - description: - 'The event is public and event details are visible to all readers of the calendar.' - } + description: 'The event is public and event details are visible to all readers of the calendar.', + }, ], default: 'default', description: 'Visibility of the event.' - } - ] + }, + ], }, { displayName: 'Reminders', @@ -348,10 +362,16 @@ export const eventFields = [ required: false, displayOptions: { show: { - resource: ['event'], - operation: ['create'], - useDefaultReminders: [false] - } + resource: [ + 'event', + ], + operation: [ + 'create', + ], + useDefaultReminders: [ + false, + ], + }, }, options: [ { @@ -365,14 +385,14 @@ export const eventFields = [ options: [ { name: 'Email', - value: 'email' + value: 'email', }, { name: 'Popup', - value: 'popup' - } + value: 'popup', + }, ], - default: '' + default: '', }, { displayName: 'Minutes Before', @@ -380,12 +400,12 @@ export const eventFields = [ type: 'number', typeOptions: { minValue: 0, - maxValue: 40320 + maxValue: 40320, }, - default: 0 - } - ] - } + default: 0, + }, + ], + }, ], description: `If the event doesn't use the default reminders, this lists the reminders specific to the event` }, @@ -397,14 +417,18 @@ export const eventFields = [ name: 'calendar', type: 'options', typeOptions: { - loadOptionsMethod: 'getCalendars' + loadOptionsMethod: 'getCalendars', }, required: true, displayOptions: { show: { - operation: ['delete'], - resource: ['event'] - } + operation: [ + 'delete', + ], + resource: [ + 'event', + ], + }, }, default: '' }, @@ -415,11 +439,15 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: ['delete'], - resource: ['event'] - } + operation: [ + 'delete', + ], + resource: [ + 'event', + ], + }, }, - default: '' + default: '', }, { displayName: 'Options', @@ -429,9 +457,13 @@ export const eventFields = [ default: {}, displayOptions: { show: { - operation: ['delete'], - resource: ['event'] - } + operation: [ + 'delete', + ], + resource: [ + 'event', + ], + }, }, options: [ { @@ -442,26 +474,23 @@ export const eventFields = [ { name: 'All', value: 'all', - description: ' Notifications are sent to all guests' + description: 'Notifications are sent to all guests', }, { name: 'External Only', value: 'externalOnly', - description: - 'Notifications are sent to non-Google Calendar guests only' + description: 'Notifications are sent to non-Google Calendar guests only', }, { name: 'None', value: 'none', - description: - ' No notifications are sent. This value should only be used for migration use case' + description: 'No notifications are sent. This value should only be used for migration use case', } ], - description: - 'Whether to send notifications about the creation of the new event', - default: '' - } - ] + description: 'Whether to send notifications about the creation of the new event', + default: '', + }, + ], }, /* -------------------------------------------------------------------------- */ /* event:get */ @@ -471,14 +500,18 @@ export const eventFields = [ name: 'calendar', type: 'options', typeOptions: { - loadOptionsMethod: 'getCalendars' + loadOptionsMethod: 'getCalendars', }, required: true, displayOptions: { show: { - operation: ['get'], - resource: ['event'] - } + operation: [ + 'get', + ], + resource: [ + 'event', + ], + }, }, default: '' }, @@ -489,11 +522,15 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: ['get'], - resource: ['event'] - } + operation: [ + 'get', + ], + resource: [ + 'event', + ], + }, }, - default: '' + default: '', }, { displayName: 'Options', @@ -503,9 +540,13 @@ export const eventFields = [ default: {}, displayOptions: { show: { - operation: ['get'], - resource: ['event'] - } + operation: [ + 'get', + ], + resource: [ + 'event', + ], + }, }, options: [ { @@ -514,17 +555,17 @@ export const eventFields = [ type: 'number', default: 0, description: `The maximum number of attendees to include in the response.
- If there are more than the specified number of attendees, only the participant is returned` + If there are more than the specified number of attendees, only the participant is returned`, }, { displayName: 'Timezone', name: 'timeZone', type: 'options', typeOptions: { - loadOptionsMethod: 'getTimezones' + loadOptionsMethod: 'getTimezones', }, default: '', - description: `Time zone used in the response. The default is the time zone of the calendar.` + description: `Time zone used in the response. The default is the time zone of the calendar.`, } ] }, @@ -536,14 +577,18 @@ export const eventFields = [ name: 'calendar', type: 'options', typeOptions: { - loadOptionsMethod: 'getCalendars' + loadOptionsMethod: 'getCalendars', }, required: true, displayOptions: { show: { - operation: ['getAll'], - resource: ['event'] - } + operation: [ + 'getAll', + ], + resource: [ + 'event', + ], + }, }, default: '' }, @@ -553,13 +598,16 @@ export const eventFields = [ type: 'boolean', displayOptions: { show: { - operation: ['getAll'], - resource: ['event'] - } + operation: [ + 'getAll', + ], + resource: [ + 'event', + ], + }, }, default: false, - description: - 'If all results should be returned or only up to a given limit.' + description: 'If all results should be returned or only up to a given limit.', }, { displayName: 'Limit', @@ -567,17 +615,23 @@ export const eventFields = [ type: 'number', displayOptions: { show: { - operation: ['getAll'], - resource: ['event'], - returnAll: [false] - } + operation: [ + 'getAll', + ], + resource: [ + 'event', + ], + returnAll: [ + false, + ], + }, }, typeOptions: { minValue: 1, - maxValue: 500 + maxValue: 500, }, default: 100, - description: 'How many results to return.' + description: 'How many results to return.', }, { displayName: 'Options', @@ -587,9 +641,13 @@ export const eventFields = [ default: {}, displayOptions: { show: { - operation: ['getAll'], - resource: ['event'] - } + operation: [ + 'getAll', + ], + resource: [ + 'event', + ], + }, }, options: [ { @@ -597,8 +655,7 @@ export const eventFields = [ name: 'iCalUID', type: 'string', default: '', - description: - 'Specifies event ID in the iCalendar format to be included in the response' + description: 'Specifies event ID in the iCalendar format to be included in the response', }, { displayName: 'Max Attendees', @@ -606,7 +663,7 @@ export const eventFields = [ type: 'number', default: 0, description: `The maximum number of attendees to include in the response.
- If there are more than the specified number of attendees, only the participant is returned` + If there are more than the specified number of attendees, only the participant is returned`, }, { displayName: 'Order By', @@ -616,40 +673,37 @@ export const eventFields = [ { name: 'Start Time', value: 'startTime', - description: - 'Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEvents is True)' + description: 'Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEvents is True)', }, { name: 'Updated', value: 'updated', - description: 'Order by last modification time (ascending).' + description: 'Order by last modification time (ascending).', } ], default: '', - description: 'The order of the events returned in the result.' + description: 'The order of the events returned in the result.', }, { displayName: 'Query', name: 'query', type: 'string', default: '', - description: - 'Free text search terms to find events that match these terms in any field, except for extended properties.' + description: 'Free text search terms to find events that match these terms in any field, except for extended properties.', }, { displayName: 'Show Deleted', name: 'showDeleted', type: 'boolean', default: false, - description: - 'Whether to include deleted events (with status equals "cancelled") in the result.' + description: 'Whether to include deleted events (with status equals "cancelled") in the result.', }, { displayName: 'Show Hidden Invitations', name: 'showHiddenInvitations', type: 'boolean', default: false, - description: 'Whether to include hidden invitations in the result.' + description: 'Whether to include hidden invitations in the result.', }, { displayName: 'Single Events', @@ -657,31 +711,31 @@ export const eventFields = [ type: 'boolean', default: false, description: `Whether to expand recurring events into instances and only return single one-off
- events and instances of recurring events, but not the underlying recurring events themselves.` + events and instances of recurring events, but not the underlying recurring events themselves.`, }, { displayName: 'Time Max', name: 'timeMax', type: 'dateTime', default: '', - description: `Upper bound (exclusive) for an event's start time to filter by` + description: `Upper bound (exclusive) for an event's start time to filter by`, }, { displayName: 'Time Min', name: 'timeMin', type: 'dateTime', default: '', - description: `Lower bound (exclusive) for an event's end time to filter by` + description: `Lower bound (exclusive) for an event's end time to filter by`, }, { displayName: 'Timezone', name: 'timeZone', type: 'options', typeOptions: { - loadOptionsMethod: 'getTimezones' + loadOptionsMethod: 'getTimezones', }, default: '', - description: `Time zone used in the response. The default is the time zone of the calendar.` + description: `Time zone used in the response. The default is the time zone of the calendar.`, }, { displayName: 'Updated Min', @@ -689,7 +743,7 @@ export const eventFields = [ type: 'dateTime', default: '', description: `Lower bound for an event's last modification time (as a RFC3339 timestamp) to filter by. - When specified, entries deleted since this time will always be included regardless of showDeleted` + When specified, entries deleted since this time will always be included regardless of showDeleted`, } ] }, @@ -706,11 +760,15 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: ['update'], - resource: ['event'] - } + operation: [ + 'update', + ], + resource: [ + 'event', + ], + }, }, - default: '' + default: '', }, { displayName: 'Event ID', @@ -719,11 +777,15 @@ export const eventFields = [ required: true, displayOptions: { show: { - operation: ['update'], - resource: ['event'] - } + operation: [ + 'update', + ], + resource: [ + 'event', + ], + }, }, - default: '' + default: '', }, { displayName: 'Use Default Reminders', @@ -731,9 +793,13 @@ export const eventFields = [ type: 'boolean', displayOptions: { show: { - operation: ['update'], - resource: ['event'] - } + operation: [ + 'update', + ], + resource: [ + 'event', + ], + }, }, default: true }, @@ -745,9 +811,13 @@ export const eventFields = [ default: {}, displayOptions: { show: { - operation: ['update'], - resource: ['event'] - } + operation: [ + 'update', + ], + resource: [ + 'event', + ], + }, }, options: [ { @@ -757,15 +827,15 @@ export const eventFields = [ options: [ { name: 'Yes', - value: 'yes' + value: 'yes', }, { name: 'No', - value: 'no' + value: 'no', } ], default: 'no', - description: 'Wheater the event is all day or not' + description: 'Wheater the event is all day or not', }, { displayName: 'Attendees', @@ -773,64 +843,62 @@ export const eventFields = [ type: 'string', typeOptions: { multipleValues: true, - multipleValueButtonText: 'Add Attendee' + multipleValueButtonText: 'Add Attendee', }, default: '', - description: 'The attendees of the event' + description: 'The attendees of the event', }, { displayName: 'Color', name: 'color', type: 'options', typeOptions: { - loadOptionsMethod: 'getColors' + loadOptionsMethod: 'getColors', }, default: '', - description: 'The color of the event.' + description: 'The color of the event.', }, { displayName: 'End', name: 'end', type: 'dateTime', default: '', - description: 'End time of the event.' + description: 'End time of the event.', }, { displayName: 'Guests Can Invite Others', name: 'guestsCanInviteOthers', type: 'boolean', default: true, - description: - 'Whether attendees other than the organizer can invite others to the event' + description: 'Whether attendees other than the organizer can invite others to the event', }, { displayName: 'Guests Can Modify', name: 'guestsCanModify', type: 'boolean', default: false, - description: - 'Whether attendees other than the organizer can modify the event' + description: 'Whether attendees other than the organizer can modify the event', }, { displayName: 'Guests Can See Other Guests', name: 'guestsCanSeeOtherGuests', type: 'boolean', default: true, - description: `Whether attendees other than the organizer can see who the event's attendees are.` + description: `Whether attendees other than the organizer can see who the event's attendees are.`, }, { displayName: 'ID', name: 'id', type: 'string', default: '', - description: 'Opaque identifier of the event' + description: 'Opaque identifier of the event', }, { displayName: 'Location', name: 'location', type: 'string', default: '', - description: 'Geographic location of the event as free-form text.' + description: 'Geographic location of the event as free-form text.', }, { displayName: 'Max Attendees', @@ -838,7 +906,7 @@ export const eventFields = [ type: 'number', default: 0, description: `The maximum number of attendees to include in the response.
- If there are more than the specified number of attendees, only the participant is returned` + If there are more than the specified number of attendees, only the participant is returned`, }, { displayName: 'Repeat Frecuency', @@ -847,44 +915,44 @@ export const eventFields = [ options: [ { name: 'Daily', - value: 'Daily' + value: 'Daily', }, { name: 'Weekly', - value: 'weekly' + value: 'weekly', }, { name: 'Monthly', - value: 'monthly' + value: 'monthly', }, { name: 'Yearly', - value: 'yearly' + value: 'yearly', } ], - default: '' + default: '', }, { displayName: 'Repeat Until', name: 'repeatUntil', type: 'dateTime', - default: '' + default: '', }, { displayName: 'Repeat How Many Times?', name: 'repeatHowManyTimes', type: 'number', typeOptions: { - minValue: 1 + minValue: 1, }, - default: 1 + default: 1, }, { displayName: 'Start', name: 'start', type: 'dateTime', default: '', - description: 'Start time of the event.' + description: 'Start time of the event.', }, { displayName: 'Send Updates', @@ -894,31 +962,28 @@ export const eventFields = [ { name: 'All', value: 'all', - description: ' Notifications are sent to all guests' + description: ' Notifications are sent to all guests', }, { name: 'External Only', value: 'externalOnly', - description: - 'Notifications are sent to non-Google Calendar guests only' + description: 'Notifications are sent to non-Google Calendar guests only', }, { name: 'None', value: 'none', - description: - ' No notifications are sent. This value should only be used for migration use case' + description: 'No notifications are sent. This value should only be used for migration use case', } ], - description: - 'Whether to send notifications about the creation of the new event', - default: '' + description: 'Whether to send notifications about the creation of the new event', + default: '', }, { displayName: 'Summary', name: 'summary', type: 'string', default: '', - description: 'Title of the event.' + description: 'Title of the event.', }, { displayName: 'Show Me As', @@ -928,27 +993,26 @@ export const eventFields = [ { name: 'Available', value: 'transparent', - description: 'The event does not block time on the calendar' + description: 'The event does not block time on the calendar', }, { name: 'Busy', value: 'opaque', - description: ' The event does block time on the calendar.' - } + description: ' The event does block time on the calendar.', + }, ], default: 'opaque', - description: 'Whether the event blocks time on the calendar' + description: 'Whether the event blocks time on the calendar', }, { displayName: 'Timezone', name: 'timezone', type: 'options', typeOptions: { - loadOptionsMethod: 'getTimezones' + loadOptionsMethod: 'getTimezones', }, default: '', - description: - 'The timezone the event will have set. By default events are schedule on n8n timezone ' + description: 'The timezone the event will have set. By default events are schedule on n8n timezone', }, { displayName: 'Visibility', @@ -958,32 +1022,28 @@ export const eventFields = [ { name: 'Confidential', value: 'confidential', - description: - 'The event is private. This value is provided for compatibility reasons.' + description: 'The event is private. This value is provided for compatibility reasons.', }, { name: 'Default', value: 'default', - description: - ' Uses the default visibility for events on the calendar.' + description: 'Uses the default visibility for events on the calendar.', }, { name: 'Public', value: 'public', - description: - 'The event is public and event details are visible to all readers of the calendar.' + description: 'The event is public and event details are visible to all readers of the calendar.', }, { name: 'Private', value: 'private', - description: - 'The event is private and only event attendees may view event details.' + description: 'The event is private and only event attendees may view event details.', } ], default: 'default', - description: 'Visibility of the event.' - } - ] + description: 'Visibility of the event.', + }, + ], }, { displayName: 'Reminders', @@ -997,10 +1057,16 @@ export const eventFields = [ required: false, displayOptions: { show: { - resource: ['event'], - operation: ['update'], - useDefaultReminders: [false] - } + resource: [ + 'event', + ], + operation: [ + 'update', + ], + useDefaultReminders: [ + false, + ], + }, }, options: [ { @@ -1014,14 +1080,14 @@ export const eventFields = [ options: [ { name: 'Email', - value: 'email' + value: 'email', }, { name: 'Popup', - value: 'popup' + value: 'popup', } ], - default: '' + default: '', }, { displayName: 'Minutes Before', @@ -1029,13 +1095,13 @@ export const eventFields = [ type: 'number', typeOptions: { minValue: 0, - maxValue: 40320 + maxValue: 40320, }, - default: 0 - } - ] - } + default: 0, + }, + ], + }, ], - description: `If the event doesn't use the default reminders, this lists the reminders specific to the event` + description: `If the event doesn't use the default reminders, this lists the reminders specific to the event`, } ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Google/Calendar/EventInterface.ts b/packages/nodes-base/nodes/Google/Calendar/EventInterface.ts index 72bf96cc80f..14cda0fe415 100644 --- a/packages/nodes-base/nodes/Google/Calendar/EventInterface.ts +++ b/packages/nodes-base/nodes/Google/Calendar/EventInterface.ts @@ -1,4 +1,6 @@ -import { IDataObject } from "n8n-workflow"; +import { + IDataObject, + } from 'n8n-workflow'; export interface IReminder { useDefault?: boolean; diff --git a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts index 53d3d9de838..dc0ddad9474 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts @@ -1,4 +1,6 @@ -import { IExecuteFunctions } from 'n8n-core'; +import { + IExecuteFunctions, +} from 'n8n-core'; import { IDataObject, @@ -6,14 +8,22 @@ import { INodeTypeDescription, INodeType, ILoadOptionsFunctions, - INodePropertyOptions + INodePropertyOptions, } from 'n8n-workflow'; -import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions'; +import { + googleApiRequest, + googleApiRequestAllItems, +} from './GenericFunctions'; -import { eventOperations, eventFields } from './EventDescription'; +import { + eventOperations, + eventFields, +} from './EventDescription'; -import { IEvent } from './EventInterface'; +import { + IEvent, +} from './EventInterface'; import * as moment from 'moment-timezone'; @@ -28,14 +38,14 @@ export class GoogleCalendar implements INodeType { description: 'Consume Google Calendar API.', defaults: { name: 'Google Calendar', - color: '#3E87E4' + color: '#3E87E4', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'googleCalendarOAuth2Api', - required: true + required: true, } ], properties: [ @@ -46,15 +56,15 @@ export class GoogleCalendar implements INodeType { options: [ { name: 'Event', - value: 'event' - } + value: 'event', + }, ], default: 'event', description: 'The resource to operate on.' }, ...eventOperations, - ...eventFields - ] + ...eventFields, + ], }; methods = { diff --git a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts index 5a7418a708f..46f70c85d69 100644 --- a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts +++ b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts @@ -1,13 +1,17 @@ -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 = { @@ -20,15 +24,15 @@ export class MessageBird implements INodeType { description: 'Sending SMS', defaults: { name: 'MessageBird', - color: '#2481d7' + color: '#2481d7', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'messageBirdApi', - required: true - } + required: true, + }, ], properties: [ { @@ -38,31 +42,32 @@ export class MessageBird implements INodeType { options: [ { name: 'SMS', - value: 'sms' - } + value: 'sms', + }, ], default: 'sms', - description: 'The resource to operate on.' + description: 'The resource to operate on.', }, - { displayName: 'Operation', name: 'operation', type: 'options', displayOptions: { show: { - resource: ['sms'] - } + resource: [ + 'sms', + ], + }, }, options: [ { 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.', }, // ---------------------------------- @@ -77,11 +82,15 @@ 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.' + description: 'The number from which to send the message.', }, { displayName: 'To', @@ -92,13 +101,16 @@ export class MessageBird implements INodeType { required: true, displayOptions: { show: { - operation: ['send'], - resource: ['sms'] - } + operation: [ + 'send', + ], + resource: [ + 'sms', + ], + }, }, - description: 'All recipients separated by commas.' + description: 'All recipients separated by commas.', }, - { displayName: 'Message', name: 'message', @@ -107,11 +119,15 @@ export class MessageBird implements INodeType { required: true, displayOptions: { show: { - operation: ['send'], - resource: ['sms'] - } + operation: [ + 'send', + ], + resource: [ + 'sms', + ], + }, }, - description: 'The message to be send.' + description: 'The message to be send.', }, { displayName: 'Additional Fields', @@ -125,8 +141,7 @@ export class MessageBird implements INodeType { name: 'createdDatetime', type: 'dateTime', 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', @@ -135,27 +150,26 @@ export class MessageBird implements INodeType { options: [ { name: 'Auto', - value: 'auto' + value: 'auto', }, { name: 'Plain', - value: 'plain' + value: 'plain', }, { name: 'Unicode', - value: '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', @@ -163,8 +177,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', @@ -174,39 +187,36 @@ export class MessageBird implements INodeType { options: [ { name: 'Flash', - value: 1 + value: 1, }, { name: 'Normal', - value: 0 - } + value: 0, + }, ], default: 1, - description: - 'Indicated the message type. 1 is a normal message, 0 is a flash message.' + 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.', }, { displayName: 'Scheduled Date-time', name: 'scheduledDatetime', type: 'dateTime', default: '', - 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', @@ -215,28 +225,26 @@ export class MessageBird implements INodeType { options: [ { name: 'Binary', - value: 'binary' + value: 'binary', }, { name: 'Flash', - value: 'flash' + value: 'flash', }, { name: 'SMS', - value: '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', 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', @@ -244,13 +252,13 @@ export class MessageBird implements INodeType { type: 'number', 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 bae541e13b1ed44be998e829b55d56f82c90d815 Mon Sep 17 00:00:00 2001 From: Tanay Pant <7481165+tanay1337@users.noreply.github.com> Date: Thu, 18 Jun 2020 10:08:31 +0200 Subject: [PATCH 100/120] :books: Make a few language changes (#681) --- CONTRIBUTING.md | 10 +++++----- LICENSE.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 568f8fc9a76..fcc046beeff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,7 @@ The most important directories: execution, active webhooks and workflows - [/packages/editor-ui](/packages/editor-ui) - Vue frontend workflow editor - - [/packages/node-dev](/packages/node-dev) - Simple CLI to create new n8n-nodes + - [/packages/node-dev](/packages/node-dev) - CLI to create new n8n-nodes - [/packages/nodes-base](/packages/nodes-base) - Base n8n nodes - [/packages/workflow](/packages/workflow) - Workflow code with interfaces which get used by front- & backend @@ -159,7 +159,7 @@ tests of all packages. ## Create Custom Nodes -It is very easy to create own nodes for n8n. More information about that can +It is very straightforward to create your own nodes for n8n. More information about that can be found in the documentation of "n8n-node-dev" which is a small CLI which helps with n8n-node-development. @@ -177,9 +177,9 @@ If you want to create a node which should be added to n8n follow these steps: 1. Create a new folder for the new node. For a service named "Example" the folder would be called: `/packages/nodes-base/nodes/Example` - 1. If there is already a similar node simply copy the existing one in the new folder and rename it. If none exists yet, create a boilerplate node with [n8n-node-dev](https://github.com/n8n-io/n8n/tree/master/packages/node-dev) and copy that one in the folder. + 1. If there is already a similar node, copy the existing one in the new folder and rename it. If none exists yet, create a boilerplate node with [n8n-node-dev](https://github.com/n8n-io/n8n/tree/master/packages/node-dev) and copy that one in the folder. - 1. If the node needs credentials because it has to authenticate with an API or similar create new ones. Existing ones can be found in folder `/packages/nodes-base/credentials`. Also there it is the easiest to simply copy existing similar ones. + 1. If the node needs credentials because it has to authenticate with an API or similar create new ones. Existing ones can be found in folder `/packages/nodes-base/credentials`. Also there it is the easiest to copy existing similar ones. 1. Add the path to the new node (and optionally credentials) to package.json of `nodes-base`. It already contains a property `n8n` with its own keys `credentials` and `nodes`. @@ -236,6 +236,6 @@ docsify serve ./docs That we do not have any potential problems later it is sadly necessary to sign a [Contributor License Agreement](CONTRIBUTOR_LICENSE_AGREEMENT.md). That can be done literally with the push of a button. -We used the most simple one that exists. It is from [Indie Open Source](https://indieopensource.com/forms/cla) which uses plain English and is literally just a few lines long. +We used the most simple one that exists. It is from [Indie Open Source](https://indieopensource.com/forms/cla) which uses plain English and is literally only a few lines long. A bot will automatically comment on the pull request once it got opened asking for the agreement to be signed. Before it did not get signed it is sadly not possible to merge it in. diff --git a/LICENSE.md b/LICENSE.md index aac54547eb9..c2aec2148e7 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -215,7 +215,7 @@ Licensor: n8n GmbH same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright [2020] [n8n GmbH] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From d9906e1b24989db2c200f38542127cc98846ab0a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 18 Jun 2020 10:12:13 +0200 Subject: [PATCH 101/120] :books: Text fix --- LICENSE.md | 2 +- packages/cli/LICENSE.md | 2 +- packages/core/LICENSE.md | 2 +- packages/editor-ui/LICENSE.md | 2 +- packages/node-dev/LICENSE.md | 2 +- packages/nodes-base/LICENSE.md | 2 +- packages/workflow/LICENSE.md | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index c2aec2148e7..24a7d38fc94 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -215,7 +215,7 @@ Licensor: n8n GmbH same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2020] [n8n GmbH] + Copyright 2020 n8n GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/cli/LICENSE.md b/packages/cli/LICENSE.md index aac54547eb9..24a7d38fc94 100644 --- a/packages/cli/LICENSE.md +++ b/packages/cli/LICENSE.md @@ -215,7 +215,7 @@ Licensor: n8n GmbH same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2020 n8n GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/core/LICENSE.md b/packages/core/LICENSE.md index aac54547eb9..24a7d38fc94 100644 --- a/packages/core/LICENSE.md +++ b/packages/core/LICENSE.md @@ -215,7 +215,7 @@ Licensor: n8n GmbH same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2020 n8n GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/editor-ui/LICENSE.md b/packages/editor-ui/LICENSE.md index aac54547eb9..24a7d38fc94 100644 --- a/packages/editor-ui/LICENSE.md +++ b/packages/editor-ui/LICENSE.md @@ -215,7 +215,7 @@ Licensor: n8n GmbH same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2020 n8n GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/node-dev/LICENSE.md b/packages/node-dev/LICENSE.md index aac54547eb9..24a7d38fc94 100644 --- a/packages/node-dev/LICENSE.md +++ b/packages/node-dev/LICENSE.md @@ -215,7 +215,7 @@ Licensor: n8n GmbH same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2020 n8n GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/nodes-base/LICENSE.md b/packages/nodes-base/LICENSE.md index aac54547eb9..24a7d38fc94 100644 --- a/packages/nodes-base/LICENSE.md +++ b/packages/nodes-base/LICENSE.md @@ -215,7 +215,7 @@ Licensor: n8n GmbH same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2020 n8n GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/workflow/LICENSE.md b/packages/workflow/LICENSE.md index aac54547eb9..24a7d38fc94 100644 --- a/packages/workflow/LICENSE.md +++ b/packages/workflow/LICENSE.md @@ -215,7 +215,7 @@ Licensor: n8n GmbH same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2020 n8n GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 5db2ce2924f08a462ad58616e0e9ab779fdf1422 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 20 Jun 2020 12:28:56 +0200 Subject: [PATCH 102/120] :bug: Fix issue that changed static data in webhook function did not get saved --- packages/cli/src/WebhookHelpers.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cli/src/WebhookHelpers.ts b/packages/cli/src/WebhookHelpers.ts index d39e3f5deef..64e4c101ff2 100644 --- a/packages/cli/src/WebhookHelpers.ts +++ b/packages/cli/src/WebhookHelpers.ts @@ -149,6 +149,9 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo }; } + // Save static data if it changed + await WorkflowHelpers.saveStaticData(workflow); + if (webhookData.webhookDescription['responseHeaders'] !== undefined) { const responseHeaders = workflow.getComplexParameterValue(workflowStartNode, webhookData.webhookDescription['responseHeaders'], undefined) as { entries?: Array<{ From 6d7368d7237d9fc5020da8ce872fc18823ee6fce Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sat, 20 Jun 2020 11:51:11 -0400 Subject: [PATCH 103/120] :zap: Added description and fixed issue with colors (#686) --- .../nodes/Google/Calendar/EventDescription.ts | 18 ++++++++++++++++++ .../nodes/Google/Calendar/GenericFunctions.ts | 10 ++++++++-- .../Google/Calendar/GoogleCalendar.node.ts | 8 ++++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts b/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts index 44af95a1515..545ed841f16 100644 --- a/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts +++ b/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts @@ -177,6 +177,15 @@ export const eventFields = [ default: '', description: 'The color of the event.', }, + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + }, { displayName: 'Guests Can Invite Others', name: 'guestsCanInviteOthers', @@ -858,6 +867,15 @@ export const eventFields = [ default: '', description: 'The color of the event.', }, + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + }, { displayName: 'End', name: 'end', diff --git a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts index 18d808cef79..caf4c9868d4 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts @@ -33,9 +33,15 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'googleCalendarOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.message) { + if (error.response && error.response.body && error.response.body.error) { + + let errors = error.response.body.error.errors; + + errors = errors.map((e: IDataObject) => e.message); // Try to return the error prettier - throw new Error(`Google Calendar error response [${error.statusCode}]: ${error.response.body.message}`); + throw new Error( + `Google Calendar error response [${error.statusCode}]: ${errors.join('|')}` + ); } throw error; } diff --git a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts index dc0ddad9474..4c8208590e2 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts @@ -97,16 +97,16 @@ export class GoogleCalendar implements INodeType { this: ILoadOptionsFunctions ): Promise { const returnData: INodePropertyOptions[] = []; - const { calendar } = await googleApiRequest.call( + const { event } = await googleApiRequest.call( this, 'GET', '/calendar/v3/colors' ); - for (const key of Object.keys(calendar)) { - const colorName = calendar[key].background; + for (const key of Object.keys(event)) { + const colorName = `Background: ${event[key].background} - Foreground: ${event[key].foreground}`; const colorId = key; returnData.push({ - name: `${colorName} - ${colorId}`, + name: `${colorName}`, value: colorId }); } From 3d45b67d50477dc02bdc2b73cdd90e33e71e45e7 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sat, 20 Jun 2020 12:08:30 -0400 Subject: [PATCH 104/120] :bug: Fixes issue #663 and #664 --- .../nodes/Gitlab/GenericFunctions.ts | 7 ++++-- .../nodes/Gitlab/GitlabTrigger.node.ts | 23 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts b/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts index 8f1811b8c74..6362896cfff 100644 --- a/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts @@ -1,6 +1,7 @@ import { IExecuteFunctions, IHookFunctions, + ILoadOptionsFunctions, } from 'n8n-core'; import { @@ -16,7 +17,7 @@ import { * @param {object} body * @returns {Promise} */ -export async function gitlabApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query?: object): Promise { // tslint:disable-line:no-any +export async function gitlabApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: object): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('gitlabApi'); if (credentials === undefined) { throw new Error('No credentials got returned!'); @@ -34,7 +35,9 @@ export async function gitlabApiRequest(this: IHookFunctions | IExecuteFunctions, }; try { - return await this.helpers.request(options); + //@ts-ignore + return await this.helpers?.request(options); + } catch (error) { if (error.statusCode === 401) { // Return a clear error diff --git a/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts b/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts index 28987c2458f..a97f2be536d 100644 --- a/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts +++ b/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts @@ -135,7 +135,10 @@ export class GitlabTrigger implements INodeType { // Webhook got created before so check if it still exists const owner = this.getNodeParameter('owner') as string; const repository = this.getNodeParameter('repository') as string; - const endpoint = `/projects/${owner}%2F${repository}/hooks/${webhookData.webhookId}`; + + const path = (`${owner}/${repository}`).replace(/\//g,'%2F'); + + const endpoint = `/projects/${path}/hooks/${webhookData.webhookId}`; try { await gitlabApiRequest.call(this, 'GET', endpoint, {}); @@ -175,15 +178,22 @@ export class GitlabTrigger implements INodeType { events[`${e}_events`] = true; } - const endpoint = `/projects/${owner}%2F${repository}/hooks`; + // gitlab set the push_events to true when the field it's not sent. + // set it to false when it's not picked by the user. + if (events['push_events'] === undefined) { + events['push_events'] = false; + } + + const path = (`${owner}/${repository}`).replace(/\//g,'%2F'); + + const endpoint = `/projects/${path}/hooks`; const body = { url: webhookUrl, - events, + ...events, enable_ssl_verification: false, }; - let responseData; try { responseData = await gitlabApiRequest.call(this, 'POST', endpoint, body); @@ -208,7 +218,10 @@ export class GitlabTrigger implements INodeType { if (webhookData.webhookId !== undefined) { const owner = this.getNodeParameter('owner') as string; const repository = this.getNodeParameter('repository') as string; - const endpoint = `/projects/${owner}%2F${repository}/hooks/${webhookData.webhookId}`; + + const path = (`${owner}/${repository}`).replace(/\//g,'%2F'); + + const endpoint = `/projects/${path}/hooks/${webhookData.webhookId}`; const body = {}; try { From 54a670c0de778d6db8d64ecf39c21f50b9901f0f Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sat, 20 Jun 2020 12:11:11 -0400 Subject: [PATCH 105/120] :bug: Fixes issue #651 and #652 (#668) * :bug: Fixes issue #651 and #652 * :zap: Added description to username field --- .../credentials/SlackOAuth2Api.credentials.ts | 3 --- .../nodes/Slack/MessageDescription.ts | 22 +------------------ packages/nodes-base/nodes/Slack/Slack.node.ts | 8 +++---- 3 files changed, 5 insertions(+), 28 deletions(-) diff --git a/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts b/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts index b56699fe680..0426ceee02d 100644 --- a/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts @@ -6,15 +6,12 @@ import { //https://api.slack.com/authentication/oauth-v2 const userScopes = [ 'chat:write', - 'conversations:history', - 'conversations:read', 'files:read', 'files:write', 'stars:read', 'stars:write', ]; - export class SlackOAuth2Api implements ICredentialType { name = 'slackOAuth2Api'; extends = [ diff --git a/packages/nodes-base/nodes/Slack/MessageDescription.ts b/packages/nodes-base/nodes/Slack/MessageDescription.ts index d5170aba67e..8d91663155c 100644 --- a/packages/nodes-base/nodes/Slack/MessageDescription.ts +++ b/packages/nodes-base/nodes/Slack/MessageDescription.ts @@ -111,7 +111,7 @@ export const messageFields = [ ], }, }, - description: 'Set the bot\'s user name.', + description: 'Set the bot\'s user name. This field will be ignored if you are using a user token.', }, { displayName: 'JSON parameters', @@ -486,26 +486,6 @@ export const messageFields = [ }, description: `Timestamp of the message to be updated.`, }, - { - displayName: 'As User', - name: 'as_user', - type: 'boolean', - default: false, - displayOptions: { - show: { - authentication: [ - 'accessToken', - ], - operation: [ - 'update' - ], - resource: [ - 'message', - ], - }, - }, - description: 'Pass true to update the message as the authed user. Bot users in this context are considered authed users.', - }, { displayName: 'Update Fields', name: 'updateFields', diff --git a/packages/nodes-base/nodes/Slack/Slack.node.ts b/packages/nodes-base/nodes/Slack/Slack.node.ts index 57fe569d228..befe931fb5a 100644 --- a/packages/nodes-base/nodes/Slack/Slack.node.ts +++ b/packages/nodes-base/nodes/Slack/Slack.node.ts @@ -454,6 +454,10 @@ export class Slack implements INodeType { body.username = this.getNodeParameter('username', i) as string; } + // ignore body.as_user as it's deprecated + + delete body.as_user; + if (!jsonParameters) { const attachments = this.getNodeParameter('attachments', i, []) as unknown as Attachment[]; const blocksUi = (this.getNodeParameter('blocksUi', i, []) as IDataObject).blocksValues as IDataObject[]; @@ -691,10 +695,6 @@ export class Slack implements INodeType { ts, }; - if (authentication === 'accessToken') { - body.as_user = this.getNodeParameter('as_user', i) as boolean; - } - // The node does save the fields data differently than the API // expects so fix the data befre we send the request for (const attachment of attachments) { From 4c9b0162efb205a551640e67a479c58dcf57bc09 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 22 Jun 2020 21:23:50 +0200 Subject: [PATCH 106/120] :zap: Improve parameter name --- packages/nodes-base/nodes/Twitter/TweetDescription.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Twitter/TweetDescription.ts b/packages/nodes-base/nodes/Twitter/TweetDescription.ts index edd12bbf5e5..12cb87ebfee 100644 --- a/packages/nodes-base/nodes/Twitter/TweetDescription.ts +++ b/packages/nodes-base/nodes/Twitter/TweetDescription.ts @@ -219,7 +219,7 @@ export const tweetFields = [ description: 'The entities node will not be included when set to false', }, { - displayName: 'Lang', + displayName: 'Language', name: 'lang', type: 'options', typeOptions: { From 93afb0940b34b82186f5ec1f6a945f501cf9c307 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 23 Jun 2020 16:12:53 +0200 Subject: [PATCH 107/120] :zap: Use rest endpoint configuration --- packages/cli/src/Server.ts | 74 +++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 5a9099bd63d..1b3750a20ca 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -113,6 +113,7 @@ class App { activeExecutionsInstance: ActiveExecutions.ActiveExecutions; push: Push.Push; versions: IPackageVersions | undefined; + restEndpoint: string; protocol: string; sslKey: string; @@ -127,6 +128,7 @@ class App { this.saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string; this.saveManualExecutions = config.get('executions.saveDataManualExecutions') as boolean; this.timezone = config.get('generic.timezone') as string; + this.restEndpoint = config.get('endpoints.rest') as string; this.activeWorkflowRunner = ActiveWorkflowRunner.getInstance(); this.testWebhooks = TestWebhooks.getInstance(); @@ -235,7 +237,7 @@ class App { // Get push connections this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { - if (req.url.indexOf('/rest/push') === 0) { + if (req.url.indexOf(`/${this.restEndpoint}/push`) === 0) { // TODO: Later also has to add some kind of authentication token if (req.query.sessionId === undefined) { next(new Error('The query parameter "sessionId" is missing!')); @@ -284,7 +286,7 @@ class App { this.app.use(history({ rewrites: [ { - from: new RegExp(`^\/(rest|healthz|css|js|${this.endpointWebhook}|${this.endpointWebhookTest})\/?.*$`), + from: new RegExp(`^\/(${this.restEndpoint}|healthz|css|js|${this.endpointWebhook}|${this.endpointWebhookTest})\/?.*$`), to: (context) => { return context.parsedUrl!.pathname!.toString(); } @@ -354,7 +356,7 @@ class App { // Creates a new workflow - this.app.post('/rest/workflows', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.post(`/${this.restEndpoint}/workflows`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const newWorkflowData = req.body as IWorkflowBase; @@ -377,7 +379,7 @@ class App { // Reads and returns workflow data from an URL - this.app.get('/rest/workflows/from-url', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/workflows/from-url`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { if (req.query.url === undefined) { throw new ResponseHelper.ResponseError(`The parameter "url" is missing!`, undefined, 400); } @@ -405,7 +407,7 @@ class App { // Returns workflows - this.app.get('/rest/workflows', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/workflows`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const findQuery = {} as FindManyOptions; if (req.query.filter) { findQuery.where = JSON.parse(req.query.filter as string); @@ -425,7 +427,7 @@ class App { // Returns a specific workflow - this.app.get('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/workflows/:id`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const result = await Db.collections.Workflow!.findOne(req.params.id); if (result === undefined) { @@ -439,7 +441,7 @@ class App { // Updates an existing workflow - this.app.patch('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.patch(`/${this.restEndpoint}/workflows/:id`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const newWorkflowData = req.body as IWorkflowBase; const id = req.params.id; @@ -510,7 +512,7 @@ class App { // Deletes a specific workflow - this.app.delete('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.delete(`/${this.restEndpoint}/workflows/:id`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const id = req.params.id; await this.externalHooks.run('workflow.delete', [id]); @@ -526,7 +528,7 @@ class App { })); - this.app.post('/rest/workflows/run', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.post(`/${this.restEndpoint}/workflows/run`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const workflowData = req.body.workflowData; const runData: IRunData | undefined = req.body.runData; const startNodes: string[] | undefined = req.body.startNodes; @@ -575,7 +577,7 @@ class App { // Returns parameter values which normally get loaded from an external API or // get generated dynamically - this.app.get('/rest/node-parameter-options', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/node-parameter-options`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const nodeType = req.query.nodeType as string; let credentials: INodeCredentials | undefined = undefined; const currentNodeParameters = JSON.parse('' + req.query.currentNodeParameters) as INodeParameters; @@ -597,7 +599,7 @@ class App { // Returns all the node-types - this.app.get('/rest/node-types', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/node-types`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const returnData: INodeTypeDescription[] = []; @@ -620,7 +622,7 @@ class App { // Returns the node icon - this.app.get(['/rest/node-icon/:nodeType', '/rest/node-icon/:scope/:nodeType'], async (req: express.Request, res: express.Response): Promise => { + this.app.get([`/${this.restEndpoint}/node-icon/:nodeType`, `/${this.restEndpoint}/node-icon/:scope/:nodeType`], async (req: express.Request, res: express.Response): Promise => { const nodeTypeName = `${req.params.scope ? `${req.params.scope}/` : ''}${req.params.nodeType}`; const nodeTypes = NodeTypes(); @@ -654,13 +656,13 @@ class App { // Returns the active workflow ids - this.app.get('/rest/active', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/active`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { return this.activeWorkflowRunner.getActiveWorkflows(); })); // Returns if the workflow with the given id had any activation errors - this.app.get('/rest/active/error/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/active/error/:id`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const id = req.params.id; return this.activeWorkflowRunner.getActivationError(id); })); @@ -673,7 +675,7 @@ class App { // Deletes a specific credential - this.app.delete('/rest/credentials/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.delete(`/${this.restEndpoint}/credentials/:id`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const id = req.params.id; await this.externalHooks.run('credentials.delete', [id]); @@ -684,7 +686,7 @@ class App { })); // Creates new credentials - this.app.post('/rest/credentials', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.post(`/${this.restEndpoint}/credentials`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const incomingData = req.body; if (!incomingData.name || incomingData.name.length < 3) { @@ -742,7 +744,7 @@ class App { // Updates existing credentials - this.app.patch('/rest/credentials/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.patch(`/${this.restEndpoint}/credentials/:id`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const incomingData = req.body; const id = req.params.id; @@ -823,7 +825,7 @@ class App { // Returns specific credentials - this.app.get('/rest/credentials/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/credentials/:id`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const findQuery = {} as FindManyOptions; // Make sure the variable has an expected value @@ -858,7 +860,7 @@ class App { // Returns all the saved credentials - this.app.get('/rest/credentials', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/credentials`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const findQuery = {} as FindManyOptions; if (req.query.filter) { findQuery.where = JSON.parse(req.query.filter as string); @@ -900,7 +902,7 @@ class App { // Returns all the credential types which are defined in the loaded n8n-modules - this.app.get('/rest/credential-types', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/credential-types`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const returnData: ICredentialType[] = []; @@ -918,7 +920,7 @@ class App { // ---------------------------------------- // Authorize OAuth Data - this.app.get('/rest/oauth1-credential/auth', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/oauth1-credential/auth`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { if (req.query.id === undefined) { throw new Error('Required credential id is missing!'); } @@ -961,7 +963,7 @@ class App { }, }); - const callback = `${WebhookHelpers.getWebhookBaseUrl()}rest/oauth1-credential/callback?cid=${req.query.id}`; + const callback = `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth1-credential/callback?cid=${req.query.id}`; const options: RequestOptions = { method: 'POST', @@ -1000,7 +1002,7 @@ class App { })); // Verify and store app code. Generate access tokens and store for respective credential. - this.app.get('/rest/oauth1-credential/callback', async (req: express.Request, res: express.Response) => { + this.app.get(`/${this.restEndpoint}/oauth1-credential/callback`, async (req: express.Request, res: express.Response) => { const { oauth_verifier, oauth_token, cid } = req.query; if (oauth_verifier === undefined || oauth_token === undefined) { @@ -1072,7 +1074,7 @@ class App { // Authorize OAuth Data - this.app.get('/rest/oauth2-credential/auth', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/oauth2-credential/auth`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { if (req.query.id === undefined) { throw new Error('Required credential id is missing!'); } @@ -1113,7 +1115,7 @@ class App { clientSecret: _.get(oauthCredentials, 'clientSecret', '') as string, accessTokenUri: _.get(oauthCredentials, 'accessTokenUrl', '') as string, authorizationUri: _.get(oauthCredentials, 'authUrl', '') as string, - redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}rest/oauth2-credential/callback`, + redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`, scopes: _.split(_.get(oauthCredentials, 'scope', 'openid,') as string, ','), state: stateEncodedStr, }); @@ -1146,7 +1148,7 @@ class App { // ---------------------------------------- // Verify and store app code. Generate access tokens and store for respective credential. - this.app.get('/rest/oauth2-credential/callback', async (req: express.Request, res: express.Response) => { + this.app.get(`/${this.restEndpoint}/oauth2-credential/callback`, async (req: express.Request, res: express.Response) => { const {code, state: stateEncoded } = req.query; if (code === undefined || stateEncoded === undefined) { @@ -1206,7 +1208,7 @@ class App { clientSecret: _.get(oauthCredentials, 'clientSecret', '') as string, accessTokenUri: _.get(oauthCredentials, 'accessTokenUrl', '') as string, authorizationUri: _.get(oauthCredentials, 'authUrl', '') as string, - redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}rest/oauth2-credential/callback`, + redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`, scopes: _.split(_.get(oauthCredentials, 'scope', 'openid,') as string, ',') }); @@ -1246,7 +1248,7 @@ class App { // Returns all finished executions - this.app.get('/rest/executions', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/executions`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { let filter: any = {}; // tslint:disable-line:no-any if (req.query.filter) { @@ -1311,7 +1313,7 @@ class App { // Returns a specific execution - this.app.get('/rest/executions/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/executions/:id`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const result = await Db.collections.Execution!.findOne(req.params.id); if (result === undefined) { @@ -1325,7 +1327,7 @@ class App { // Retries a failed execution - this.app.post('/rest/executions/:id/retry', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.post(`/${this.restEndpoint}/executions/:id/retry`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { // Get the data to execute const fullExecutionDataFlatted = await Db.collections.Execution!.findOne(req.params.id); @@ -1399,7 +1401,7 @@ class App { // Delete Executions // INFORMATION: We use POST instead of DELETE to not run into any issues // with the query data getting to long - this.app.post('/rest/executions/delete', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.post(`/${this.restEndpoint}/executions/delete`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const deleteData = req.body as IExecutionDeleteFilter; if (deleteData.deleteBefore !== undefined) { @@ -1426,7 +1428,7 @@ class App { // Returns all the currently working executions - this.app.get('/rest/executions-current', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/executions-current`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const executingWorkflows = this.activeExecutionsInstance.getActiveExecutions(); const returnData: IExecutionsSummary[] = []; @@ -1455,7 +1457,7 @@ class App { })); // Forces the execution to stop - this.app.post('/rest/executions-current/:id/stop', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.post(`/${this.restEndpoint}/executions-current/:id/stop`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const executionId = req.params.id; // Stopt he execution and wait till it is done and we got the data @@ -1477,7 +1479,7 @@ class App { // Removes a test webhook - this.app.delete('/rest/test-webhook/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.delete(`/${this.restEndpoint}/test-webhook/:id`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const workflowId = req.params.id; return this.testWebhooks.cancelTestWebhook(workflowId); })); @@ -1489,7 +1491,7 @@ class App { // ---------------------------------------- // Returns all the available timezones - this.app.get('/rest/options/timezones', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/options/timezones`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { return timezones; })); @@ -1502,7 +1504,7 @@ class App { // Returns the settings which are needed in the UI - this.app.get('/rest/settings', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { + this.app.get(`/${this.restEndpoint}/settings`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { return { endpointWebhook: this.endpointWebhook, endpointWebhookTest: this.endpointWebhookTest, From 6a96587c5041ed9a6c51e84fc87c92179e79f460 Mon Sep 17 00:00:00 2001 From: Ron <43244104+rons4@users.noreply.github.com> Date: Tue, 23 Jun 2020 17:18:01 +0200 Subject: [PATCH 108/120] Initial version. --- .../nodes/SIGNL4/GenericFunctions.ts | 64 +++++ .../nodes-base/nodes/SIGNL4/signl4.node.ts | 233 ++++++++++++++++++ packages/nodes-base/nodes/SIGNL4/signl4.png | Bin 0 -> 3045 bytes 3 files changed, 297 insertions(+) create mode 100644 packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/SIGNL4/signl4.node.ts create mode 100644 packages/nodes-base/nodes/SIGNL4/signl4.png diff --git a/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts b/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts new file mode 100644 index 00000000000..525a0b1ef9b --- /dev/null +++ b/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts @@ -0,0 +1,64 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + IDataObject, +} from 'n8n-workflow'; + +/** + * Make an API request to MSG91 + * + * @param {IHookFunctions | IExecuteFunctions} this + * @param {object} message + * @returns {Promise} + */ +export async function SIGNL4ApiRequest(this: IHookFunctions | IExecuteFunctions, message: IDataObject): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('SIGNL4Api'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const teamsecret = credentials!.teamSecret as string; + + var response = null; + try { + response = await this.helpers.request({ + headers: {'Content-Type': 'application/json'}, + method: 'POST', + body: message, + qs: {}, + uri: `https://connect.signl4.com/webhook/${teamsecret}`, + json: true + }); + } + catch (error) { + + throw new Error(`I am behind. Status: ${JSON.stringify(teamsecret)}`); + + if (error && error.message) { + throw new Error(`Error sending the SIGNL4 request. Error: ${JSON.stringify(error.message)}`); + } + + throw new Error('Error sending the SIGNL4 request.'); + + throw error; + } + + return response; +} + + +function setPayload(credentials: ICredentialDataDecryptedObject, o?: IDataObject) { + if (!o) { + o = {}; + } + + o.p = credentials!.teamSecret as string; + o.json = 1; + o.sendwith = 'n8n'; + + return o; +} diff --git a/packages/nodes-base/nodes/SIGNL4/signl4.node.ts b/packages/nodes-base/nodes/SIGNL4/signl4.node.ts new file mode 100644 index 00000000000..cc665eadd46 --- /dev/null +++ b/packages/nodes-base/nodes/SIGNL4/signl4.node.ts @@ -0,0 +1,233 @@ +import {IExecuteFunctions,} from 'n8n-core'; +import {IDataObject, INodeExecutionData, INodeType, INodeTypeDescription,} from 'n8n-workflow'; +import {SIGNL4ApiRequest} from './GenericFunctions'; + +export class SIGNL4 implements INodeType { + description: INodeTypeDescription = { + displayName: 'SIGNL4', + name: 'SIGNL4', + icon: 'file:signl4.png', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Send SIGNL4 alert.', + defaults: { + name: 'SIGNL4', + color: '#0000FF', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'SIGNL4Api', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Alert', + value: 'alert', + }, + ], + default: 'alert', + description: 'The resource to operate on.', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'alert', + ], + }, + }, + options: [ + { + name: 'Send Alert', + value: 'send', + description: 'Send SIGNL4 alert.', + }, + ], + default: 'send', + description: 'The operation to perform.', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + default: 'SIGNL4 Alert', + placeholder: 'SIGNL4 Alert', + required: true, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'alert', + ], + }, + }, + description: 'The subject of the SIGNL4 alert.', + }, + { + displayName: 'Body', + name: 'body', + type: 'string', + default: '', + placeholder: 'Alert description.', + required: false, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'alert', + ], + }, + }, + description: 'A more detailed description for the alert.', + }, + { + displayName: 'S4-Service', + name: 'xS4Service', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'alert', + ], + }, + }, + description: 'Assigns the alert to the service/system category with the specified name.', + }, + { + displayName: 'S4-Location', + name: 'xS4Location', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'alert', + ], + }, + }, + description: 'Transmit location information (\'latitude, longitude\') with your event and display a map in the mobile app.', + }, + { + displayName: 'S4-AlertingScenario', + name: 'xS4AlertingScenario', + type: 'options', + options: [ + { + name: 'single_ack', + value: 'single_ack', + }, + { + name: 'multi_ack', + value: 'multi_ack', + }, + ], + default: 'single_ack', + required: false, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'alert', + ], + }, + }, + description: 'Pass \'single_ack\' if only one person needs to confirm this Signl. Pass \'multi_ack\' in case this alert must be confirmed by the number of people who are on duty at the time this Singl is raised.', + }, + { + displayName: 'S4-ExternalID', + name: 'xS4ExternalId', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'alert', + ], + }, + }, + description: 'If the event originates from a record in a 3rd party system, use this parameter to pass the unique ID of that record. That ID will be communicated in outbound webhook notifications from SIGNL4, which is great for correlation/synchronization of that record with the alert.', + }, + { + displayName: 'S4-Filtering', + name: 'xS4Filtering', + type: 'boolean', + default: 'false', + required: false, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'alert', + ], + }, + }, + description: 'Specify a boolean value of true or false to apply event filtering for this event, or not. If set to true, the event will only trigger a notification to the team, if it contains at least one keyword from one of your services and system categories (i.e. it is whitelisted)', + } + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const returnData: IDataObject[] = []; + + for (let i = 0; i < this.getInputData().length; i++) { + const resource = this.getNodeParameter('resource', i); + if ('alert' !== resource) { + throw new Error(`The resource "${resource}" is not known!`); + } + + const operation = this.getNodeParameter('operation', i); + if ('send' !== operation) { + throw new Error(`The operation "${operation}" is not known!`); + } + + // Assemble JSON data + var message = { + Subject: this.getNodeParameter('subject', i), + Body: this.getNodeParameter('body', i) + } as IDataObject; + message['X-S4-Service'] = this.getNodeParameter('xS4Service', i); + message['X-S4-Location'] = this.getNodeParameter('xS4Location', i); + message['X-S4-AlertingScenario'] = this.getNodeParameter('xS4AlertingScenario', i); + message['X-S4-ExternalID'] = this.getNodeParameter('xS4ExternalId', i); + message['X-S4-Filtering'] = this.getNodeParameter('xS4Filtering', i); + + const responseData = await SIGNL4ApiRequest.call(this, message); + + returnData.push(responseData); + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/SIGNL4/signl4.png b/packages/nodes-base/nodes/SIGNL4/signl4.png new file mode 100644 index 0000000000000000000000000000000000000000..205c94a5d2ba0e8d25d7b792050e341a8c8ab29b GIT binary patch literal 3045 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf00v@9M??Vs0RI60 zpuMM)00007bV*G`2jK|@3RSOzj|T*UUHjarW6~ukZWz-rwG>1;C@6 zG_Zs8q`^r?*s`pX%cV)!S{7kV{zl9%H+K5}q|r!(MT#Ebr;U02QR*kN1U+VypG_O` z?-kl2+(^#mABhvBS_G7u=K*t%tU_XS{&53QUiGeN^WG3|Nmzu$BP1z_u`uM`#~+(=er;sMzdNv2{z2hEyhs&bN|=T6|#E^Sv8K{6@TD9jgV z=3mMkT~j7X&^nK;lHw%18JJ)?!Bl0D7wHlk%H6}c&E&Yla-4;PHEt&$E6Zg_CQEW6yDACvr7W!J@bjyf zmjcv=kBzoOV1^c)q(#Td9!845NXf%+(I-F7I+2z_W=V_if|301(Uh>UE=6vvxhk@< zPLlZO!X&K8NVYcA;<6In3lxFHU3h?blJ7p5l{Z)5-$EGLi^uB~CMwrMt?4 zp;b@MSXkc)Y|Jw4%3#wMzKQG=pNEz1;G679y>apKDisv=JdVNp@5JDdVGQPh19_63 z@rjSp;&O4{z5Uf(R!)=Xwi|B+K5;$9<2ly*J3yv7h4NDm;_dtHL?u6=p)L#LYwZ-t zMzI5T*4*tdge~;Sm1)tnB)z`V!lv#tHs5>~vR8f?R)S9 zd+AT8R?B9gl9-j+wft?0B*Qt}*}EFsfADJ@+V(l*y#(s2&%YL{&pyj2PFq=;ik#iM zDf-qLkvQ+u#tNgqz zRQA2Z71?Tts{FoT#6|JIn++Omjmm-7=+YWxw4bIfZS=;JV*?jdvvp{n^bY}ypa%EB z8~T@8wTFp=n0)DJ^R8BDe|7eWOKsv@eDe3yp}y7{o)q-#Bb*9+F&J~ZvcRQ7D9n@8 zI3gVcM0SQj!W7w$PL(nJ&>eip$e|V7I*O0pjo~9BnA8D+qBvXOq!ekkS~jaOGAlg2 zi-Xe0mH?m6JoON2`(F+!qQQxAAD*X#*)7Se(kz5WLv#LRrA|-I=peF%!-${DebdoC z+yt~(<jyH;su(NYc6*Xv66o4=aBWVUAB1p2~Er;sWx;pp@Qv04md|)%|Tt79%Mq1Y0 z8r;Ksk-z_2IPlodF*xHgoP>#~kCuGG(^U)knq^#E3s#-tR*IN;_F>eP#gJQn77`ST5mV*xKI8{q#b~i&4hu(URgqN;t%Jib_#?diRXI_!k!Cw~uIz>rDR^g)DUC@12iRQ|a^c2a7-~>r4Ras;U!9u93 zpgGn(&al!=2n(T=McNN7{aX)_HWvbPb93Ee$oh&=TS0@@Hm3RMU-3xZz3C{hzRX4< zE6c8wbp;YmH2svuK1-T}8?7yCDkc_DrK*?vhttb!XS7;)i6@e>W|7QFs%PFZcbQ`hjL+_wX}()DgM=uQh&>m==m&RimYthm8te|k(2_Yh%nlcsO?HwSFk#k zAS{T3h=kS-#aLZrRc6XxqZ~nDwjnKY%$tyusmdZ3lI8D;k`-DK7Q{njLQ+aY1t?V= ze<^-fif{#ynU$~{q*4lhmxTVw#xyeD@cTSgSREb7R^K)73F*TPd5Zq@44jUED?S5f z-Fb|SZB+iV7h}&nhVlKctHmriBSBsW)A@)elGt?74y3kS2DhUZ_2Lxr|9lcd|9ArR zGB@;NfR$;J@O+4@6Hj2vP2Wa+#|;?qGKP$K#)oNC;61ksL%;k!Y7--ilADjyw_k$3 z8}Go#nyqHun-|%i)``@}n>c#k_b~b5-_19cgXI%u82ORg?!nMUuVd)9NEaNG{nllNnyxh+tMi7l73-@XARR){KgwvYR26kSoXU8@%KGb=oMx_tx8wGTVn z%NPA)Y7|-Y)^aJSmI|DnQMBu!K0Vg7FC#qfmL~P@M(x8@f94{dg$S}Nm#*==Tc6|y z+eAG-j_Uqbg3>}Ls3^YroH9X83$R`3m1oR*Q9+lE{)0*Zg}?o_y#cb#%o7j5E=)G` zFz<(L%Fpcbt=0zInF*Bl{1LV-2aWc#ESJ``OW~m(qx{-#7F4-7%6s=<`hg$9Q5@RP z$9(a|hcNl8+qoVsJykrnhSB@J1AF?Y|LKV0B$M%-%tnUgR#AN6am3E}5Muow2x=Q3 ztG%%Yqd&SCvEsO~#!`@#>+k#;B&W>$JozqMRoTs#A1%&5EWJ~ zn~uJ=hv1tvImuZUAbrU-h;R5Hd$dtK_y&rMgsLw;iG<}wx`)zMQ}14j4Cs=qJksLoshdtS$CfcL_2Ig% zf1G5W7W4YQHxAFC$9##G>*_dW!tW<6Yg>9h!|FcCENpe>PPz06jgxN<_vA9CT>3 Date: Tue, 23 Jun 2020 17:19:17 +0200 Subject: [PATCH 109/120] Add files via upload --- packages/nodes-base/nodes/SIGNL4/signl4.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/SIGNL4/signl4.node.ts b/packages/nodes-base/nodes/SIGNL4/signl4.node.ts index cc665eadd46..5b91df3814c 100644 --- a/packages/nodes-base/nodes/SIGNL4/signl4.node.ts +++ b/packages/nodes-base/nodes/SIGNL4/signl4.node.ts @@ -10,7 +10,7 @@ export class SIGNL4 implements INodeType { group: ['transform'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Send SIGNL4 alert.', + description: 'Send SIGNL4 alert. Find our more at https://www.signl4.com.', defaults: { name: 'SIGNL4', color: '#0000FF', From 474eac5005d36f973820cd89487c6a862feac03b Mon Sep 17 00:00:00 2001 From: Ron <43244104+rons4@users.noreply.github.com> Date: Tue, 23 Jun 2020 17:20:21 +0200 Subject: [PATCH 110/120] Initial version. --- .../credentials/SIGNL4Api.credentials.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 packages/nodes-base/credentials/SIGNL4Api.credentials.ts diff --git a/packages/nodes-base/credentials/SIGNL4Api.credentials.ts b/packages/nodes-base/credentials/SIGNL4Api.credentials.ts new file mode 100644 index 00000000000..4fa31c1422f --- /dev/null +++ b/packages/nodes-base/credentials/SIGNL4Api.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class SIGNL4Api implements ICredentialType { + name = 'SIGNL4Api'; + displayName = 'SIGNL4 Webhook'; + properties = [ + { + displayName: 'Team Secret', + name: 'teamSecret', + type: 'string' as NodePropertyTypes, + default: '', + description: 'The team secret is the last part of your SIGNL4 webhook URL.' + }, + ]; +} From d59658ade0531c20eb6a71eedb36fc7dbe217b41 Mon Sep 17 00:00:00 2001 From: Ron <43244104+rons4@users.noreply.github.com> Date: Tue, 23 Jun 2020 17:23:39 +0200 Subject: [PATCH 111/120] Updated for the SIGNL4 node. --- packages/nodes-base/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index a09d97d0329..1b4a18a5cc8 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.65.0", + "version": "0.66.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -119,6 +119,7 @@ "dist/credentials/StripeApi.credentials.js", "dist/credentials/SalesmateApi.credentials.js", "dist/credentials/SegmentApi.credentials.js", + "dist/credentials/SIGNL4Api.credentials.js", "dist/credentials/SurveyMonkeyApi.credentials.js", "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js", "dist/credentials/TelegramApi.credentials.js", @@ -260,6 +261,7 @@ "dist/nodes/Set.node.js", "dist/nodes/Shopify/Shopify.node.js", "dist/nodes/Shopify/ShopifyTrigger.node.js", + "dist/nodes/SIGNL4/SIGNL4.node.js", "dist/nodes/Slack/Slack.node.js", "dist/nodes/Sms77/Sms77.node.js", "dist/nodes/SplitInBatches.node.js", From d41b976b3bc99daff7168db113590d47c4247f46 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 23 Jun 2020 21:37:15 +0200 Subject: [PATCH 112/120] :zap: Remove unnecessary import --- packages/editor-ui/src/store.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts index b9762bb6161..80454fc9e43 100644 --- a/packages/editor-ui/src/store.ts +++ b/packages/editor-ui/src/store.ts @@ -29,8 +29,6 @@ import { XYPositon, } from './Interface'; -import { get } from 'lodash'; - Vue.use(Vuex); export const store = new Vuex.Store({ From 0bdb9cecac846db285b0df768f0a8e7e4d9d43c6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 23 Jun 2020 22:15:07 +0200 Subject: [PATCH 113/120] :zap: Make it possible that n8n is deployed under subfolder --- packages/editor-ui/vue.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor-ui/vue.config.js b/packages/editor-ui/vue.config.js index f70f41c5b23..cdcd8259f95 100644 --- a/packages/editor-ui/vue.config.js +++ b/packages/editor-ui/vue.config.js @@ -29,4 +29,5 @@ module.exports = { }, }, }, + publicPath: process.env.VUE_APP_PUBLIC_PATH ? process.env.VUE_APP_PUBLIC_PATH : '/', }; From 8f75087f8c454cb07f906feeabec47ae69ccb9c0 Mon Sep 17 00:00:00 2001 From: Ron <43244104+rons4@users.noreply.github.com> Date: Wed, 24 Jun 2020 08:45:39 +0200 Subject: [PATCH 114/120] Fixed some old debug output. --- packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts b/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts index 525a0b1ef9b..e04e3b8393c 100644 --- a/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts +++ b/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts @@ -36,8 +36,6 @@ export async function SIGNL4ApiRequest(this: IHookFunctions | IExecuteFunctions, } catch (error) { - throw new Error(`I am behind. Status: ${JSON.stringify(teamsecret)}`); - if (error && error.message) { throw new Error(`Error sending the SIGNL4 request. Error: ${JSON.stringify(error.message)}`); } From 5e81e02599601dd1c45220473d3bd88f918598d7 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 24 Jun 2020 14:16:48 -0400 Subject: [PATCH 115/120] :sparkles: Spotify Node Co-authored-by: Ricardo Espinoza --- .../SpotifyOAuth2Api.credentials.ts | 53 ++ .../nodes/Spotify/GenericFunctions.ts | 84 ++ .../nodes-base/nodes/Spotify/Spotify.node.ts | 816 ++++++++++++++++++ packages/nodes-base/nodes/Spotify/spotify.png | Bin 0 -> 6664 bytes packages/nodes-base/package.json | 2 + 5 files changed, 955 insertions(+) create mode 100644 packages/nodes-base/credentials/SpotifyOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Spotify/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Spotify/Spotify.node.ts create mode 100644 packages/nodes-base/nodes/Spotify/spotify.png diff --git a/packages/nodes-base/credentials/SpotifyOAuth2Api.credentials.ts b/packages/nodes-base/credentials/SpotifyOAuth2Api.credentials.ts new file mode 100644 index 00000000000..1dc9f1057a4 --- /dev/null +++ b/packages/nodes-base/credentials/SpotifyOAuth2Api.credentials.ts @@ -0,0 +1,53 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class SpotifyOAuth2Api implements ICredentialType { + name = 'spotifyOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Spotify OAuth2 API'; + properties = [ + { + displayName: 'Spotify Server', + name: 'server', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.spotify.com/', + }, + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://accounts.spotify.com/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://accounts.spotify.com/api/token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: 'user-read-playback-state playlist-read-collaborative user-modify-playback-state playlist-modify-public user-read-currently-playing playlist-read-private user-read-recently-played playlist-modify-private', + }, + { + 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/Spotify/GenericFunctions.ts b/packages/nodes-base/nodes/Spotify/GenericFunctions.ts new file mode 100644 index 00000000000..f4c86a9d2db --- /dev/null +++ b/packages/nodes-base/nodes/Spotify/GenericFunctions.ts @@ -0,0 +1,84 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +/** + * Make an API request to Spotify + * + * @param {IHookFunctions} this + * @param {string} method + * @param {string} url + * @param {object} body + * @returns {Promise} + */ +export async function spotifyApiRequest(this: IHookFunctions | IExecuteFunctions, + method: string, endpoint: string, body: object, query?: object, uri?: string): Promise { // tslint:disable-line:no-any + + const options: OptionsWithUri = { + method, + headers: { + 'User-Agent': 'n8n', + 'Content-Type': 'text/plain', + 'Accept': ' application/json', + }, + body, + qs: query, + uri: uri || `https://api.spotify.com/v1${endpoint}`, + json: true + }; + + try { + const credentials = this.getCredentials('spotifyOAuth2Api'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + if (Object.keys(body).length === 0) { + delete options.body; + } + + return await this.helpers.requestOAuth2.call(this, 'spotifyOAuth2Api', options); + } catch (error) { + if (error.statusCode === 401) { + // Return a clear error + throw new Error('The Spotify credentials are not valid!'); + } + + if (error.error && error.error.error && error.error.error.message) { + // Try to return the error prettier + throw new Error(`Spotify error response [${error.error.error.status}]: ${error.error.error.message}`); + } + + // If that data does not exist for some reason return the actual error + throw error; + } +} + +export async function spotifyApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, + propertyName: string, method: string, endpoint: string, body: object, query?: object): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + let uri: string | undefined; + + do { + responseData = await spotifyApiRequest.call(this, method, endpoint, body, query, uri); + returnData.push.apply(returnData, responseData[propertyName]); + uri = responseData.next; + + } while ( + responseData['next'] !== null + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Spotify/Spotify.node.ts b/packages/nodes-base/nodes/Spotify/Spotify.node.ts new file mode 100644 index 00000000000..b0c2df81190 --- /dev/null +++ b/packages/nodes-base/nodes/Spotify/Spotify.node.ts @@ -0,0 +1,816 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + spotifyApiRequest, + spotifyApiRequestAllItems, +} from './GenericFunctions'; + +export class Spotify implements INodeType { + description: INodeTypeDescription = { + displayName: 'Spotify', + name: 'spotify', + icon: 'file:spotify.png', + group: ['input'], + version: 1, + description: 'Access public song data via the Spotify API.', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + defaults: { + name: 'Spotify', + color: '#1DB954', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'spotifyOAuth2Api', + required: true, + }, + ], + properties: [ + // ---------------------------------------------------------- + // Resource to Operate on + // Player, Album, Artisits, Playlists, Tracks + // ---------------------------------------------------------- + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Album', + value: 'album', + }, + { + name: 'Artist', + value: 'artist', + }, + { + name: 'Player', + value: 'player', + }, + { + name: 'Playlist', + value: 'playlist', + }, + { + name: 'Track', + value: 'track', + }, + ], + default: 'player', + description: 'The resource to operate on.', + }, + // -------------------------------------------------------------------------------------------------------- + // Player Operations + // Pause, Play, Get Recently Played, Get Currently Playing, Next Song, Previous Song, Add to Queue + // -------------------------------------------------------------------------------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'player', + ], + }, + }, + options: [ + { + name: 'Add Song to Queue', + value: 'addSongToQueue', + description: 'Add a song to your queue.' + }, + { + name: 'Currently Playing', + value: 'currentlyPlaying', + description: 'Get your currently playing track.' + }, + { + name: 'Next Song', + value: 'nextSong', + description: 'Skip to your next track.' + }, + { + name: 'Pause', + value: 'pause', + description: 'Pause your music.', + }, + { + name: 'Previous Song', + value: 'previousSong', + description: 'Skip to your previous song.' + }, + { + name: 'Recently Played', + value: 'recentlyPlayed', + description: 'Get your recently played tracks.' + }, + { + name: 'Start Music', + value: 'startMusic', + description: 'Start playing a playlist, artist, or album.' + }, + ], + default: 'addSongToQueue', + description: 'The operation to perform.', + }, + { + displayName: 'Resource ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'player' + ], + operation: [ + 'startMusic', + ], + }, + }, + placeholder: 'spotify:album:1YZ3k65Mqw3G8FzYlW1mmp', + description: `Enter a playlist, artist, or album URI or ID.`, + }, + { + displayName: 'Track ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'player' + ], + operation: [ + 'addSongToQueue', + ], + }, + }, + placeholder: 'spotify:track:0xE4LEFzSNGsz1F6kvXsHU', + description: `Enter a track URI or ID.`, + }, + // ----------------------------------------------- + // Album Operations + // Get an Album, Get an Album's Tracks + // ----------------------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'album', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get an album by URI or ID.', + }, + { + name: `Get Tracks`, + value: 'getTracks', + description: `Get an album's tracks by URI or ID.`, + }, + ], + default: 'get', + description: 'The operation to perform.', + }, + { + displayName: 'Album ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'album', + ], + }, + }, + placeholder: 'spotify:album:1YZ3k65Mqw3G8FzYlW1mmp', + description: `The album's Spotify URI or ID.`, + }, + // ------------------------------------------------------------------------------------------------------------- + // Artist Operations + // Get an Artist, Get an Artist's Related Artists, Get an Artist's Top Tracks, Get an Artist's Albums + // ------------------------------------------------------------------------------------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'artist', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get an artist by URI or ID.', + }, + { + name: `Get Albums`, + value: 'getAlbums', + description: `Get an artist's albums by URI or ID.`, + }, + { + name: `Get Related Artists`, + value: 'getRelatedArtists', + description: `Get an artist's related artists by URI or ID.`, + }, + { + name: `Get Top Tracks`, + value: 'getTopTracks', + description: `Get an artist's top tracks by URI or ID.`, + }, + ], + default: 'get', + description: 'The operation to perform.', + }, + { + displayName: 'Artist ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'artist', + ], + }, + }, + placeholder: 'spotify:artist:4LLpKhyESsyAXpc4laK94U', + description: `The artist's Spotify URI or ID.`, + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: 'US', + required: true, + displayOptions: { + show: { + resource: [ + 'artist' + ], + operation: [ + 'getTopTracks', + ], + }, + }, + placeholder: 'US', + description: `Top tracks in which country? Enter the postal abbriviation.`, + }, + // ------------------------------------------------------------------------------------------------------------- + // Playlist Operations + // Get a Playlist, Get a Playlist's Tracks, Add/Remove a Song from a Playlist, Get a User's Playlists + // ------------------------------------------------------------------------------------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'playlist', + ], + }, + }, + options: [ + { + name: 'Add an Item', + value: 'add', + description: 'Add tracks from a playlist by track and playlist URI or ID.', + }, + { + name: 'Get', + value: 'get', + description: 'Get a playlist by URI or ID.', + }, + { + name: 'Get Tracks', + value: 'getTracks', + description: `Get a playlist's tracks by URI or ID.`, + }, + { + name: `Get the User's Playlists`, + value: 'getUserPlaylists', + description: `Get a user's playlists.`, + }, + { + name: 'Remove an Item', + value: 'delete', + description: 'Remove tracks from a playlist by track and playlist URI or ID.', + }, + ], + default: 'add', + description: 'The operation to perform.', + }, + { + displayName: 'Playlist ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'playlist' + ], + operation: [ + 'add', + 'delete', + 'get', + 'getTracks' + ], + }, + }, + placeholder: 'spotify:playlist:37i9dQZF1DWUhI3iC1khPH', + description: `The playlist's Spotify URI or its ID.`, + }, + { + displayName: 'Track ID', + name: 'trackID', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'playlist' + ], + operation: [ + 'add', + 'delete' + ], + }, + }, + placeholder: 'spotify:track:0xE4LEFzSNGsz1F6kvXsHU', + description: `The track's Spotify URI or its ID. The track to add/delete from the playlist.`, + }, + // ----------------------------------------------------- + // Track Operations + // Get a Track, Get a Track's Audio Features + // ----------------------------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'track', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get a track by its URI or ID.', + }, + { + name: 'Get Audio Features', + value: 'getAudioFeatures', + description: 'Get audio features for a track by URI or ID.', + }, + ], + default: 'track', + description: 'The operation to perform.', + }, + { + displayName: 'Track ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'track', + ], + }, + }, + placeholder: 'spotify:track:0xE4LEFzSNGsz1F6kvXsHU', + description: `The track's Spotify URI or ID.`, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + required: true, + displayOptions: { + show: { + resource: [ + 'album', + 'artist', + 'playlist' + ], + operation: [ + 'getTracks', + 'getAlbums', + 'getUserPlaylists' + ] + }, + }, + description: `The number of items to return.`, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + required: true, + displayOptions: { + show: { + resource: [ + 'album', + 'artist', + 'playlist' + ], + operation: [ + 'getTracks', + 'getAlbums', + 'getUserPlaylists' + ], + returnAll: [ + false + ] + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + description: `The number of items to return.`, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + required: true, + displayOptions: { + show: { + resource: [ + 'player', + ], + operation: [ + 'recentlyPlayed', + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 50, + }, + description: `The number of items to return.`, + }, + ] + }; + + + async execute(this: IExecuteFunctions): Promise { + // Get all of the incoming input data to loop through + const items = this.getInputData(); + const returnData: IDataObject[] = []; + + // For Post + let body: IDataObject; + // For Query string + let qs: IDataObject; + + let requestMethod: string; + let endpoint: string; + let returnAll: boolean; + let propertyName = ''; + let responseData; + + const operation = this.getNodeParameter('operation', 0) as string; + const resource = this.getNodeParameter('resource', 0) as string; + + // Set initial values + requestMethod = 'GET'; + endpoint = ''; + body = {}; + qs = {}; + returnAll = false; + + for(let i = 0; i < items.length; i++) { + // ----------------------------- + // Player Operations + // ----------------------------- + if( resource === 'player' ) { + if(operation === 'pause') { + requestMethod = 'PUT'; + + endpoint = `/me/player/pause`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = { success: true }; + + } else if(operation === 'recentlyPlayed') { + requestMethod = 'GET'; + + endpoint = `/me/player/recently-played`; + + const limit = this.getNodeParameter('limit', i) as number; + + qs = { + 'limit': limit + }; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = responseData.items; + + } else if(operation === 'currentlyPlaying') { + requestMethod = 'GET'; + + endpoint = `/me/player/currently-playing`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + } else if(operation === 'nextSong') { + requestMethod = 'POST'; + + endpoint = `/me/player/next`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = { success: true }; + + } else if(operation === 'previousSong') { + requestMethod = 'POST'; + + endpoint = `/me/player/previous`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = { success: true }; + + } else if(operation === 'startMusic') { + requestMethod = 'PUT'; + + endpoint = `/me/player/play`; + + const id = this.getNodeParameter('id', i) as string; + + body.context_uri = id; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = { success: true }; + + } else if(operation === 'addSongToQueue') { + requestMethod = 'POST'; + + endpoint = `/me/player/queue`; + + const id = this.getNodeParameter('id', i) as string; + + qs = { + 'uri': id + }; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = { success: true }; + } + // ----------------------------- + // Album Operations + // ----------------------------- + } else if( resource === 'album') { + const uri = this.getNodeParameter('id', i) as string; + + const id = uri.replace('spotify:album:', ''); + + requestMethod = 'GET'; + + if(operation === 'get') { + endpoint = `/albums/${id}`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + } else if(operation === 'getTracks') { + endpoint = `/albums/${id}/tracks`; + + propertyName = 'tracks'; + + returnAll = this.getNodeParameter('returnAll', i) as boolean; + + propertyName = 'items'; + + if(!returnAll) { + const limit = this.getNodeParameter('limit', i) as number; + + qs = { + 'limit': limit + }; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = responseData.items; + } + } + // ----------------------------- + // Artist Operations + // ----------------------------- + } else if( resource === 'artist') { + const uri = this.getNodeParameter('id', i) as string; + + const id = uri.replace('spotify:artist:', ''); + + if(operation === 'getAlbums') { + + endpoint = `/artists/${id}/albums`; + + returnAll = this.getNodeParameter('returnAll', i) as boolean; + + propertyName = 'items'; + + if(!returnAll) { + const limit = this.getNodeParameter('limit', i) as number; + + qs = { + 'limit': limit + }; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = responseData.items; + } + } else if(operation === 'getRelatedArtists') { + + endpoint = `/artists/${id}/related-artists`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = responseData.artists; + + } else if(operation === 'getTopTracks'){ + const country = this.getNodeParameter('country', i) as string; + + qs = { + 'country': country + }; + + endpoint = `/artists/${id}/top-tracks`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = responseData.tracks; + + } else if (operation === 'get') { + + requestMethod = 'GET'; + + endpoint = `/artists/${id}`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + } + // ----------------------------- + // Playlist Operations + // ----------------------------- + } else if( resource === 'playlist') { + if(['delete', 'get', 'getTracks', 'add'].includes(operation)) { + const uri = this.getNodeParameter('id', i) as string; + + const id = uri.replace('spotify:playlist:', ''); + + if(operation === 'delete') { + requestMethod = 'DELETE'; + const trackId = this.getNodeParameter('trackID', i) as string; + + body.tracks = [ + { + "uri": `${trackId}`, + "positions": [ 0 ] + } + ]; + + endpoint = `/playlists/${id}/tracks`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = { success: true }; + + } else if(operation === 'get') { + requestMethod = 'GET'; + + endpoint = `/playlists/${id}`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + } else if(operation === 'getTracks') { + requestMethod = 'GET'; + + endpoint = `/playlists/${id}/tracks`; + + returnAll = this.getNodeParameter('returnAll', i) as boolean; + + propertyName = 'items'; + + if(!returnAll) { + const limit = this.getNodeParameter('limit', i) as number; + + qs = { + 'limit': limit + }; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = responseData.items; + } + } else if(operation === 'add') { + requestMethod = 'POST'; + + const trackId = this.getNodeParameter('trackID', i) as string; + + qs = { + 'uris': trackId + }; + + endpoint = `/playlists/${id}/tracks`; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + } + } else if(operation === 'getUserPlaylists') { + requestMethod = 'GET'; + + endpoint = `/me/playlists`; + + returnAll = this.getNodeParameter('returnAll', i) as boolean; + + propertyName = 'items'; + + if(!returnAll) { + const limit = this.getNodeParameter('limit', i) as number; + + qs = { + 'limit': limit + }; + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = responseData.items; + } + } + // ----------------------------- + // Track Operations + // ----------------------------- + } else if( resource === 'track') { + const uri = this.getNodeParameter('id', i) as string; + + const id = uri.replace('spotify:track:', ''); + + requestMethod = 'GET'; + + if(operation === 'getAudioFeatures') { + endpoint = `/audio-features/${id}`; + } else if(operation === 'get') { + endpoint = `/tracks/${id}`; + } + + responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs); + } + + if(returnAll) { + responseData = await spotifyApiRequestAllItems.call(this, propertyName, requestMethod, endpoint, body, qs); + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Spotify/spotify.png b/packages/nodes-base/nodes/Spotify/spotify.png new file mode 100644 index 0000000000000000000000000000000000000000..2feeb78bbf25c43b2e1ceab5ee5f382eb790b207 GIT binary patch literal 6664 zcmZu$WmptI*CiyRL^`EQVp+PoySsa-1$OC%1*994E|HM#5D;mkTe`bbsSn@ydY)(M z&bjB_bLZboq?(E>CK?GE92^{`yquKA%PjP_qaweI0uKvrFB3dOLskN=a+GZUW%Jra zP9FjXhmQNV!^34{5xoG0L0WoHJ!K^UORy80xfR&Ln$63}aPByTcEda>R&ktbd0B~@yz93j3-p){SFIHy=%|A*0%_C(E zv2+8uKtW(<%D=qk7GQU%Fg5kxK>ykPm8X--e+F`f{7cn~K7g0G3joN*4*36^pdg$7 zO7DMo{XO}o+dowOGnmjzX#^zQtj(cdH!U#OQAGT2?I`7)%x$eTtj(>wg#iB>@n4w= z{iPI8bpu(yX#J~51SkafKem72g#dr${x|vmtnELnFU1u>dx`Y#ixNR&VjB^LgCm@k zmlD^qLmDxB{ZVTsRRmnB>#KW>#bsEdAbr9e!^mj5hB0*X(czp;Dj45vlFq^fMYr?QT}eS{?isA9gzel-;K=F;!xkydKkh zPg`t7cXMtN_AGf88$U19%ax{EgrlR4r9{NDRV}6qa<$AHkmc6ZZYnKSDkYhu)W^#G zn$(V`wMMA@qFlGFBUyWUC%;Un^B@>5zrFYbx#IOuOtNY@vvXJatdFjoMBBEz;B6we z%OC$ytu!D}qE7X#&vW8xfCqTzOqn63C(A>SyK>6bL4T$=1*A61KIni`N-6ohfJcW1 zKT=7bT3LI5X1HjN=l7%s8|}S^jMb{)4`Dih5dWVJcVWKD9Hj0;alUNc^!rWaVAF3o> zvSaT>2z-V_N0IVdMMKlC;S|pv*Z9L#$(6$wOApto-$|CVS2k>O*W|QAwB$=pJIU_i zkpv+qC!m`z%$?8eFojsBzdB6hHy3cY+G(Lj-uq&_oHldF$hxX#pgEGabRCMJ5)Mpej&&8WP#x6<0H>eKRddXDj(Bv z_8PhO^=08Hm<(eAk@^_tzApLM_||^0mAlQ)SW<$C8HIFaRFK|J>HWP5$^}1Xp8Zq3 zKQzR)=c}kzzejGYT?+uaBcAH0nxxLk6uhd|X{?Cx`pXen(VtSPv~#N+W_ii6JG;Hv zrTjmD)8~Cg9fwBH$!S$K{W?*%Jcj3T5^`iz8xSnO+>G&%q;M+YwUE9jL-WU!Q8?j$ku?dGv4XmDE{F$#csxo zp~{|*?0oADr*FrjCCcp4RJV0JY`E!GxySc$7D7FOV4$0sqHi>r_S0P+S9zA%pC;4HhnOKy{KN}Sc!Si0$V=)fz*yc3 za(|qyQDnnKB>(g3HG;~|xKg0w>C^E*EugBtNywLL#r%#I|DG&D>4_9BF<{^;IgGrz zO-u_%RsSjAL*0i;9R<6OIG=Go2ce~6OK(#m(PGkgHE6P$e`a$2jJhae&y54^I?D5` z&^aq6XZHpNO=u)26v1Jp`blU`CK&2qpRPgB44!qn784{h4APnI`n4N6y~2<5)w+BV zpUaoy(3BdsGjAUEBHFfJr4E<%7}-eaBmGzlK*XkP%f@qmwY>ceMt@&cGHEFOqk$vJ zs~ZnKQG~cQ-70_RvC!>+sg{mITyAnmEj_Uat1uaBS7ki^2>2_*serrX@hUL@+rg$Q z2%#($oe{Odth)`igF2gq@X<5uTfG$}g`kc53VLhKrr~!_#)P3v&lqlkcitC`p&dPU zF5Mg~RvJny<;jwcDE>2|VJD2OY4#5^CDWRysUjm3Tdezg$OJ#?{C?|WdtW>1-j$D6DHbk2oFNt+3ccOsy zvAD)ktuncSK8$oaa;eN)R|}?C_FPhz zjN3p&E67L+9irJHIDuhWMhSi%XVB)I@od1vL*f@x^zdL&tj(?<5lwPT@ol(Cs38{L zkqYmG1Nq@zPS1O}Z>fFEl9V8MKGiskWo0Szc-C$D`*5Q>iR}&KyZ8EXZk{*egz>Oi zt?6L$gzR*6_c3>?^&oTFyOmT4Q)+z@ewW-&KVa3_Zg3iA2tx6Rx~>)C91lIe=R%93 zncu_dp>+N37uaO)?-L56e^u7&YIttslkddsU$rS6bEZa4W`}mOtXvNpX>q0|2AxEi z#=~O*%REsyHN?`2aQ^uBXF~O%9QXsl&ai~pu7F=LeB{FoldfbD zYc2*c9sJtoB0@Gy#8HBp`K)E{M9aKdHd(E`=hnesI%J`4#<<6YyK zWzkD$jaht^`I+e~Ur0v%5jZIb>kz#1Xr*^!T82Phe@k*em=IS8wX>nB`^mGu<>=4m z94H=rzzXvB+1<+}5Af+P@1uHVTtsa7P-DTmpu13_OiPKe$OLFOg&NjG%jaz-2rZKqCg8$zkfy5{_F_sYic zG6-6OWbX4qy)||mnrQXHIvnare1ts{?DKwQ)tga8Vhqm;yUEj#SN`DWpBZ)ZyD;aM zew$JlgjTg$4xDr(w0w*5O^%1y*i+q;67mffXK^*zsphKn^StD{rLcq)2wT`lsu(F( zKK>rUh51r^-TABhh+{#_I(=jrfTc`R%J{eMW8;K{ocjgiL~_du?dQLaC~(*Zn__Ue zNZ99D+DLujr!IyRc#<ug7E$C3CQZ2quc+^5Qbf*P;kK>^JVIS(0%f4AZW>;8|Wv=hq70d_6}{s7%%ncGR;y}MK8pzF=R)Pyv`9Jw2csGG#F##Y-U1lmC<5X zy@Wtb3lp7Gmr8y;_+AWW7ENM4l!3oj%FUHyPDOcvre1hT>eT9XoWx&Li6jP=(l1`l zC6^O9CSkuRjM zdo$P%Nm}O?K^XT!i+s!TZL6}2`xQ@$S`zoUq?q&JGzNo7k95w)%&8-Yc-{~n^y_Ds z&X!2Usx^T5Mkr@_3izgLKWPqcnUtwI=YQT;rW6TIX&~xB$)}@7t#vU*4gYmzH%B6e zoe_S2?)LT%AMG_r37dv`aC@n+z-`ld%G%(bCT${C%wjRd@@lwy5hTuUYp-JIyW=$| znseC9N*{Hvnw>o{YRW5V+5G!*9w3#+|F&ly-DSzZ2j5@R|KqH?^JfN}a*)#vO%O{# zv>98r#DZ-0Yf0hl69@Rr(e0Z4kWojWALjZ$wUq;Mh3tY zEXCqe2ngI{Z{ux$5G=+nhb+-@+Y&Qvk?Q@-7Yxkv&Bj%slOoj=1bIL+sx_Mc=EWt_ zvfaU3n;%v}>~Ovil$dAE&^e~g-~sxjZ17>PQ>Yfq40kaHuxZKY3{s-Zo{!x5-P&Tm z0W5ozECx-D`9j&^m5Qr+Mf2_r)28Sjv8iPfU5Xw-#pn@ZUt7&eDAk-A+EfV2=bMW2 z_a1+4$&y7MVuMd(J7ZV|6gko9rzVWhos>?Bu3fmQ_D8CddBm)ndN8RZVU#%|QfCmd zf>mXn@dz(|1J*+QoJ6h>Q(0W8S|Hj}KGCmDJFLyYeI`kZTvz3FYp!o*)Q|V;U-s84 zeM;O@lGJvCNyav35+6Pgx{fQE3Kf(TDK1Qfy1c_1O|dUx_b)%zr$y>SxoAg4P5$8( zSFc_P$3T8DDV;k9!3)qH4T z@xQr?>ohA}oL33oKe;g6U!N9$4{caeA{l~F@rOvc$s=qSIn8!3SP{ zAD!F(-a`Qj=Se$>`kCJ{LCnaNf)<_;GVuzT=0Bsnh83}3qgsZ*LkkwWCK9-;#e053?3`aahr?<*++O+8GBjq2S?JL7?7 zzQtde{BoqN9^;UKY~Dj(s^8cO{9$VP(+=1LY+B5qZviPQ&c^ihA<1u4+>Mj>!Jv~( zRcr+Q76*y%1KC0{F@bC2r;SThox-I&yWHwjS}@F;U~CxLiPVa(rL$uOjZ+t`4^h&i!z-vgp{*qRyVk=*MJORAFQ6 zwTuh1HX#nHrM^dK0k8^m^F{8XSoE0<`F>7v0=GHM?+Jo!q`UQ2{h*|(NvhfJv&RG< zI3<|mSP&a#%ny&uG)ffQlrZ1lsh}iXh~Yj&5jI8?fC1<1h>q{ir9diII2ria3g}6^ z;}?xhYdt1?0#sW=RQADRW>w1$_HPKj0rZ$_)FoI)pG}u=7dyIXcAS*CjsFbK+Rh2j zwDgvxN^qo0-1DgX5)_w0I=U=fU(FYIW=wP;8|fxUeCFAXo1l7pMIKZLg2nQh>889@ ziP~g~#?i*`+In50HftfP9d>89S|lRZs)iZI5!I*Z({7y3a8 zL#TlWGJn4lL#0!f9@NK-wajgYlS3`=*`Z1Rx2pz1?A;Bx-TmaJ(#>`Vz?3(U0yQ#C z_B8v}kL=dUm0m7Nw(*BvHdf7eFfM(ZpNvoYdhxfV?U$gp+A=ps?eMSODG>Y52&y<< z*}@pqKZA*E@-mIFMzdfp6Hjr7(dvEtl#L1Fq*qz_>zFXS^O8ii-=A3BJ6i;m`)K#( zow;qQbSx(wE!|umHgm_0Ol}n1I*lWV`VT&>62Y$&xN-;AtvnAN6;BZ9Qog}Q1p?a> zv2Be6Mb!u1&SjCglthNS?KO+(#s|wKJR*tc6f#T&<}+wkFU(r}QFo#DuW?C}6KbY_ zDQN=yDPU9)t&{7Xeh!!X4AdJ?#urmT3G3c>s{cw+& zZcZIP>Upuoy1nK1ZpjB5pvu0~eLy>ZW3+Auk{-n6QQE9+wi>-!!02^gl99T( zyRC9F-1|}exs4tLgDfN2Cf~!omLn02=~A>`@X4eu(tRi*17Fbz1~k8B$bOnn_0{oV zW@cUpNJGzW*62g@xf|d-T`sK3FiF;m{c07d%l7b)?D!MT`K{L#25ko+R<%LO2fD-+ ztRi`#HyO4^x5NidIx~&CWdthl-`Cy*BGQoDgxRrJbFG&+v$Py!dQS3b@ow0QY4F|o zZN$Fb2sO=jd8Zxi(!fYMAM`@n8?HkWD4 z0|qQigHo)A0!B$dVJ_H(|8$XCVPk7C{LM{M+vr~d`Ltv0ND zMGND7p9r-f+O(fRrw{y6r~<`c|?? zovSNbkel?0S=Z#w!M3b3Qzm3PHlMhE;m!IcnPdAs_z~x)B%zXV9-xNTyFF<-q2^ez z{zs0sC_v9Nb#HI7e*hjIy!T`iA`|66BRFN)UFK0IV*i}|1DpY=2y{!nZ?SqV5>Q2< z)x&00o=JE7wRJ8(8Ne459UU*56WDR}Sbku5GvA;wEoM;Q)_5(iKFT3YV13k}d2v^O zb3E&KoqJmGFp83B;lTknYb!d00Q^rgd!()y6aY5?fZ9x09gt?IQUPCQ~ zNG#rYBi3Um!d|7w0H>|#S*1<)>~UJq*8me@6=?i=88sOOIV@IH!DZu zM0S=Z^H+q1s{xJ`OD==CtvA?TB)MyIY1(4+_H4LA^#i+rF+`}_9Bm8cyG2;JUzN@Y zCVG8(iw_IamwhF#IKh`&e(lJw(&m76A>LU1`!7$p!3U@yn%j=cvB)>4i$y#$9ZAA4 zC)Esv0SCOiO1MZyHShC4*FPuiOV{46yse&!iIOxD9ZuG3M2obV+K42|T7_Knx~={e zc{};1>0^O1KO=O^Wy9aZjmCEFN>xYObwnZZA2-C^cGlB19OgFJX0okrg9~H%@$NVV zW|SA+Q}s%*VuWlIu=M+!zkzM^6LG3Nj8H0f4Rg>%^75JI%Fqalm&yJ9nP{*7*xWTh zx61tOSQ?vw>&QrJzIQ=5m+BEi8&hZpsKX=HhiQQZ_5%xZ*iSVpEBLG>5uo7;gj-${7^}Y;i7QprJkQAcgKPq0L&{$gOsEb^rtDK z&fO0oIW__>(659{IQ(Gi#{T&%{Jf(z-Ue2tCbRDhOtM6`ypS-k%PIfUO8I1O>p@BO yuDUBI#tL~Id;c)#1LY@6KIaUBA^!o!Zi|xu literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index a09d97d0329..7782a493b12 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -119,6 +119,7 @@ "dist/credentials/StripeApi.credentials.js", "dist/credentials/SalesmateApi.credentials.js", "dist/credentials/SegmentApi.credentials.js", + "dist/credentials/SpotifyOAuth2Api.credentials.js", "dist/credentials/SurveyMonkeyApi.credentials.js", "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js", "dist/credentials/TelegramApi.credentials.js", @@ -263,6 +264,7 @@ "dist/nodes/Slack/Slack.node.js", "dist/nodes/Sms77/Sms77.node.js", "dist/nodes/SplitInBatches.node.js", + "dist/nodes/Spotify/Spotify.node.js", "dist/nodes/SpreadsheetFile.node.js", "dist/nodes/SseTrigger.node.js", "dist/nodes/Start.node.js", From 5b269b45f1d247e4bbf8dfe453da5258d98dd717 Mon Sep 17 00:00:00 2001 From: ricardo Date: Thu, 25 Jun 2020 00:06:48 -0400 Subject: [PATCH 116/120] :sparkles: SIGNL4-Node --- .../credentials/SIGNL4Api.credentials.ts | 4 +- .../nodes/SIGNL4/GenericFunctions.ts | 72 ++-- .../nodes-base/nodes/SIGNL4/signl4.node.ts | 368 +++++++++++------- packages/nodes-base/package.json | 6 +- 4 files changed, 266 insertions(+), 184 deletions(-) diff --git a/packages/nodes-base/credentials/SIGNL4Api.credentials.ts b/packages/nodes-base/credentials/SIGNL4Api.credentials.ts index 4fa31c1422f..842136de021 100644 --- a/packages/nodes-base/credentials/SIGNL4Api.credentials.ts +++ b/packages/nodes-base/credentials/SIGNL4Api.credentials.ts @@ -3,8 +3,8 @@ import { NodePropertyTypes, } from 'n8n-workflow'; -export class SIGNL4Api implements ICredentialType { - name = 'SIGNL4Api'; +export class Signl4Api implements ICredentialType { + name = 'signl4Api'; displayName = 'SIGNL4 Webhook'; properties = [ { diff --git a/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts b/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts index e04e3b8393c..5281ae8c25e 100644 --- a/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts +++ b/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts @@ -1,62 +1,52 @@ import { IExecuteFunctions, - IHookFunctions, } from 'n8n-core'; import { - ICredentialDataDecryptedObject, IDataObject, } from 'n8n-workflow'; +import { + OptionsWithUri, + } from 'request'; + /** - * Make an API request to MSG91 + * Make an API request to SIGNL4 * * @param {IHookFunctions | IExecuteFunctions} this * @param {object} message * @returns {Promise} */ -export async function SIGNL4ApiRequest(this: IHookFunctions | IExecuteFunctions, message: IDataObject): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('SIGNL4Api'); - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - - const teamsecret = credentials!.teamSecret as string; - var response = null; - try { - response = await this.helpers.request({ - headers: {'Content-Type': 'application/json'}, - method: 'POST', - body: message, - qs: {}, - uri: `https://connect.signl4.com/webhook/${teamsecret}`, - json: true - }); +export async function SIGNL4ApiRequest(this: IExecuteFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + + let options: OptionsWithUri = { + headers: { + 'Accept': '*/*', + }, + method, + body, + qs: query, + uri: uri || ``, + json: true, + }; + + if (!Object.keys(body).length) { + delete options.body; } - catch (error) { - - if (error && error.message) { - throw new Error(`Error sending the SIGNL4 request. Error: ${JSON.stringify(error.message)}`); + if (!Object.keys(query).length) { + delete options.qs; + } + options = Object.assign({}, options, option); + + try { + return await this.helpers.request!(options); + } catch (error) { + + if (error.response && error.response.body && error.response.body.details) { + throw new Error(`SIGNL4 error response [${error.statusCode}]: ${error.response.body.details}`); } - - throw new Error('Error sending the SIGNL4 request.'); throw error; } - - return response; -} - - -function setPayload(credentials: ICredentialDataDecryptedObject, o?: IDataObject) { - if (!o) { - o = {}; - } - - o.p = credentials!.teamSecret as string; - o.json = 1; - o.sendwith = 'n8n'; - - return o; } diff --git a/packages/nodes-base/nodes/SIGNL4/signl4.node.ts b/packages/nodes-base/nodes/SIGNL4/signl4.node.ts index 5b91df3814c..5a2ec290e38 100644 --- a/packages/nodes-base/nodes/SIGNL4/signl4.node.ts +++ b/packages/nodes-base/nodes/SIGNL4/signl4.node.ts @@ -1,27 +1,40 @@ -import {IExecuteFunctions,} from 'n8n-core'; -import {IDataObject, INodeExecutionData, INodeType, INodeTypeDescription,} from 'n8n-workflow'; -import {SIGNL4ApiRequest} from './GenericFunctions'; +import { + IExecuteFunctions, + BINARY_ENCODING, +} from 'n8n-core'; -export class SIGNL4 implements INodeType { +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, + IBinaryKeyData, +} from 'n8n-workflow'; + +import { + SIGNL4ApiRequest, +} from './GenericFunctions'; + +export class Signl4 implements INodeType { description: INodeTypeDescription = { displayName: 'SIGNL4', - name: 'SIGNL4', + name: 'signl4', icon: 'file:signl4.png', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Send SIGNL4 alert. Find our more at https://www.signl4.com.', + description: 'Consume SIGNL4 API.', defaults: { name: 'SIGNL4', - color: '#0000FF', + color: '#53afe8', }, inputs: ['main'], outputs: ['main'], credentials: [ { - name: 'SIGNL4Api', + name: 'signl4Api', required: true, - } + }, ], properties: [ { @@ -50,39 +63,22 @@ export class SIGNL4 implements INodeType { }, options: [ { - name: 'Send Alert', + name: 'Send', value: 'send', - description: 'Send SIGNL4 alert.', + description: 'Send an alert.', }, ], default: 'send', description: 'The operation to perform.', }, { - displayName: 'Subject', - name: 'subject', + displayName: 'Message', + name: 'message', type: 'string', - default: 'SIGNL4 Alert', - placeholder: 'SIGNL4 Alert', - required: true, - displayOptions: { - show: { - operation: [ - 'send', - ], - resource: [ - 'alert', - ], - }, + typeOptions: { + alwaysOpenEditWindow: true, }, - description: 'The subject of the SIGNL4 alert.', - }, - { - displayName: 'Body', - name: 'body', - type: 'string', default: '', - placeholder: 'Alert description.', required: false, displayOptions: { show: { @@ -97,11 +93,10 @@ export class SIGNL4 implements INodeType { description: 'A more detailed description for the alert.', }, { - displayName: 'S4-Service', - name: 'xS4Service', - type: 'string', - default: '', - required: false, + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', displayOptions: { show: { operation: [ @@ -112,121 +107,218 @@ export class SIGNL4 implements INodeType { ], }, }, - description: 'Assigns the alert to the service/system category with the specified name.', - }, - { - displayName: 'S4-Location', - name: 'xS4Location', - type: 'string', - default: '', - required: false, - displayOptions: { - show: { - operation: [ - 'send', - ], - resource: [ - 'alert', - ], - }, - }, - description: 'Transmit location information (\'latitude, longitude\') with your event and display a map in the mobile app.', - }, - { - displayName: 'S4-AlertingScenario', - name: 'xS4AlertingScenario', - type: 'options', + default: {}, options: [ { - name: 'single_ack', - value: 'single_ack', + displayName: 'Alerting Scenario', + name: 'alertingScenario', + type: 'options', + options: [ + { + name: 'Single ACK', + value: 'single_ack', + description: 'In case only one person needs to confirm this Signl.' + }, + { + name: 'Multi ACK', + value: 'multi_ack', + description: 'in case this alert must be confirmed by the number of people who are on duty at the time this Singl is raised', + }, + ], + default: 'single_ack', + required: false, }, { - name: 'multi_ack', - value: 'multi_ack', + displayName: 'Attachments', + name: 'attachmentsUi', + placeholder: 'Add Attachments', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + options: [ + { + name: 'attachmentsBinary', + displayName: 'Attachments Binary', + values: [ + { + displayName: 'Property Name', + name: 'property', + type: 'string', + placeholder: 'data', + default: '', + description: 'Name of the binary properties which contain data which should be added as attachment', + }, + ], + }, + ], + default: {}, + }, + { + displayName: 'External ID', + name: 'externalId', + type: 'string', + default: '', + description: `If the event originates from a record in a 3rd party system, use this parameter to pass
+ the unique ID of that record. That ID will be communicated in outbound webhook notifications from SIGNL4,
+ which is great for correlation/synchronization of that record with the alert.`, + }, + { + displayName: 'Filtering', + name: 'filtering', + type: 'boolean', + default: 'false', + description: `Specify a boolean value of true or false to apply event filtering for this event, or not.
+ If set to true, the event will only trigger a notification to the team, if it contains at least one keyword
+ from one of your services and system categories (i.e. it is whitelisted)`, + }, + { + displayName: 'Location', + name: 'locationFieldsUi', + type: 'fixedCollection', + placeholder: 'Add Location', + default: {}, + description: 'Transmit location information (\'latitude, longitude\') with your event and display a map in the mobile app.', + options: [ + { + name: 'locationFieldsValues', + displayName: 'Location', + values: [ + { + displayName: 'Latitude', + name: 'latitude', + type: 'string', + required: true, + description: 'The location latitude.', + default: '', + }, + { + displayName: 'Longitude', + name: 'longitude', + type: 'string', + required: true, + description: 'The location longitude.', + default: '', + }, + ], + } + ], + }, + { + displayName: 'Service', + name: 'service', + type: 'string', + default: '', + description: 'Assigns the alert to the service/system category with the specified name.', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', }, ], - default: 'single_ack', - required: false, - displayOptions: { - show: { - operation: [ - 'send', - ], - resource: [ - 'alert', - ], - }, - }, - description: 'Pass \'single_ack\' if only one person needs to confirm this Signl. Pass \'multi_ack\' in case this alert must be confirmed by the number of people who are on duty at the time this Singl is raised.', }, - { - displayName: 'S4-ExternalID', - name: 'xS4ExternalId', - type: 'string', - default: '', - required: false, - displayOptions: { - show: { - operation: [ - 'send', - ], - resource: [ - 'alert', - ], - }, - }, - description: 'If the event originates from a record in a 3rd party system, use this parameter to pass the unique ID of that record. That ID will be communicated in outbound webhook notifications from SIGNL4, which is great for correlation/synchronization of that record with the alert.', - }, - { - displayName: 'S4-Filtering', - name: 'xS4Filtering', - type: 'boolean', - default: 'false', - required: false, - displayOptions: { - show: { - operation: [ - 'send', - ], - resource: [ - 'alert', - ], - }, - }, - description: 'Specify a boolean value of true or false to apply event filtering for this event, or not. If set to true, the event will only trigger a notification to the team, if it contains at least one keyword from one of your services and system categories (i.e. it is whitelisted)', - } ], }; async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); const returnData: IDataObject[] = []; + const length = (items.length as unknown) as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'alert') { + //https://connect.signl4.com/webhook/docs/index.html + if (operation === 'send') { + const message = this.getNodeParameter('message', i) as string; + const additionalFields = this.getNodeParameter('additionalFields',i) as IDataObject; - for (let i = 0; i < this.getInputData().length; i++) { - const resource = this.getNodeParameter('resource', i); - if ('alert' !== resource) { - throw new Error(`The resource "${resource}" is not known!`); + const data: IDataObject = { + message, + }; + + if (additionalFields.alertingScenario) { + data['X-S4-AlertingScenario'] = additionalFields.alertingScenario as string; + } + if (additionalFields.externalId) { + data['X-S4-ExternalID'] = additionalFields.externalId as string; + } + if (additionalFields.filtering) { + data['X-S4-Filtering'] = (additionalFields.filtering as boolean).toString(); + } + if (additionalFields.locationFieldsUi) { + const locationUi = (additionalFields.locationFieldsUi as IDataObject).locationFieldsValues as IDataObject; + if (locationUi) { + data['X-S4-Location'] = `${locationUi.latitude},${locationUi.longitude}`; + } + } + if (additionalFields.service) { + data['X-S4-Service'] = additionalFields.service as string; + } + if (additionalFields.title) { + data['title'] = additionalFields.title as string; + } + + const attachments = additionalFields.attachmentsUi as IDataObject; + + if (attachments) { + if (attachments.attachmentsBinary && items[i].binary) { + + const propertyName = (attachments.attachmentsBinary as IDataObject).property as string + + const binaryProperty = (items[i].binary as IBinaryKeyData)[propertyName]; + + if (binaryProperty) { + + const supportedFileExtension = ['png', 'jpg', 'txt']; + + if (!supportedFileExtension.includes(binaryProperty.fileExtension as string)) { + + throw new Error(`Invalid extension, just ${supportedFileExtension.join(',')} are supported}`); + } + + data['file'] = { + value: Buffer.from(binaryProperty.data, BINARY_ENCODING), + options: { + filename: binaryProperty.fileName, + contentType: binaryProperty.mimeType, + }, + } + + } else { + throw new Error(`Binary property ${propertyName} does not exist on input`); + } + } + } + + const credentials = this.getCredentials('signl4Api'); + + const endpoint = `https://connect.signl4.com/webhook/${credentials?.teamSecret}`; + + responseData = await SIGNL4ApiRequest.call( + this, + 'POST', + '', + {}, + {}, + endpoint, + { + formData: { + ...data, + }, + }, + ); + } } - - const operation = this.getNodeParameter('operation', i); - if ('send' !== operation) { - throw new Error(`The operation "${operation}" is not known!`); - } - - // Assemble JSON data - var message = { - Subject: this.getNodeParameter('subject', i), - Body: this.getNodeParameter('body', i) - } as IDataObject; - message['X-S4-Service'] = this.getNodeParameter('xS4Service', i); - message['X-S4-Location'] = this.getNodeParameter('xS4Location', i); - message['X-S4-AlertingScenario'] = this.getNodeParameter('xS4AlertingScenario', i); - message['X-S4-ExternalID'] = this.getNodeParameter('xS4ExternalId', i); - message['X-S4-Filtering'] = this.getNodeParameter('xS4Filtering', i); - - const responseData = await SIGNL4ApiRequest.call(this, message); - - returnData.push(responseData); + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); } return [this.helpers.returnJsonArray(returnData)]; } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 1b4a18a5cc8..915b3c81da6 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.66.0", + "version": "0.65.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -119,7 +119,7 @@ "dist/credentials/StripeApi.credentials.js", "dist/credentials/SalesmateApi.credentials.js", "dist/credentials/SegmentApi.credentials.js", - "dist/credentials/SIGNL4Api.credentials.js", + "dist/credentials/Signl4Api.credentials.js", "dist/credentials/SurveyMonkeyApi.credentials.js", "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js", "dist/credentials/TelegramApi.credentials.js", @@ -261,7 +261,7 @@ "dist/nodes/Set.node.js", "dist/nodes/Shopify/Shopify.node.js", "dist/nodes/Shopify/ShopifyTrigger.node.js", - "dist/nodes/SIGNL4/SIGNL4.node.js", + "dist/nodes/signl4/Signl4.node.js", "dist/nodes/Slack/Slack.node.js", "dist/nodes/Sms77/Sms77.node.js", "dist/nodes/SplitInBatches.node.js", From 830e4f19b6edfadb0aea20b0ce8b1de553ae9ca2 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 25 Jun 2020 09:54:25 +0200 Subject: [PATCH 117/120] :zap: Minor improvements to Signl4-Node --- ...4Api.credentials.ts => Signl4Api.credentials.ts} | 0 .../nodes/{SIGNL4 => Signl4}/GenericFunctions.ts | 0 .../signl4.node.ts => Signl4/Signl4.node.ts} | 4 ++-- .../nodes-base/nodes/{SIGNL4 => Signl4}/signl4.png | Bin packages/nodes-base/package.json | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename packages/nodes-base/credentials/{SIGNL4Api.credentials.ts => Signl4Api.credentials.ts} (100%) rename packages/nodes-base/nodes/{SIGNL4 => Signl4}/GenericFunctions.ts (100%) rename packages/nodes-base/nodes/{SIGNL4/signl4.node.ts => Signl4/Signl4.node.ts} (99%) rename packages/nodes-base/nodes/{SIGNL4 => Signl4}/signl4.png (100%) diff --git a/packages/nodes-base/credentials/SIGNL4Api.credentials.ts b/packages/nodes-base/credentials/Signl4Api.credentials.ts similarity index 100% rename from packages/nodes-base/credentials/SIGNL4Api.credentials.ts rename to packages/nodes-base/credentials/Signl4Api.credentials.ts diff --git a/packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts b/packages/nodes-base/nodes/Signl4/GenericFunctions.ts similarity index 100% rename from packages/nodes-base/nodes/SIGNL4/GenericFunctions.ts rename to packages/nodes-base/nodes/Signl4/GenericFunctions.ts diff --git a/packages/nodes-base/nodes/SIGNL4/signl4.node.ts b/packages/nodes-base/nodes/Signl4/Signl4.node.ts similarity index 99% rename from packages/nodes-base/nodes/SIGNL4/signl4.node.ts rename to packages/nodes-base/nodes/Signl4/Signl4.node.ts index 5a2ec290e38..29d3917cf6c 100644 --- a/packages/nodes-base/nodes/SIGNL4/signl4.node.ts +++ b/packages/nodes-base/nodes/Signl4/Signl4.node.ts @@ -268,7 +268,7 @@ export class Signl4 implements INodeType { if (attachments) { if (attachments.attachmentsBinary && items[i].binary) { - const propertyName = (attachments.attachmentsBinary as IDataObject).property as string + const propertyName = (attachments.attachmentsBinary as IDataObject).property as string; const binaryProperty = (items[i].binary as IBinaryKeyData)[propertyName]; @@ -287,7 +287,7 @@ export class Signl4 implements INodeType { filename: binaryProperty.fileName, contentType: binaryProperty.mimeType, }, - } + }; } else { throw new Error(`Binary property ${propertyName} does not exist on input`); diff --git a/packages/nodes-base/nodes/SIGNL4/signl4.png b/packages/nodes-base/nodes/Signl4/signl4.png similarity index 100% rename from packages/nodes-base/nodes/SIGNL4/signl4.png rename to packages/nodes-base/nodes/Signl4/signl4.png diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 915b3c81da6..0fa350583b0 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -261,7 +261,7 @@ "dist/nodes/Set.node.js", "dist/nodes/Shopify/Shopify.node.js", "dist/nodes/Shopify/ShopifyTrigger.node.js", - "dist/nodes/signl4/Signl4.node.js", + "dist/nodes/Signl4/Signl4.node.js", "dist/nodes/Slack/Slack.node.js", "dist/nodes/Sms77/Sms77.node.js", "dist/nodes/SplitInBatches.node.js", From 6af3743e1ba5ccdbc354cad9701cf22eda2603a5 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 25 Jun 2020 11:39:04 +0200 Subject: [PATCH 118/120] :sparkles: Add Postgres SSL support --- packages/cli/config/index.ts | 28 ++++++++++++++++++++++++++++ packages/cli/src/Db.ts | 20 ++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts index ce79ca52215..a3c2cfd03aa 100644 --- a/packages/cli/config/index.ts +++ b/packages/cli/config/index.ts @@ -63,6 +63,34 @@ const config = convict({ default: 'public', env: 'DB_POSTGRESDB_SCHEMA' }, + + ssl: { + ca: { + doc: 'SSL certificate authority', + format: String, + default: '', + env: 'DB_POSTGRESDB_SSL_CA', + }, + cert: { + doc: 'SSL certificate', + format: String, + default: '', + env: 'DB_POSTGRESDB_SSL_CERT', + }, + key: { + doc: 'SSL key', + format: String, + default: '', + env: 'DB_POSTGRESDB_SSL_KEY', + }, + rejectUnauthorized: { + doc: 'If unauthorized SSL connections should be rejected', + format: 'Boolean', + default: true, + env: 'DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED', + }, + } + }, mysqldb: { database: { diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index 54633adb139..efda1f63665 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -14,6 +14,8 @@ import { getRepository, } from 'typeorm'; +import { TlsOptions } from 'tls'; + import * as config from '../config'; import { @@ -72,6 +74,22 @@ export async function init(): Promise { case 'postgresdb': entities = PostgresDb; + + const sslCa = await GenericHelpers.getConfigValue('database.postgresdb.ssl.ca') as string; + const sslCert = await GenericHelpers.getConfigValue('database.postgresdb.ssl.cert') as string; + const sslKey = await GenericHelpers.getConfigValue('database.postgresdb.ssl.key') as string; + const sslRejectUnauthorized = await GenericHelpers.getConfigValue('database.postgresdb.ssl.rejectUnauthorized') as boolean; + + let ssl: TlsOptions | undefined = undefined; + if (sslCa !== '' || sslCert !== '' || sslKey !== '' || sslRejectUnauthorized !== true) { + ssl = { + ca: sslCa || undefined, + cert: sslCert || undefined, + key: sslKey || undefined, + rejectUnauthorized: sslRejectUnauthorized, + }; + } + connectionOptions = { type: 'postgres', entityPrefix, @@ -84,7 +102,9 @@ export async function init(): Promise { migrations: [InitialMigration1587669153312], migrationsRun: true, migrationsTableName: `${entityPrefix}migrations`, + ssl, }; + break; case 'mariadb': From 28befc10346362b79e4ead8a7ec9d2efb3589b3e Mon Sep 17 00:00:00 2001 From: ricardo Date: Thu, 25 Jun 2020 19:43:49 -0400 Subject: [PATCH 119/120] :zap: Improvements to Eventbrite-Trigger --- .../credentials/EventbriteApi.credentials.ts | 2 +- .../Eventbrite/EventbriteTrigger.node.ts | 38 ++++++++++++------- .../nodes/Eventbrite/GenericFunctions.ts | 12 ++++-- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/packages/nodes-base/credentials/EventbriteApi.credentials.ts b/packages/nodes-base/credentials/EventbriteApi.credentials.ts index 9fa48753fbe..e54be8580cd 100644 --- a/packages/nodes-base/credentials/EventbriteApi.credentials.ts +++ b/packages/nodes-base/credentials/EventbriteApi.credentials.ts @@ -8,7 +8,7 @@ export class EventbriteApi implements ICredentialType { displayName = 'Eventbrite API'; properties = [ { - displayName: 'API Key', + displayName: 'Private Key', name: 'apiKey', type: 'string' as NodePropertyTypes, default: '', diff --git a/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts index 8299ae866df..0c9158b6c3a 100644 --- a/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts +++ b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts @@ -38,7 +38,7 @@ export class EventbriteTrigger implements INodeType { displayOptions: { show: { authentication: [ - 'accessToken', + 'privateKey', ], }, }, @@ -70,15 +70,15 @@ export class EventbriteTrigger implements INodeType { type: 'options', options: [ { - name: 'Access Token', - value: 'accessToken', + name: 'Private Key', + value: 'privateKey', }, { name: 'OAuth2', value: 'oAuth2', }, ], - default: 'accessToken', + default: 'privateKey', description: 'The resource to operate on.', }, { @@ -184,7 +184,6 @@ export class EventbriteTrigger implements INodeType { description: 'By default does the webhook-data only contain the URL to receive
the object data manually. If this option gets activated it
will resolve the data automatically.', }, ], - }; methods = { @@ -227,18 +226,31 @@ export class EventbriteTrigger implements INodeType { default: { async checkExists(this: IHookFunctions): Promise { const webhookData = this.getWorkflowStaticData('node'); + const webhookUrl = this.getNodeWebhookUrl('default'); const organisation = this.getNodeParameter('organization') as string; + const actions = this.getNodeParameter('actions') as string[]; - if (webhookData.webhookId === undefined) { - return false; - } const endpoint = `/organizations/${organisation}/webhooks/`; - try { - await eventbriteApiRequest.call(this, 'GET', endpoint); - } catch (e) { - return false; + + const { webhooks } = await eventbriteApiRequest.call(this, 'GET', endpoint); + + const check = (currentActions: string[], webhookActions: string[]) => { + for (const currentAction of currentActions) { + if (!webhookActions.includes(currentAction)) { + return false; + } + } + return true; + }; + + for (const webhook of webhooks) { + if (webhook.endpoint_url === webhookUrl && check(actions, webhook.actions)) { + webhookData.webhookId = webhook.id; + return true; + } } - return true; + + return false; }, async create(this: IHookFunctions): Promise { const webhookUrl = this.getNodeWebhookUrl('default'); diff --git a/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts b/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts index 5bbb517c5d1..2392a0ae209 100644 --- a/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts @@ -1,4 +1,7 @@ -import { OptionsWithUri } from 'request'; +import { + OptionsWithUri, +} from 'request'; + import { IExecuteFunctions, IExecuteSingleFunctions, @@ -6,7 +9,10 @@ import { ILoadOptionsFunctions, IWebhookFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; + +import { + IDataObject, +} from 'n8n-workflow'; export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any let options: OptionsWithUri = { @@ -25,7 +31,7 @@ export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFuncti const authenticationMethod = this.getNodeParameter('authentication', 0); try { - if (authenticationMethod === 'accessToken') { + if (authenticationMethod === 'privateKey') { const credentials = this.getCredentials('eventbriteApi'); if (credentials === undefined) { throw new Error('No credentials got returned!'); From e68f45c142ab559fafb89953dec8ff2c26c104a0 Mon Sep 17 00:00:00 2001 From: ricardo Date: Thu, 25 Jun 2020 22:29:04 -0400 Subject: [PATCH 120/120] :zap: Improvements to Drift-Node --- .../nodes-base/credentials/DriftOAuth2Api.credentials.ts | 8 +++++++- packages/nodes-base/nodes/Drift/GenericFunctions.ts | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/credentials/DriftOAuth2Api.credentials.ts b/packages/nodes-base/credentials/DriftOAuth2Api.credentials.ts index 5b509c5981a..1d25c49dfe2 100644 --- a/packages/nodes-base/credentials/DriftOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/DriftOAuth2Api.credentials.ts @@ -15,7 +15,7 @@ export class DriftOAuth2Api implements ICredentialType { displayName: 'Authorization URL', name: 'authUrl', type: 'hidden' as NodePropertyTypes, - default: 'ttps://dev.drift.com/authorize', + default: 'https://dev.drift.com/authorize', required: true, }, { @@ -31,6 +31,12 @@ export class DriftOAuth2Api implements ICredentialType { type: 'hidden' as NodePropertyTypes, default: '', }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: '', + }, { displayName: 'Authentication', name: 'authentication', diff --git a/packages/nodes-base/nodes/Drift/GenericFunctions.ts b/packages/nodes-base/nodes/Drift/GenericFunctions.ts index c28d6bd5a21..904fc4c6b3c 100644 --- a/packages/nodes-base/nodes/Drift/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Drift/GenericFunctions.ts @@ -46,8 +46,9 @@ export async function driftApiRequest(this: IExecuteFunctions | IWebhookFunction return await this.helpers.requestOAuth2!.call(this, 'driftOAuth2Api', options); } } catch (error) { - if (error.response) { - const errorMessage = error.message || (error.response.body && error.response.body.message ); + + if (error.response && error.response.body && error.response.body.error) { + const errorMessage = error.response.body.error.message; throw new Error(`Drift error response [${error.statusCode}]: ${errorMessage}`); } throw error;