From 34fea51f1e7826405f5a0f258c2cfa198a258fed Mon Sep 17 00:00:00 2001 From: GeylaniBerk Date: Thu, 4 Nov 2021 10:58:31 +0100 Subject: [PATCH 01/29] :bug: Adding credential test for Zendesk API Token --- .../nodes-base/nodes/Zendesk/Zendesk.node.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index 22718413d1..b6d9d173dd 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -1,8 +1,14 @@ +import { + OptionsWithUri, +} from 'request'; + import { IExecuteFunctions, } from 'n8n-core'; import { + ICredentialsDecrypted, + ICredentialTestFunctions, IDataObject, ILoadOptionsFunctions, INodeExecutionData, @@ -10,6 +16,7 @@ import { INodeType, INodeTypeDescription, NodeApiError, + NodeCredentialTestResult, NodeOperationError, } from 'n8n-workflow'; @@ -70,6 +77,7 @@ export class Zendesk implements INodeType { ], }, }, + testedBy: 'zendeskSoftwareApiTest', }, { name: 'zendeskOAuth2Api', @@ -146,6 +154,42 @@ export class Zendesk implements INodeType { }; methods = { + credentialTest: { + async zendeskSoftwareApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + const credentials = credential.data; + const subdomain = credentials!.subdomain; + const email = credentials!.email; + const apiToken = credentials!.apiToken; + + const base64Key = Buffer.from(`${email}/token:${apiToken}`).toString('base64'); + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Basic ${base64Key}`, + }, + method: 'GET', + uri: `https://${subdomain}.zendesk.com/api/v2/ticket_fields.json`, + qs: { + recent: 0, + }, + json: true, + timeout: 5000, + }; + + try { + const response = await this.helpers.request!(options); + } catch (error) { + return { + status: 'Error', + message: `Connection details not valid; ${error.message}`, + }; + } + return { + status: 'OK', + message: 'Authentication successful!', + }; + }, + }, loadOptions: { // Get all the custom fields to display them to user so that he can // select them easily From 653a8bb42ea150e3305970c26a35af399a460fcf Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 9 Nov 2021 22:04:45 +0100 Subject: [PATCH 02/29] :bug: Fix bug with internal hooks and CLI workflow execution --- packages/cli/commands/execute.ts | 4 ++++ packages/cli/commands/executeBatch.ts | 16 ++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/cli/commands/execute.ts b/packages/cli/commands/execute.ts index b6641628df..b74eb7397d 100644 --- a/packages/cli/commands/execute.ts +++ b/packages/cli/commands/execute.ts @@ -11,6 +11,7 @@ import { CredentialTypes, Db, ExternalHooks, + InternalHooksManager, IWorkflowBase, IWorkflowExecutionDataProcess, LoadNodesAndCredentials, @@ -123,6 +124,9 @@ export class Execute extends Command { const externalHooks = ExternalHooks(); await externalHooks.init(); + const instanceId = await UserSettings.getInstanceId(); + InternalHooksManager.init(instanceId); + // 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/executeBatch.ts b/packages/cli/commands/executeBatch.ts index d4489c38d4..4834e69d65 100644 --- a/packages/cli/commands/executeBatch.ts +++ b/packages/cli/commands/executeBatch.ts @@ -28,6 +28,7 @@ import { CredentialTypes, Db, ExternalHooks, + InternalHooksManager, IWorkflowDb, IWorkflowExecutionDataProcess, LoadNodesAndCredentials, @@ -55,12 +56,12 @@ export class ExecuteBatch extends Command { static executionTimeout = 3 * 60 * 1000; static examples = [ - `$ n8n executeAll`, - `$ n8n executeAll --concurrency=10 --skipList=/data/skipList.txt`, - `$ n8n executeAll --debug --output=/data/output.json`, - `$ n8n executeAll --ids=10,13,15 --shortOutput`, - `$ n8n executeAll --snapshot=/data/snapshots --shallow`, - `$ n8n executeAll --compare=/data/previousExecutionData --retries=2`, + `$ n8n executeBatch`, + `$ n8n executeBatch --concurrency=10 --skipList=/data/skipList.txt`, + `$ n8n executeBatch --debug --output=/data/output.json`, + `$ n8n executeBatch --ids=10,13,15 --shortOutput`, + `$ n8n executeBatch --snapshot=/data/snapshots --shallow`, + `$ n8n executeBatch --compare=/data/previousExecutionData --retries=2`, ]; static flags = { @@ -303,6 +304,9 @@ export class ExecuteBatch extends Command { const externalHooks = ExternalHooks(); await externalHooks.init(); + const instanceId = await UserSettings.getInstanceId(); + InternalHooksManager.init(instanceId); + // Add the found types to an instance other parts of the application can use const nodeTypes = NodeTypes(); await nodeTypes.init(loadNodesAndCredentials.nodeTypes); From e8133d80f8998ba638494939fcf1e2aa8f707c27 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 10 Nov 2021 08:49:45 +0100 Subject: [PATCH 03/29] :bug: Improve expression security --- packages/workflow/src/Expression.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/workflow/src/Expression.ts b/packages/workflow/src/Expression.ts index 6a92d2e26d..2b65ef03b8 100644 --- a/packages/workflow/src/Expression.ts +++ b/packages/workflow/src/Expression.ts @@ -99,6 +99,19 @@ export class Expression { ); const data = dataProxy.getDataProxy(); + // Support only a subset of process properties + // @ts-ignore + data.process = { + arch: process.arch, + env: process.env, + platform: process.platform, + pid: process.pid, + ppid: process.ppid, + release: process.release, + version: process.pid, + versions: process.versions, + }; + // Execute the expression try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call From 3c6f38d045cb06096b3e75d417d7c28614658494 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 10 Nov 2021 16:48:20 -0500 Subject: [PATCH 04/29] :sparkles: Add OneSimpleAPI Node (#2360) * Start of OneSimpleAPI Node * Node functionality is complete * :zap: Improvements to #2357 * :zap: Add internal feedback * :zap: Minor improvements Co-authored-by: Jonathan Co-authored-by: Jan Oberhauser --- .../credentials/OneSimpleApi.credentials.ts | 19 + .../nodes/OneSimpleApi/GenericFunctions.ts | 41 + .../nodes/OneSimpleApi/OneSimpleApi.node.json | 20 + .../nodes/OneSimpleApi/OneSimpleApi.node.ts | 867 ++++++++++++++++++ .../nodes/OneSimpleApi/onesimpleapi.svg | 25 + packages/nodes-base/package.json | 2 + 6 files changed, 974 insertions(+) create mode 100644 packages/nodes-base/credentials/OneSimpleApi.credentials.ts create mode 100644 packages/nodes-base/nodes/OneSimpleApi/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json create mode 100644 packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.ts create mode 100644 packages/nodes-base/nodes/OneSimpleApi/onesimpleapi.svg diff --git a/packages/nodes-base/credentials/OneSimpleApi.credentials.ts b/packages/nodes-base/credentials/OneSimpleApi.credentials.ts new file mode 100644 index 0000000000..63ec3d13a9 --- /dev/null +++ b/packages/nodes-base/credentials/OneSimpleApi.credentials.ts @@ -0,0 +1,19 @@ +import { + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + + +export class OneSimpleApi implements ICredentialType { + name = 'oneSimpleApi'; + displayName = 'One Simple API'; + documentationUrl = 'oneSimpleApi'; + properties: INodeProperties[] = [ + { + displayName: 'API Token', + name: 'apiToken', + type: 'string', + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/OneSimpleApi/GenericFunctions.ts b/packages/nodes-base/nodes/OneSimpleApi/GenericFunctions.ts new file mode 100644 index 0000000000..5e89c03156 --- /dev/null +++ b/packages/nodes-base/nodes/OneSimpleApi/GenericFunctions.ts @@ -0,0 +1,41 @@ +import { + OptionsWithUri +} from 'request'; + +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + NodeApiError, + NodeOperationError, +} from 'n8n-workflow'; + +export async function oneSimpleApiRequest(this: IExecuteFunctions, method: string, resource: string, body: IDataObject = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}) { + const credentials = await this.getCredentials('oneSimpleApi'); + if (credentials === undefined) { + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); + } + + const outputFormat = 'json'; + let options: OptionsWithUri = { + method, + body, + qs, + uri: uri || `https://onesimpleapi.com/api${resource}?token=${credentials.apiToken}&output=${outputFormat}`, + json: true, + }; + options = Object.assign({}, options, option); + + if (Object.keys(body).length === 0) { + delete options.body; + } + + try { + const responseData = await this.helpers.request(options); + return responseData; + } catch (error) { + throw new NodeApiError(this.getNode(), error); + } +} diff --git a/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json b/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json new file mode 100644 index 0000000000..bd85e8ee3b --- /dev/null +++ b/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json @@ -0,0 +1,20 @@ +{ + "node": "n8n-nodes-base.oneSimpleApi", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Utility" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/OneSimpleAPI" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.oneSimpleApi/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.ts b/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.ts new file mode 100644 index 0000000000..5edda2ee4b --- /dev/null +++ b/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.ts @@ -0,0 +1,867 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + oneSimpleApiRequest, +} from './GenericFunctions'; + +export class OneSimpleApi implements INodeType { + description: INodeTypeDescription = { + displayName: 'One Simple API', + name: 'oneSimpleApi', + icon: 'file:onesimpleapi.svg', + group: ['transform'], + version: 1, + description: 'A toolbox of no-code utilities', + defaults: { + name: 'One Simple API', + color: '#1A82e2', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'oneSimpleApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Information', + value: 'information', + }, + { + name: 'Utility', + value: 'utility', + }, + { + name: 'Website', + value: 'website', + }, + ], + default: 'website', + required: true, + }, + // Generation + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'website', + ], + }, + }, + options: [ + { + name: 'Generate PDF', + value: 'pdf', + description: 'Generate a PDF from a webpage', + }, + { + name: 'Get SEO Data', + value: 'seo', + description: 'Get SEO information from website', + }, + { + name: 'Take Screenshot', + value: 'screenshot', + description: 'Create a screenshot from a webpage', + }, + ], + default: 'pdf', + }, + // Information + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'information', + ], + }, + }, + options: [ + { + name: 'Exchange Rate', + value: 'exchangeRate', + description: 'Convert a value between currencies', + }, + { + name: 'Image Metadata', + value: 'imageMetadata', + description: 'Retrieve image metadata from a URL', + }, + ], + default: 'exchangeRate', + description: 'The operation to perform.', + }, + // Utiliy + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'utility', + ], + }, + }, + options: [ + { + name: 'Expand URL', + value: 'expandURL', + description: 'Expand a shortened url', + }, + { + name: 'Generate QR Code', + value: 'qrCode', + description: 'Generate a QR Code', + }, + { + name: 'Validate Email', + value: 'validateEmail', + description: 'Validate an email address', + }, + ], + default: 'validateEmail', + description: 'The operation to perform.', + }, + // website: pdf + { + displayName: 'Webpage URL', + name: 'link', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'pdf', + ], + resource: [ + 'website', + ], + }, + }, + default: '', + description: 'Link to webpage to convert', + }, + { + displayName: 'Download PDF?', + name: 'download', + type: 'boolean', + required: true, + displayOptions: { + show: { + operation: [ + 'pdf', + ], + resource: [ + 'website', + ], + }, + }, + default: false, + description: 'Whether to download the PDF or return a link to it', + }, + { + displayName: 'Put Output In Field', + name: 'output', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'pdf', + ], + resource: [ + 'website', + ], + download: [ + true, + ], + }, + }, + default: 'data', + description: 'The name of the output field to put the binary file data in', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'website', + ], + operation: [ + 'pdf', + ], + }, + }, + options: [ + { + displayName: 'Page Size', + name: 'page', + type: 'options', + options: [ + { + name: 'A0', + value: 'A0', + }, + { + name: 'A1', + value: 'A1', + }, + { + name: 'A2', + value: 'A2', + }, + { + name: 'A3', + value: 'A3', + }, + { + name: 'A4', + value: 'A4', + }, + { + name: 'A5', + value: 'A5', + }, + { + name: 'A6', + value: 'A6', + }, + { + name: 'Legal', + value: 'Legal', + }, + { + name: 'Ledger', + value: 'Ledger', + }, + { + name: 'Letter', + value: 'Letter', + }, + { + name: 'Tabloid', + value: 'Tabloid', + }, + ], + default: '', + description: 'The page size', + }, + { + displayName: 'Force Refresh', + name: 'force', + type: 'boolean', + default: false, + description: `Normally the API will reuse a previously taken screenshot of the URL to give a faster response. + This option allows you to retake the screenshot at that exact time, for those times when it's necessary`, + }, + ], + }, + // website: qrCode + { + displayName: 'QR Content', + name: 'message', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'qrCode', + ], + resource: [ + 'utility', + ], + }, + }, + default: '', + description: 'The text that should be turned into a QR code - like a website URL', + }, + { + displayName: 'Download Image?', + name: 'download', + type: 'boolean', + required: true, + displayOptions: { + show: { + operation: [ + 'qrCode', + ], + resource: [ + 'utility', + ], + }, + }, + default: false, + description: 'Whether to download the QR code or return a link to it', + }, + { + displayName: 'Put Output In Field', + name: 'output', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'qrCode', + ], + resource: [ + 'utility', + ], + download: [ + true, + ], + }, + }, + default: 'data', + description: 'The name of the output field to put the binary file data in', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'utility', + ], + operation: [ + 'qrCode', + ], + }, + }, + options: [ + { + displayName: 'Size', + name: 'size', + type: 'options', + options: [ + { + name: 'Small', + value: 'Small', + }, + { + name: 'Medium', + value: 'Medium', + }, + { + name: 'Large', + value: 'Large', + }, + ], + default: 'Small', + description: 'The QR Code size', + }, + { + displayName: 'Format', + name: 'format', + type: 'options', + options: [ + { + name: 'PNG', + value: 'PNG', + }, + { + name: 'SVG', + value: 'SVG', + }, + ], + default: 'PNG', + description: 'The QR Code format', + }, + ], + }, + // website: screenshot + { + displayName: 'Webpage URL', + name: 'link', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'screenshot', + ], + resource: [ + 'website', + ], + }, + }, + default: '', + description: 'Link to webpage to convert', + }, + { + displayName: 'Download Screenshot?', + name: 'download', + type: 'boolean', + required: true, + displayOptions: { + show: { + operation: [ + 'screenshot', + ], + resource: [ + 'website', + ], + }, + }, + default: false, + description: 'Whether to download the screenshot or return a link to it', + }, + { + displayName: 'Put Output In Field', + name: 'output', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'screenshot', + ], + resource: [ + 'website', + ], + download: [ + true, + ], + }, + }, + default: 'data', + description: 'The name of the output field to put the binary file data in', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'website', + ], + operation: [ + 'screenshot', + ], + }, + }, + options: [ + { + displayName: 'Screen Size', + name: 'screen', + type: 'options', + options: [ + { + name: 'Phone', + value: 'phone', + }, + { + name: 'Phone Landscape', + value: 'phone-landscape', + }, + { + name: 'Retina', + value: 'retina', + }, + { + name: 'Tablet', + value: 'tablet', + }, + { + name: 'Tablet Landscape', + value: 'tablet-landscape', + }, + ], + default: '', + description: 'The screen size', + }, + { + displayName: 'Force Refresh', + name: 'force', + type: 'boolean', + default: false, + description: `Normally the API will reuse a previously taken screenshot of the URL to give a faster response. + This option allows you to retake the screenshot at that exact time, for those times when it's necessary`, + }, + { + displayName: 'Full Page', + name: 'fullpage', + type: 'boolean', + default: false, + description: 'The API takes a screenshot of the viewable area for the desired screen size. If you need a screenshot of the whole length of the page, use this option', + }, + ], + }, + // information: exchangeRate + { + displayName: 'Value', + name: 'value', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'exchangeRate', + ], + resource: [ + 'information', + ], + }, + }, + default: '', + description: 'Value to convert', + }, + { + displayName: 'From Currency', + name: 'fromCurrency', + type: 'string', + required: true, + placeholder: 'USD', + displayOptions: { + show: { + operation: [ + 'exchangeRate', + ], + resource: [ + 'information', + ], + }, + }, + default: '', + description: 'From Currency', + }, + { + displayName: 'To Currency', + name: 'toCurrency', + type: 'string', + placeholder: 'EUR', + required: true, + displayOptions: { + show: { + operation: [ + 'exchangeRate', + ], + resource: [ + 'information', + ], + }, + }, + default: '', + description: 'To Currency', + }, + // information: imageMetadata + { + displayName: 'Link To Image', + name: 'link', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'imageMetadata', + ], + resource: [ + 'information', + ], + }, + }, + default: '', + description: 'Image to get metadata from', + }, + // website: seo + { + displayName: 'Webpage URL', + name: 'link', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'seo', + ], + resource: [ + 'website', + ], + }, + }, + default: '', + description: 'Webpage to get SEO information for', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'website', + ], + operation: [ + 'seo', + ], + }, + }, + options: [ + { + displayName: 'Include Headers?', + name: 'headers', + type: 'boolean', + default: false, + description: '', + }, + ], + }, + // utility: validateEmail + { + displayName: 'Email Address', + name: 'emailAddress', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'validateEmail', + ], + resource: [ + 'utility', + ], + }, + }, + default: '', + description: 'Email Address', + }, + // utility: expandURL + { + displayName: 'URL', + name: 'link', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'expandURL', + ], + resource: [ + 'utility', + ], + }, + }, + default: '', + description: 'URL to unshorten', + }, + ], + }; + + 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; + let download; + for (let i = 0; i < length; i++) { + try { + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + if (resource === 'website') { + if (operation === 'pdf') { + const link = this.getNodeParameter('link', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + download = this.getNodeParameter('download', i) as boolean; + qs.url = link; + + if (options.page) { + qs.page = options.page as string; + } + + if (options.force) { + qs.force = 'yes'; + } else { + qs.force = 'no'; + } + + const response = await oneSimpleApiRequest.call(this, 'GET', '/pdf', {}, qs); + + if (download) { + const output = this.getNodeParameter('output', i) as string; + const buffer = await oneSimpleApiRequest.call(this, 'GET', '', {}, {}, response.url, { json: false, encoding: null }) as Buffer; + responseData = { + json: response, + binary: { + [output]: await this.helpers.prepareBinaryData(buffer), + }, + }; + } else { + responseData = response; + } + } + + if (operation === 'screenshot') { + const link = this.getNodeParameter('link', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + download = this.getNodeParameter('download', i) as boolean; + + qs.url = link; + + if (options.screen) { + qs.screen = options.screen as string; + } + + if (options.fullpage) { + qs.fullpage = 'yes'; + } else { + qs.fullpage = 'no'; + } + + if (options.force) { + qs.force = 'yes'; + } else { + qs.force = 'no'; + } + + const response = await oneSimpleApiRequest.call(this, 'GET', '/screenshot', {}, qs); + + if (download) { + const output = this.getNodeParameter('output', i) as string; + const buffer = await oneSimpleApiRequest.call(this, 'GET', '', {}, {}, response.url, { json: false, encoding: null }) as Buffer; + responseData = { + json: response, + binary: { + [output]: await this.helpers.prepareBinaryData(buffer), + }, + }; + } else { + responseData = response; + } + } + + if (operation === 'seo') { + const link = this.getNodeParameter('link', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + qs.url = link; + + if (options.headers) { + qs.headers = 'yes'; + } + + responseData = await oneSimpleApiRequest.call(this, 'GET', '/page_info', {}, qs); + } + } + + if (resource === 'information') { + if (operation === 'exchangeRate') { + const value = this.getNodeParameter('value', i) as string; + const fromCurrency = this.getNodeParameter('fromCurrency', i) as string; + const toCurrency = this.getNodeParameter('toCurrency', i) as string; + qs.from_currency = fromCurrency; + qs.to_currency = toCurrency; + qs.from_value = value; + responseData = await oneSimpleApiRequest.call(this, 'GET', '/exchange_rate', {}, qs); + } + + if (operation === 'imageMetadata') { + const link = this.getNodeParameter('link', i) as string; + qs.url = link; + qs.raw = true; + responseData = await oneSimpleApiRequest.call(this, 'GET', '/image_info', {}, qs); + } + } + + if (resource === 'utility') { + // validateEmail + if (operation === 'validateEmail') { + const emailAddress = this.getNodeParameter('emailAddress', i) as string; + qs.email = emailAddress; + responseData = await oneSimpleApiRequest.call(this, 'GET', '/email', {}, qs); + } + // expandURL + if (operation === 'expandURL') { + const url = this.getNodeParameter('link', i) as string; + qs.url = url; + responseData = await oneSimpleApiRequest.call(this, 'GET', '/unshorten', {}, qs); + } + + if (operation === 'qrCode') { + const message = this.getNodeParameter('message', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + download = this.getNodeParameter('download', i) as boolean; + + qs.message = message; + + if (options.size) { + qs.size = options.size as string; + } + + if (options.format) { + qs.format = options.format as string; + } + + const response = await oneSimpleApiRequest.call(this, 'GET', '/qr_code', {}, qs); + + if (download) { + const output = this.getNodeParameter('output', i) as string; + const buffer = await oneSimpleApiRequest.call(this, 'GET', '', {}, {}, response.url, { json: false, encoding: null }) as Buffer; + responseData = { + json: response, + binary: { + [output]: await this.helpers.prepareBinaryData(buffer), + }, + }; + } else { + responseData = response; + } + } + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ error: error.message }); + continue; + } + throw error; + } + } + + if (download) { + return this.prepareOutputData(returnData as unknown as INodeExecutionData[]); + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/OneSimpleApi/onesimpleapi.svg b/packages/nodes-base/nodes/OneSimpleApi/onesimpleapi.svg new file mode 100644 index 0000000000..9a918f0a9c --- /dev/null +++ b/packages/nodes-base/nodes/OneSimpleApi/onesimpleapi.svg @@ -0,0 +1,25 @@ + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3034a6ab2c..c51772500e 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -201,6 +201,7 @@ "dist/credentials/NotionOAuth2Api.credentials.js", "dist/credentials/OAuth1Api.credentials.js", "dist/credentials/OAuth2Api.credentials.js", + "dist/credentials/OneSimpleApi.credentials.js", "dist/credentials/OpenWeatherMapApi.credentials.js", "dist/credentials/OrbitApi.credentials.js", "dist/credentials/OuraApi.credentials.js", @@ -520,6 +521,7 @@ "dist/nodes/Notion/NotionTrigger.node.js", "dist/nodes/N8nTrainingCustomerDatastore.node.js", "dist/nodes/N8nTrainingCustomerMessenger.node.js", + "dist/nodes/OneSimpleApi/OneSimpleApi.node.js", "dist/nodes/OpenThesaurus/OpenThesaurus.node.js", "dist/nodes/OpenWeatherMap.node.js", "dist/nodes/Orbit/Orbit.node.js", From 1a1bc26ecf3cfddaa99533d22adad06896be12dc Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 10 Nov 2021 18:03:45 -0500 Subject: [PATCH 05/29] :zap: Add role parameter to user:update (Zulip) (#2336) * :zap: Add role parameter to user:update * :pencil2: Fix typo issue --- .../nodes-base/nodes/Zulip/UserDescription.ts | 33 +++++++++++++++++-- .../nodes-base/nodes/Zulip/UserInterface.ts | 1 + packages/nodes-base/nodes/Zulip/Zulip.node.ts | 3 ++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Zulip/UserDescription.ts b/packages/nodes-base/nodes/Zulip/UserDescription.ts index 1890f71c9d..164432e5b0 100644 --- a/packages/nodes-base/nodes/Zulip/UserDescription.ts +++ b/packages/nodes-base/nodes/Zulip/UserDescription.ts @@ -226,14 +226,14 @@ export const userFields = [ name: 'isAdmin', type: 'boolean', default: false, - description: 'Whether the target user is an administrator.', + description: 'Whether the target user is an administrator', }, { displayName: 'Is Guest', name: 'isGuest', type: 'boolean', default: false, - description: 'Whether the target user is a guest.', + description: 'Whether the target user is a guest', }, { displayName: 'Profile Data', @@ -268,6 +268,35 @@ export const userFields = [ }, ], }, + { + displayName: 'Role', + name: 'role', + type: 'options', + options: [ + { + name: 'Organization Owner', + value: 100, + }, + { + name: 'Organization Administrator', + value: 200, + }, + { + name: 'Organization Moderator', + value: 300, + }, + { + name: 'Member', + value: 400, + }, + { + name: 'Guest', + value: 600, + }, + ], + default: '', + description: 'Role for the user', + }, ], }, diff --git a/packages/nodes-base/nodes/Zulip/UserInterface.ts b/packages/nodes-base/nodes/Zulip/UserInterface.ts index fd7dffcea9..12ff6d63b5 100644 --- a/packages/nodes-base/nodes/Zulip/UserInterface.ts +++ b/packages/nodes-base/nodes/Zulip/UserInterface.ts @@ -8,4 +8,5 @@ export interface IUser { email?: string; password?: string; short_name?: string; + role?: number; } diff --git a/packages/nodes-base/nodes/Zulip/Zulip.node.ts b/packages/nodes-base/nodes/Zulip/Zulip.node.ts index 9281e90dc9..1e9e9c9bd0 100644 --- a/packages/nodes-base/nodes/Zulip/Zulip.node.ts +++ b/packages/nodes-base/nodes/Zulip/Zulip.node.ts @@ -431,6 +431,9 @@ export class Zulip implements INodeType { if (additionalFields.isGuest) { body.is_guest = additionalFields.isGuest as boolean; } + if (additionalFields.role) { + body.role = additionalFields.role as number; + } if (additionalFields.profileData) { //@ts-ignore body.profile_data = additionalFields.profileData.properties as [{}]; From dc2bda4baa9d45fc9edce2862531a17f5bff9179 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 11 Nov 2021 12:08:05 +0100 Subject: [PATCH 06/29] :zap: Minor improvements --- packages/nodes-base/nodes/Jira/Jira.node.ts | 2 +- packages/nodes-base/nodes/Zendesk/Zendesk.node.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts index 3cd0509566..579840d3a9 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -179,7 +179,7 @@ export class Jira implements INodeType { } catch (err) { return { status: 'Error', - message: `Connection details not valid; ${err.message}`, + message: `Connection details not valid: ${err.message}`, }; } return { diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index b6d9d173dd..83c7850e74 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -177,11 +177,11 @@ export class Zendesk implements INodeType { }; try { - const response = await this.helpers.request!(options); + await this.helpers.request!(options); } catch (error) { return { status: 'Error', - message: `Connection details not valid; ${error.message}`, + message: `Connection details not valid: ${error.message}`, }; } return { From abdcb0836e07944b4585b7052c9f1cbf299c8537 Mon Sep 17 00:00:00 2001 From: Harshil Agrawal Date: Fri, 12 Nov 2021 13:53:47 +0100 Subject: [PATCH 07/29] :zap: Add codex files (#2431) --- .../nodes/Dropcontact/Dropcontact.node.json | 20 +++++++++++++++++++ .../nodes/RespondToWebhook.node.json | 19 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 packages/nodes-base/nodes/Dropcontact/Dropcontact.node.json create mode 100644 packages/nodes-base/nodes/RespondToWebhook.node.json diff --git a/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.json b/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.json new file mode 100644 index 0000000000..4b47e8e54e --- /dev/null +++ b/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.json @@ -0,0 +1,20 @@ +{ + "node": "n8n-nodes-base.dropcontact", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Sales" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/dropcontact" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.dropcontact/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/RespondToWebhook.node.json b/packages/nodes-base/nodes/RespondToWebhook.node.json new file mode 100644 index 0000000000..99bf635add --- /dev/null +++ b/packages/nodes-base/nodes/RespondToWebhook.node.json @@ -0,0 +1,19 @@ +{ + "node": "n8n-nodes-base.respondToWebhook", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Core Nodes", + "Utility" + ], + "resources": { + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.respondToWebhook/" + } + ] + }, + "subcategories": { + "Core Nodes":["Flow"] + } +} From 15e64d1bc44802aed2a63a6557f439229e9c5631 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Fri, 12 Nov 2021 13:55:29 +0100 Subject: [PATCH 08/29] :bug: Add function to calculate content-length when using multipart/form-data (#2427) --- packages/core/src/NodeExecuteFunctions.ts | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 29b0c140e7..7fcf724401 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -135,6 +135,28 @@ function searchForHeader(headers: IDataObject, headerName: string) { return headerNames.find((thisHeader) => thisHeader.toLowerCase() === headerName); } +async function generateContentLengthHeader(formData: FormData, headers: IDataObject) { + if (!formData || !formData.getLength) { + return; + } + try { + const length = await new Promise((res, rej) => { + formData.getLength((error: Error | null, length: number) => { + if (error) { + rej(error); + return; + } + res(length); + }); + }); + headers = Object.assign(headers, { + 'content-length': length, + }); + } catch (error) { + Logger.error('Unable to calculate form data length', { error }); + } +} + async function parseRequestObject(requestObject: IDataObject) { // This function is a temporary implementation // That translates all http requests done via @@ -199,6 +221,7 @@ async function parseRequestObject(requestObject: IDataObject) { delete axiosConfig.headers[contentTypeHeaderKeyName]; const headers = axiosConfig.data.getHeaders(); axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers); + await generateContentLengthHeader(axiosConfig.data, axiosConfig.headers); } else { // When using the `form` property it means the content should be x-www-form-urlencoded. if (requestObject.form !== undefined && requestObject.body === undefined) { @@ -235,6 +258,7 @@ async function parseRequestObject(requestObject: IDataObject) { // Mix in headers as FormData creates the boundary. const headers = axiosConfig.data.getHeaders(); axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers); + await generateContentLengthHeader(axiosConfig.data, axiosConfig.headers); } else if (requestObject.body !== undefined) { // If we have body and possibly form if (requestObject.form !== undefined) { From 357178d83b0ac6f8714f3950951bf52f0a9e5294 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Fri, 12 Nov 2021 14:28:49 +0100 Subject: [PATCH 09/29] :zap: New JSON attributes are now considered warnings in testing workflows (#2432) --- packages/cli/commands/executeBatch.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/cli/commands/executeBatch.ts b/packages/cli/commands/executeBatch.ts index 4834e69d65..587f91c2a9 100644 --- a/packages/cli/commands/executeBatch.ts +++ b/packages/cli/commands/executeBatch.ts @@ -817,10 +817,22 @@ export class ExecuteBatch extends Command { const changes = diff(JSON.parse(contents), data, { keysOnly: true }); if (changes !== undefined) { - // we have structural changes. Report them. - executionResult.error = `Workflow may contain breaking changes`; - executionResult.changes = changes; - executionResult.executionStatus = 'error'; + // If we had only additions with no removals + // Then we treat as a warning and not an error. + // To find this, we convert the object to JSON + // and search for the `__deleted` string + const changesJson = JSON.stringify(changes); + if (changesJson.includes('__deleted')) { + // we have structural changes. Report them. + executionResult.error = 'Workflow may contain breaking changes'; + executionResult.changes = changes; + executionResult.executionStatus = 'error'; + } else { + executionResult.error = + 'Workflow contains new data that previously did not exist.'; + executionResult.changes = changes; + executionResult.executionStatus = 'warning'; + } } else { executionResult.executionStatus = 'success'; } From 670e93c0f439abb739431b4c50a26d7dcaca602c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 09:37:42 +0100 Subject: [PATCH 10/29] :shirt: Fix lint issue --- packages/cli/src/Server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 655ef52e78..9cf50c40b1 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -1580,11 +1580,11 @@ class App { const findQuery = {} as FindManyOptions; if (req.query.filter) { findQuery.where = JSON.parse(req.query.filter as string); - if ((findQuery.where! as IDataObject).id !== undefined) { + if (findQuery.where.id !== undefined) { // No idea if multiple where parameters make db search // slower but to be sure that that is not the case we // remove all unnecessary fields in case the id is defined. - findQuery.where = { id: (findQuery.where! as IDataObject).id }; + findQuery.where = { id: findQuery.where.id }; } } From 6a1ca823122cf3071b6a01fc585b1af60f93fca1 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 13 Nov 2021 09:39:22 +0100 Subject: [PATCH 11/29] :sparkles: Edit-Image addition (circle + composite operator) (#2419) * Add a new dropcontact node * Improvements to #2389 * :zap: Add credentials verification * :zap: Small improvement * :zap: set default time to 45 seconds * :zap: Improvements * :zap: Improvements * :zap: Improvements * :zap: Improvements * :zap: Improvements * :bug: Set siren and language correctly * :sparkles: Add support to draw circle and composite operator * :zap: Improve naming Co-authored-by: PaulineDropcontact Co-authored-by: ricardo --- packages/nodes-base/nodes/EditImage.node.ts | 119 +++++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/EditImage.node.ts b/packages/nodes-base/nodes/EditImage.node.ts index a499cabeb1..ec34be54fa 100644 --- a/packages/nodes-base/nodes/EditImage.node.ts +++ b/packages/nodes-base/nodes/EditImage.node.ts @@ -152,6 +152,10 @@ const nodeOperationOptions: INodeProperties[] = [ }, }, options: [ + { + name: 'Circle', + value: 'circle', + }, { name: 'Line', value: 'line', @@ -192,6 +196,7 @@ const nodeOperationOptions: INodeProperties[] = [ 'draw', ], primitive: [ + 'circle', 'line', 'rectangle', ], @@ -210,6 +215,7 @@ const nodeOperationOptions: INodeProperties[] = [ 'draw', ], primitive: [ + 'circle', 'line', 'rectangle', ], @@ -228,6 +234,7 @@ const nodeOperationOptions: INodeProperties[] = [ 'draw', ], primitive: [ + 'circle', 'line', 'rectangle', ], @@ -246,6 +253,7 @@ const nodeOperationOptions: INodeProperties[] = [ 'draw', ], primitive: [ + 'circle', 'line', 'rectangle', ], @@ -472,6 +480,110 @@ const nodeOperationOptions: INodeProperties[] = [ }, description: 'The name of the binary property which contains the data of the image to composite on top of image which is found in Property Name.', }, + { + displayName: 'Operator', + name: 'operator', + type: 'options', + displayOptions: { + show: { + operation: [ + 'composite', + ], + }, + }, + options: [ + { + name: 'Add', + value: 'Add', + }, + { + name: 'Atop', + value: 'Atop', + }, + { + name: 'Bumpmap', + value: 'Bumpmap', + }, + { + name: 'Copy', + value: 'Copy', + }, + { + name: 'Copy Black', + value: 'CopyBlack', + }, + { + name: 'Copy Blue', + value: 'CopyBlue', + }, + { + name: 'Copy Cyan', + value: 'CopyCyan', + }, + { + name: 'Copy Green', + value: 'CopyGreen', + }, + { + name: 'Copy Magenta', + value: 'CopyMagenta', + }, + { + name: 'Copy Opacity', + value: 'CopyOpacity', + }, + { + name: 'Copy Red', + value: 'CopyRed', + }, + { + name: 'Copy Yellow', + value: 'CopyYellow', + }, + { + name: 'Difference', + value: 'Difference', + }, + { + name: 'Divide', + value: 'Divide', + }, + { + name: 'In', + value: 'In', + }, + { + name: 'Minus', + value: 'Minus', + }, + { + name: 'Multiply', + value: 'Multiply', + }, + { + name: 'Out', + value: 'Out', + }, + { + name: 'Over', + value: 'Over', + }, + { + name: 'Plus', + value: 'Plus', + }, + { + name: 'Subtract', + value: 'Subtract', + }, + { + name: 'Xor', + value: 'Xor', + }, + ], + default: 'Over', + description: 'The operator to use to combine the images.', + }, { displayName: 'Position X', name: 'positionX', @@ -1095,6 +1207,7 @@ export class EditImage implements INodeType { } else if (operationData.operation === 'composite') { const positionX = operationData.positionX as number; const positionY = operationData.positionY as number; + const operator = operationData.operator as string; const geometryString = (positionX >= 0 ? '+' : '') + positionX + (positionY >= 0 ? '+' : '') + positionY; @@ -1109,9 +1222,9 @@ export class EditImage implements INodeType { if (operations[0].operation === 'create') { // It seems like if the image gets created newly we have to create a new gm instance // else it fails for some reason - gmInstance = gm(gmInstance!.stream('png')).geometry(geometryString).composite(path); + gmInstance = gm(gmInstance!.stream('png')).compose(operator).geometry(geometryString).composite(path); } else { - gmInstance = gmInstance!.geometry(geometryString).composite(path); + gmInstance = gmInstance!.compose(operator).geometry(geometryString).composite(path); } if (operations.length !== i + 1) { @@ -1131,6 +1244,8 @@ export class EditImage implements INodeType { if (operationData.primitive === 'line') { gmInstance = gmInstance.drawLine(operationData.startPositionX as number, operationData.startPositionY as number, operationData.endPositionX as number, operationData.endPositionY as number); + } else if (operationData.primitive === 'circle') { + gmInstance = gmInstance.drawCircle(operationData.startPositionX as number, operationData.startPositionY as number, operationData.endPositionX as number, operationData.endPositionY as number); } else if (operationData.primitive === 'rectangle') { gmInstance = gmInstance.drawRectangle(operationData.startPositionX as number, operationData.startPositionY as number, operationData.endPositionX as number, operationData.endPositionY as number, operationData.cornerRadius as number || undefined); } From 7a0e072d98811ed7cfd0e700f986ecd96c303178 Mon Sep 17 00:00:00 2001 From: Tom McAtee Date: Sat, 13 Nov 2021 19:44:25 +1030 Subject: [PATCH 12/29] :bug: Fix Toggl Trigger Node (#2418) Updating API URL as per https://support.toggl.com/en/articles/5708431-why-did-my-integration-stop-working #2417 --- packages/nodes-base/nodes/Toggl/GenericFunctions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Toggl/GenericFunctions.ts b/packages/nodes-base/nodes/Toggl/GenericFunctions.ts index c694cf6008..b8386abb94 100644 --- a/packages/nodes-base/nodes/Toggl/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Toggl/GenericFunctions.ts @@ -25,7 +25,7 @@ export async function togglApiRequest(this: ITriggerFunctions | IPollFunctions | headers: headerWithAuthentication, method, qs: query, - uri: uri || `https://www.toggl.com/api/v8${resource}`, + uri: uri || `https://api.track.toggl.com/api/v8${resource}`, body, json: true, }; From 345c94bd37616212114e24f887bf11a8908197d1 Mon Sep 17 00:00:00 2001 From: Tom <19203795+that-one-tom@users.noreply.github.com> Date: Sat, 13 Nov 2021 10:16:56 +0100 Subject: [PATCH 13/29] :bug: Google Tasks: Fix due field (#2426) --- packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts | 4 ++-- packages/nodes-base/nodes/Google/Task/TaskDescription.ts | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts index f956251911..37b02c9343 100644 --- a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts +++ b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts @@ -124,7 +124,7 @@ export class GoogleTasks implements INodeType { body.notes = additionalFields.notes as string; } if (additionalFields.dueDate) { - body.dueDate = additionalFields.dueDate as string; + body.due = additionalFields.dueDate as string; } if (additionalFields.completed) { @@ -249,7 +249,7 @@ export class GoogleTasks implements INodeType { } if (updateFields.dueDate) { - body.dueDate = updateFields.dueDate as string; + body.due = updateFields.dueDate as string; } if (updateFields.completed) { diff --git a/packages/nodes-base/nodes/Google/Task/TaskDescription.ts b/packages/nodes-base/nodes/Google/Task/TaskDescription.ts index aaf2892ab5..90747569d6 100644 --- a/packages/nodes-base/nodes/Google/Task/TaskDescription.ts +++ b/packages/nodes-base/nodes/Google/Task/TaskDescription.ts @@ -447,6 +447,13 @@ export const taskFields = [ 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', From 8427ade2e6886edfb2dc9de24fb2205dfbdaa00a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 10:54:14 +0100 Subject: [PATCH 14/29] :bug: Allow Stripe Webhook creation if old does not exist anymore #2429 --- packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts b/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts index 58829d9aea..12ac348165 100644 --- a/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts +++ b/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts @@ -820,7 +820,7 @@ export class StripeTrigger implements INodeType { try { await stripeApiRequest.call(this, 'GET', endpoint, {}); } catch (error) { - if (error.message.includes('resource_missing')) { + if (error.httpCode === '404' || error.message.includes('resource_missing')) { // Webhook does not exist delete webhookData.webhookId; delete webhookData.webhookEvents; From c6fec12bec46245da9ac7736c1bb15767106f3e3 Mon Sep 17 00:00:00 2001 From: Max Mayr Date: Sat, 13 Nov 2021 11:06:16 +0100 Subject: [PATCH 15/29] :bug: Fix permission issue with custom images (#2355) --- docker/images/n8n-custom/docker-entrypoint.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/images/n8n-custom/docker-entrypoint.sh b/docker/images/n8n-custom/docker-entrypoint.sh index 2dd4dae105..acd6a6019c 100755 --- a/docker/images/n8n-custom/docker-entrypoint.sh +++ b/docker/images/n8n-custom/docker-entrypoint.sh @@ -6,6 +6,8 @@ if [ -d /root/.n8n ] ; then ln -s /root/.n8n /home/node/ fi +chown -R node /home/node + if [ "$#" -gt 0 ]; then # Got started with arguments COMMAND=$1; From 1db7d178b879ce767fe9380d3e0b375df54319bb Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:03 +0000 Subject: [PATCH 16/29] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-workflow@0.?= =?UTF-8?q?76.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 3027a02013..2e35eff0d3 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.75.0", + "version": "0.76.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From b8e83e0eea32bc05f1b3c6cf9bf4320fd975cfb4 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:11 +0000 Subject: [PATCH 17/29] :arrow_up: Set n8n-workflow@0.76.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 9ac21f0dec..8cdc3b2a87 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -50,7 +50,7 @@ "form-data": "^4.0.0", "lodash.get": "^4.4.2", "mime-types": "^2.1.27", - "n8n-workflow": "~0.75.0", + "n8n-workflow": "~0.76.0", "oauth-1.0a": "^2.2.6", "p-cancelable": "^2.0.0", "qs": "^6.10.1", From 86c234f55761af766d49adbb30a3b117577d56ee Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:11 +0000 Subject: [PATCH 18/29] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-core@0.93.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 8cdc3b2a87..dfc9c18a51 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.92.0", + "version": "0.93.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From f9ba3fa1d547146c71669aaaa46535a1ffab4862 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:19 +0000 Subject: [PATCH 19/29] :arrow_up: Set n8n-core@0.93.0 and n8n-workflow@0.76.0 on n8n-node-dev --- packages/node-dev/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index baed211bb4..ec7dd4ab92 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -60,8 +60,8 @@ "change-case": "^4.1.1", "copyfiles": "^2.1.1", "inquirer": "^7.0.1", - "n8n-core": "~0.92.0", - "n8n-workflow": "~0.75.0", + "n8n-core": "~0.93.0", + "n8n-workflow": "~0.76.0", "oauth-1.0a": "^2.2.6", "replace-in-file": "^6.0.0", "request": "^2.88.2", From ecb265b72faeaa22d8ea5ceac9966f3e04e3c729 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:19 +0000 Subject: [PATCH 20/29] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-node-dev@0.?= =?UTF-8?q?33.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 ec7dd4ab92..3c2de68f43 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.32.0", + "version": "0.33.0", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 96f178003cb3151c8adaee98add65bd7e6345ed6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:28 +0000 Subject: [PATCH 21/29] :arrow_up: Set n8n-core@0.93.0 and n8n-workflow@0.76.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 c51772500e..d4817e9baf 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -673,7 +673,7 @@ "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^26.4.2", - "n8n-workflow": "~0.75.0", + "n8n-workflow": "~0.76.0", "nodelinter": "^0.1.9", "ts-jest": "^26.3.0", "tslint": "^6.1.2", @@ -713,7 +713,7 @@ "mqtt": "4.2.6", "mssql": "^6.2.0", "mysql2": "~2.3.0", - "n8n-core": "~0.92.0", + "n8n-core": "~0.93.0", "node-ssh": "^12.0.0", "nodemailer": "^6.5.0", "pdf-parse": "^1.1.1", From f3c27cf506314162580a3093ae2fc1e48153ab87 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:29 +0000 Subject: [PATCH 22/29] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-nodes-base@?= =?UTF-8?q?0.146.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 d4817e9baf..b17229cd90 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.145.0", + "version": "0.146.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From e887aeea957a15ef95bb717b8e6aca86c679500e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:12:06 +0000 Subject: [PATCH 23/29] :arrow_up: Set n8n-workflow@0.76.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 35a98903e2..ea8d0ea2fd 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -71,7 +71,7 @@ "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "n8n-workflow": "~0.75.0", + "n8n-workflow": "~0.76.0", "sass": "^1.26.5", "normalize-wheel": "^1.0.1", "prismjs": "^1.17.1", From bfaa2634bc7a86348a7221e434434e2bc7acd2b1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:12:06 +0000 Subject: [PATCH 24/29] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-editor-ui@0?= =?UTF-8?q?.116.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 ea8d0ea2fd..93be8548eb 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.115.0", + "version": "0.116.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 3ecd78dd29bc0c86aec10cabac4381ad77245248 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:12:39 +0000 Subject: [PATCH 25/29] :arrow_up: Set n8n-core@0.93.0, n8n-editor-ui@0.116.0, n8n-nodes-base@0.146.0 and n8n-workflow@0.76.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 29cee52fde..2b0c4eace3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -110,10 +110,10 @@ "localtunnel": "^2.0.0", "lodash.get": "^4.4.2", "mysql2": "~2.3.0", - "n8n-core": "~0.92.0", - "n8n-editor-ui": "~0.115.0", - "n8n-nodes-base": "~0.145.0", - "n8n-workflow": "~0.75.0", + "n8n-core": "~0.93.0", + "n8n-editor-ui": "~0.116.0", + "n8n-nodes-base": "~0.146.0", + "n8n-workflow": "~0.76.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", "pg": "^8.3.0", From dec81a171a7b90eecbe84eab01cb75f578c6c754 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:12:39 +0000 Subject: [PATCH 26/29] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n@0.149.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 2b0c4eace3..070ab83f9f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.148.0", + "version": "0.149.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 7a37f73eaed32e88123dfcc48847f42b1cfcf193 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 14 Nov 2021 00:11:50 +0100 Subject: [PATCH 27/29] :bug: Improve expression security --- packages/workflow/src/Expression.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/workflow/src/Expression.ts b/packages/workflow/src/Expression.ts index 2b65ef03b8..b7fd4d0138 100644 --- a/packages/workflow/src/Expression.ts +++ b/packages/workflow/src/Expression.ts @@ -112,6 +112,9 @@ export class Expression { versions: process.versions, }; + // @ts-ignore + data.document = {}; + // Execute the expression try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call From 9f7113c94b2ccda78ea12611bbfadd226d4178df Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Mon, 15 Nov 2021 17:20:28 +0100 Subject: [PATCH 28/29] :bug: Remove default headers for PUT and PATCH (#2434) --- packages/core/src/NodeExecuteFunctions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 7fcf724401..fa1894a1da 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -87,6 +87,8 @@ import { axios.defaults.timeout = 300000; // Prevent axios from adding x-form-www-urlencoded headers by default axios.defaults.headers.post = {}; +axios.defaults.headers.put = {}; +axios.defaults.headers.patch = {}; axios.defaults.paramsSerializer = (params) => { if (params instanceof URLSearchParams) { return params.toString(); From 0022c7eb099374eb0b9346bfe8539a05d7324507 Mon Sep 17 00:00:00 2001 From: Oliver Trajceski Date: Mon, 15 Nov 2021 17:31:00 +0100 Subject: [PATCH 29/29] :bug: Fix issue that Start-Node did not get reset (#2425) * N8N-2549 Editor UI - Disabled start node when reseting new workflow * N8N-2549 Editor UI - Disabled start node when reseting new workflow, reseting the position of the default node * N8N-2549 Updated Editor-ui - Resetting Default Node (disable, position and all props) when reseting new workflow * N8N-2549 Remove comment --- packages/editor-ui/src/views/NodeView.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 3e6abff170..b8b5ad7d37 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -1607,7 +1607,9 @@ export default mixins( await this.$store.dispatch('workflows/setNewWorkflowName'); this.$store.commit('setStateDirty', false); - await this.addNodes([DEFAULT_START_NODE]); + const nodes = [{...DEFAULT_START_NODE}]; + + await this.addNodes(nodes); this.$store.commit('setStateDirty', false); this.setZoomLevel(1);