From c08c92abd4e745c7dbd7802f8146ec1cb3e68845 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 17 Jan 2020 21:08:29 -0500 Subject: [PATCH] :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",