From 83319c30dc14bbee00c9d4ad7c5e98bbd0c0b859 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 17 Jan 2020 17:48:54 -0500 Subject: [PATCH 1/6] :sparkles: copper trigger --- .../credentials/CopperApi.credentials.ts | 31 ++++ .../nodes/Copper/CopperTrigger.node.ts | 162 ++++++++++++++++++ .../nodes/Copper/GenericFunctions.ts | 44 +++++ packages/nodes-base/nodes/Copper/copper.png | Bin 0 -> 8023 bytes packages/nodes-base/package.json | 2 + 5 files changed, 239 insertions(+) create mode 100644 packages/nodes-base/credentials/CopperApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Copper/CopperTrigger.node.ts create mode 100644 packages/nodes-base/nodes/Copper/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Copper/copper.png diff --git a/packages/nodes-base/credentials/CopperApi.credentials.ts b/packages/nodes-base/credentials/CopperApi.credentials.ts new file mode 100644 index 0000000000..12d2f75096 --- /dev/null +++ b/packages/nodes-base/credentials/CopperApi.credentials.ts @@ -0,0 +1,31 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class CopperApi implements ICredentialType { + name = 'copperApi'; + displayName = 'Copper API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + required: true, + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Email', + name: 'email', + required: true, + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Secret', + name: 'secret', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts b/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts new file mode 100644 index 0000000000..bb16ca6834 --- /dev/null +++ b/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts @@ -0,0 +1,162 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeTypeDescription, + INodeType, + IWebhookResponseData, +} from 'n8n-workflow'; + +import { + copperApiRequest, +} from './GenericFunctions'; + +export class CopperTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Copper Trigger', + name: 'copper', + icon: 'file:copper.png', + group: ['trigger'], + version: 1, + description: 'Handle Copper events via webhooks', + defaults: { + name: 'Copper Trigger', + color: '#ff2564', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'copperApi', + required: true, + } + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + required: true, + default: '', + options: [ + { + name: 'Lead', + value: 'lead', + }, + { + name: 'Person', + value: 'person', + }, + { + name: 'Company', + value: 'company', + }, + { + name: 'Opportunity', + value: 'opportunity', + }, + { + name: 'Project', + value: 'project', + }, + ], + description: 'The resource is gonna fire the event', + }, + { + displayName: 'Event', + name: 'event', + type: 'options', + required: true, + default: '', + options: [ + { + name: 'New', + value: 'new', + description: 'A new record is created', + }, + { + name: 'Update', + value: 'update', + description: 'Any field in the existing entity record is changed', + }, + { + name: 'Delete', + value: 'delete', + description: 'An existing record is removed', + }, + ], + description: 'The resource is gonna fire the event', + }, + ], + }; + // @ts-ignore + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + if (webhookData.webhookId === undefined) { + return false; + } + const endpoint = `/webhooks/${webhookData.webhookId}`; + try { + await copperApiRequest.call(this, 'GET', endpoint); + } catch (err) { + return false + } + return true; + }, + async create(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + const webhookData = this.getWorkflowStaticData('node'); + const resource = this.getNodeParameter('resource') as string; + const event = this.getNodeParameter('event') as string; + const endpoint = '/webhooks'; + const body: IDataObject = { + target: webhookUrl, + type: resource, + event: event, + }; + const { id } = await copperApiRequest.call(this, 'POST', endpoint, body); + webhookData.webhookId = id; + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + const endpoint = `/webhooks/${webhookData.webhookId}`; + try { + await copperApiRequest.call(this, 'DELETE', endpoint); + } catch(error) { + return false; + } + delete webhookData.webhookId; + return true; + }, + }, + }; + + async webhook(this: IWebhookFunctions): Promise { + const credentials = this.getCredentials('copperApi'); + const req = this.getRequestObject(); + if (credentials!.secret) { + if (req.body.secret !== credentials!.secret) { + return {}; + }; + } + return { + workflowData: [ + this.helpers.returnJsonArray(req.body), + ], + }; + } +} diff --git a/packages/nodes-base/nodes/Copper/GenericFunctions.ts b/packages/nodes-base/nodes/Copper/GenericFunctions.ts new file mode 100644 index 0000000000..7c7c5d5967 --- /dev/null +++ b/packages/nodes-base/nodes/Copper/GenericFunctions.ts @@ -0,0 +1,44 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function copperApiRequest(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('copperApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + if (credentials.secret) { + body.secret = { + secret: credentials.secret as string, + }; + }; + let options: OptionsWithUri = { + headers: { + 'X-PW-AccessToken': credentials.apiKey, + 'X-PW-Application': 'developer_api', + 'X-PW-UserEmail': credentials.email, + 'Content-Type': 'application/json', + }, + method, + qs, + body, + uri: uri ||`https://api.prosperworks.com/developer_api/v1${resource}`, + 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 (error) { + throw new Error('Zoom Error: ' + error.message); + } +} diff --git a/packages/nodes-base/nodes/Copper/copper.png b/packages/nodes-base/nodes/Copper/copper.png new file mode 100644 index 0000000000000000000000000000000000000000..befa65c1810d62f9c51fdf304e78c9bf8cc83114 GIT binary patch literal 8023 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 Date: Fri, 17 Jan 2020 17:50:22 -0500 Subject: [PATCH 2/6] :zap: small fix --- packages/nodes-base/nodes/Copper/GenericFunctions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Copper/GenericFunctions.ts b/packages/nodes-base/nodes/Copper/GenericFunctions.ts index 7c7c5d5967..48222181f4 100644 --- a/packages/nodes-base/nodes/Copper/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Copper/GenericFunctions.ts @@ -39,6 +39,6 @@ export async function copperApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - throw new Error('Zoom Error: ' + error.message); + throw new Error('Copper Error: ' + error.message); } } From c08c92abd4e745c7dbd7802f8146ec1cb3e68845 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 17 Jan 2020 21:08:29 -0500 Subject: [PATCH 3/6] :sparkles: acuity scheduling trigger --- .../AcuitySchedulingApi.credentials.ts | 23 +++ .../AcuitySchedulingTrigger.node.ts | 136 ++++++++++++++++++ .../AcuityScheduling/GenericFunctions.ts | 34 +++++ .../AcuityScheduling/acuityScheduling.png | Bin 0 -> 5387 bytes packages/nodes-base/package.json | 2 + 5 files changed, 195 insertions(+) create mode 100644 packages/nodes-base/credentials/AcuitySchedulingApi.credentials.ts create mode 100644 packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts create mode 100644 packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/AcuityScheduling/acuityScheduling.png diff --git a/packages/nodes-base/credentials/AcuitySchedulingApi.credentials.ts b/packages/nodes-base/credentials/AcuitySchedulingApi.credentials.ts new file mode 100644 index 0000000000..5ee2344ef1 --- /dev/null +++ b/packages/nodes-base/credentials/AcuitySchedulingApi.credentials.ts @@ -0,0 +1,23 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class AcuitySchedulingApi implements ICredentialType { + name = 'acuitySchedulingApi'; + displayName = 'Acuity Scheduling API'; + properties = [ + { + displayName: 'User ID', + name: 'userId', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts b/packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts new file mode 100644 index 0000000000..dc9a9b072d --- /dev/null +++ b/packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts @@ -0,0 +1,136 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeTypeDescription, + INodeType, + IWebhookResponseData, +} from 'n8n-workflow'; + +import { + acuitySchedulingApiRequest, +} from './GenericFunctions'; + +export class AcuitySchedulingTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Acuity Scheduling Trigger', + name: 'acuityScheduling', + icon: 'file:acuityScheduling.png', + group: ['trigger'], + version: 1, + description: 'Handle Acuity Scheduling events via webhooks', + defaults: { + name: 'Acuity Scheduling Trigger', + color: '#000000', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'acuitySchedulingApi', + required: true, + } + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Event', + name: 'event', + type: 'options', + required: true, + default: '', + options: [ + { + name: 'appointment.scheduled', + value: 'appointment.scheduled', + description: 'is called once when an appointment is initially booked', + }, + { + name: 'appointment.rescheduled', + value: 'appointment.rescheduled', + description: 'is called when the appointment is rescheduled to a new time', + }, + { + name: 'appointment.canceled', + value: 'appointment.canceled', + description: 'is called whenever an appointment is canceled', + }, + { + name: 'appointment.changed', + value: 'appointment.changed', + description: 'is called when the appointment is changed in any way', + }, + { + name: 'order.completed', + value: 'order.completed', + description: 'is called when an order is completed', + }, + ], + }, + ], + }; + // @ts-ignore + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + if (webhookData.webhookId === undefined) { + return false; + } + const endpoint = '/webhooks'; + const webhooks = await acuitySchedulingApiRequest.call(this, 'GET', endpoint); + if (Array.isArray(webhooks)) { + for (const webhook of webhooks) { + if (webhook.id === webhookData.webhookId) { + return true; + } + } + } + return false; + }, + async create(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + const webhookData = this.getWorkflowStaticData('node'); + const event = this.getNodeParameter('event') as string; + const endpoint = '/webhooks'; + const body: IDataObject = { + target: webhookUrl, + event, + }; + const { id } = await acuitySchedulingApiRequest.call(this, 'POST', endpoint, body); + webhookData.webhookId = id; + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + const endpoint = `/webhooks/${webhookData.webhookId}`; + try { + await acuitySchedulingApiRequest.call(this, 'DELETE', endpoint); + } catch(error) { + return false; + } + delete webhookData.webhookId; + return true; + }, + }, + }; + + async webhook(this: IWebhookFunctions): Promise { + const req = this.getRequestObject(); + return { + workflowData: [ + this.helpers.returnJsonArray(req.body), + ], + }; + } +} diff --git a/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts b/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts new file mode 100644 index 0000000000..e14ff589d5 --- /dev/null +++ b/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts @@ -0,0 +1,34 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function acuitySchedulingApiRequest(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('acuitySchedulingApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const base64Key = Buffer.from(`${credentials.userId}:${credentials.apiKey}`).toString('base64'); + let options: OptionsWithUri = { + headers: { + Authorization: `Basic ${base64Key}`, + 'Content-Type': 'application/json', + }, + method, + qs, + body, + uri: uri ||`https://acuityscheduling.com/api/v1${resource}`, + json: true + }; + try { + return await this.helpers.request!(options); + } catch (error) { + throw new Error('Acuity Scheduling Error: ' + error.message); + } +} diff --git a/packages/nodes-base/nodes/AcuityScheduling/acuityScheduling.png b/packages/nodes-base/nodes/AcuityScheduling/acuityScheduling.png new file mode 100644 index 0000000000000000000000000000000000000000..df087b60e8972d8b79430e0735cffe0b0b6649df GIT binary patch literal 5387 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 literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 8e565d7870..bab965cafa 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -27,6 +27,7 @@ "n8n": { "credentials": [ "dist/credentials/ActiveCampaignApi.credentials.js", + "dist/credentials/AcuitySchedulingApi.credentials.js", "dist/credentials/AirtableApi.credentials.js", "dist/credentials/Amqp.credentials.js", "dist/credentials/AsanaApi.credentials.js", @@ -87,6 +88,7 @@ "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", "dist/nodes/ActiveCampaign/ActiveCampaignTrigger.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", From 6808c51c0abbaabfd3f0f444516f93afb7d116d6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 17 Jan 2020 22:41:12 -0600 Subject: [PATCH 4/6] :bug: Fix Webflow-Trigger node name --- packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts index 53a0a75302..709b9858cd 100644 --- a/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts +++ b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts @@ -19,7 +19,7 @@ import { export class WebflowTrigger implements INodeType { description: INodeTypeDescription = { displayName: 'Webflow Trigger', - name: 'webflow', + name: 'webflowTrigger', icon: 'file:webflow.png', group: ['trigger'], version: 1, From 6b45d79d362e6ef848cf74e7b5992fa5d08f9ea3 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 18 Jan 2020 09:28:19 -0600 Subject: [PATCH 5/6] :zap: Minor improvements to CopperTrigger-Node --- .../credentials/CopperApi.credentials.ts | 6 -- .../nodes/Copper/CopperTrigger.node.ts | 56 +++++++++++-------- .../nodes/Copper/GenericFunctions.ts | 33 ++++++++--- 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/packages/nodes-base/credentials/CopperApi.credentials.ts b/packages/nodes-base/credentials/CopperApi.credentials.ts index 12d2f75096..ea3105054d 100644 --- a/packages/nodes-base/credentials/CopperApi.credentials.ts +++ b/packages/nodes-base/credentials/CopperApi.credentials.ts @@ -21,11 +21,5 @@ export class CopperApi implements ICredentialType { type: 'string' as NodePropertyTypes, default: '', }, - { - displayName: 'Secret', - name: 'secret', - type: 'string' as NodePropertyTypes, - default: '', - }, ]; } diff --git a/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts b/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts index bb16ca6834..2c95c25ebf 100644 --- a/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts +++ b/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts @@ -12,12 +12,13 @@ import { import { copperApiRequest, + getAutomaticSecret, } from './GenericFunctions'; export class CopperTrigger implements INodeType { description: INodeTypeDescription = { displayName: 'Copper Trigger', - name: 'copper', + name: 'copperTrigger', icon: 'file:copper.png', group: ['trigger'], version: 1, @@ -50,28 +51,32 @@ export class CopperTrigger implements INodeType { required: true, default: '', options: [ - { - name: 'Lead', - value: 'lead', - }, - { - name: 'Person', - value: 'person', - }, { name: 'Company', value: 'company', }, + { + name: 'Lead', + value: 'lead', + }, { name: 'Opportunity', value: 'opportunity', }, + { + name: 'Person', + value: 'person', + }, { name: 'Project', value: 'project', }, + { + name: 'Task', + value: 'task', + }, ], - description: 'The resource is gonna fire the event', + description: 'The resource which will fire the event.', }, { displayName: 'Event', @@ -80,6 +85,11 @@ export class CopperTrigger implements INodeType { required: true, default: '', options: [ + { + name: 'Delete', + value: 'delete', + description: 'An existing record is removed', + }, { name: 'New', value: 'new', @@ -90,13 +100,8 @@ export class CopperTrigger implements INodeType { value: 'update', description: 'Any field in the existing entity record is changed', }, - { - name: 'Delete', - value: 'delete', - description: 'An existing record is removed', - }, ], - description: 'The resource is gonna fire the event', + description: 'The event to listen to.', }, ], }; @@ -112,7 +117,7 @@ export class CopperTrigger implements INodeType { try { await copperApiRequest.call(this, 'GET', endpoint); } catch (err) { - return false + return false; } return true; }, @@ -125,8 +130,14 @@ export class CopperTrigger implements INodeType { const body: IDataObject = { target: webhookUrl, type: resource, - event: event, + event, }; + + const credentials = this.getCredentials('copperApi'); + body.secret = { + secret: getAutomaticSecret(credentials!), + }; + const { id } = await copperApiRequest.call(this, 'POST', endpoint, body); webhookData.webhookId = id; return true; @@ -148,11 +159,12 @@ export class CopperTrigger implements INodeType { async webhook(this: IWebhookFunctions): Promise { const credentials = this.getCredentials('copperApi'); const req = this.getRequestObject(); - if (credentials!.secret) { - if (req.body.secret !== credentials!.secret) { - return {}; - }; + + // Check if the supplied secret matches. If not ignore request. + if (req.body.secret !== getAutomaticSecret(credentials!)) { + return {}; } + return { workflowData: [ this.helpers.returnJsonArray(req.body), diff --git a/packages/nodes-base/nodes/Copper/GenericFunctions.ts b/packages/nodes-base/nodes/Copper/GenericFunctions.ts index 48222181f4..31bd1379c2 100644 --- a/packages/nodes-base/nodes/Copper/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Copper/GenericFunctions.ts @@ -1,4 +1,6 @@ +import { createHash } from 'crypto'; import { OptionsWithUri } from 'request'; + import { IExecuteFunctions, IExecuteSingleFunctions, @@ -6,18 +8,17 @@ import { ILoadOptionsFunctions, IWebhookFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { + ICredentialDataDecryptedObject, + IDataObject, +} from 'n8n-workflow'; export async function copperApiRequest(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('copperApi'); if (credentials === undefined) { throw new Error('No credentials got returned!'); } - if (credentials.secret) { - body.secret = { - secret: credentials.secret as string, - }; - }; + let options: OptionsWithUri = { headers: { 'X-PW-AccessToken': credentials.apiKey, @@ -39,6 +40,24 @@ export async function copperApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - throw new Error('Copper Error: ' + error.message); + let errorMessage = error.message; + if (error.response.body && error.response.body.message) { + errorMessage = error.response.body.message; + } + + throw new Error('Copper Error: ' + errorMessage); } } + + +/** + * Creates a secret from the credentials + * + * @export + * @param {ICredentialDataDecryptedObject} credentials + * @returns + */ +export function getAutomaticSecret(credentials: ICredentialDataDecryptedObject) { + const data = `${credentials.email},${credentials.apiKey}`; + return createHash('md5').update(data).digest("hex"); +} From c5ad3eecca86fd2455e91c44af93e611fa0cafc9 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 18 Jan 2020 11:10:46 -0600 Subject: [PATCH 6/6] :zap: Minor improvements to AcuityScheduling-Node --- .../AcuitySchedulingTrigger.node.ts | 31 +++++++++++++++++-- .../AcuityScheduling/GenericFunctions.ts | 16 +++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts b/packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts index dc9a9b072d..2166b5cc85 100644 --- a/packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts +++ b/packages/nodes-base/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.ts @@ -17,7 +17,7 @@ import { export class AcuitySchedulingTrigger implements INodeType { description: INodeTypeDescription = { displayName: 'Acuity Scheduling Trigger', - name: 'acuityScheduling', + name: 'acuitySchedulingTrigger', icon: 'file:acuityScheduling.png', group: ['trigger'], version: 1, @@ -77,6 +77,13 @@ export class AcuitySchedulingTrigger implements INodeType { }, ], }, + { + displayName: 'Resolve Data', + name: 'resolveData', + type: 'boolean', + default: true, + description: 'By default does the webhook-data only contain the ID of the object.
If this option gets activated it will resolve the data automatically.', + }, ], }; // @ts-ignore @@ -127,10 +134,30 @@ export class AcuitySchedulingTrigger implements INodeType { async webhook(this: IWebhookFunctions): Promise { const req = this.getRequestObject(); + + const resolveData = this.getNodeParameter('resolveData', false) as boolean; + + if (resolveData === false) { + // Return the data as it got received + return { + workflowData: [ + this.helpers.returnJsonArray(req.body), + ], + }; + } + + // Resolve the data by requesting the information via API + const event = this.getNodeParameter('event', false) as string; + const eventType = event.split('.').shift(); + const endpoint = `/${eventType}s/${req.body.id}`; + const responseData = await acuitySchedulingApiRequest.call(this, 'GET', endpoint, {}); + return { workflowData: [ - this.helpers.returnJsonArray(req.body), + this.helpers.returnJsonArray(responseData), ], }; + + } } diff --git a/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts b/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts index e14ff589d5..9a2d0fc597 100644 --- a/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts +++ b/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts @@ -14,12 +14,14 @@ export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecute throw new Error('No credentials got returned!'); } - const base64Key = Buffer.from(`${credentials.userId}:${credentials.apiKey}`).toString('base64'); - let options: OptionsWithUri = { + const options: OptionsWithUri = { headers: { - Authorization: `Basic ${base64Key}`, 'Content-Type': 'application/json', }, + auth: { + user: credentials.userId as string, + password: credentials.apiKey as string, + }, method, qs, body, @@ -29,6 +31,12 @@ export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecute try { return await this.helpers.request!(options); } catch (error) { - throw new Error('Acuity Scheduling Error: ' + error.message); + + let errorMessage = error.message; + if (error.response.body && error.response.body.message) { + errorMessage = `[${error.response.body.status_code}] ${error.response.body.message}`; + } + + throw new Error('Acuity Scheduling Error: ' + errorMessage); } }