diff --git a/packages/nodes-base/credentials/CalendlyApi.credentials.ts b/packages/nodes-base/credentials/CalendlyApi.credentials.ts new file mode 100644 index 0000000000..5a659c4a32 --- /dev/null +++ b/packages/nodes-base/credentials/CalendlyApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class CalendlyApi implements ICredentialType { + name = 'calendlyApi'; + displayName = 'Calendly API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts b/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts new file mode 100644 index 0000000000..29a8033809 --- /dev/null +++ b/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts @@ -0,0 +1,147 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + INodeTypeDescription, + INodeType, + IWebhookResponseData, +} from 'n8n-workflow'; + +import { + calendlyApiRequest, +} from './GenericFunctions'; + +export class CalendlyTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Calendly Trigger', + name: 'calendlyTrigger', + icon: 'file:calendly.png', + group: ['trigger'], + version: 1, + description: 'Starts the workflow when Calendly events occure.', + defaults: { + name: 'Calendly Trigger', + color: '#374252', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'calendlyApi', + required: true, + }, + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Events', + name: 'events', + type: 'multiOptions', + options: [ + { + name: 'invitee.created', + value: 'invitee.created', + description: 'Receive notifications when a new Calendly event is created', + }, + { + name: 'invitee.canceled', + value: 'invitee.canceled', + description: 'Receive notifications when a Calendly event is canceled', + }, + ], + default: [], + required: true, + }, + ], + + }; + + // @ts-ignore (because of request) + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + const webhookData = this.getWorkflowStaticData('node'); + const events = this.getNodeParameter('events') as string; + + // Check all the webhooks which exist already if it is identical to the + // one that is supposed to get created. + const endpoint = '/hooks'; + const { data } = await calendlyApiRequest.call(this, 'GET', endpoint, {}); + + for (const webhook of data) { + if (webhook.attributes.url === webhookUrl) { + for (const event of events) { + if (!webhook.attributes.events.includes(event)) { + return false; + } + } + } + // Set webhook-id to be sure that it can be deleted + webhookData.webhookId = webhook.id as string; + return true; + } + return false; + }, + async create(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + const webhookUrl = this.getNodeWebhookUrl('default'); + const events = this.getNodeParameter('events') as string; + + const endpoint = '/hooks'; + + const body = { + url: webhookUrl, + events, + }; + + const responseData = await calendlyApiRequest.call(this, 'POST', endpoint, body); + + if (responseData.id === undefined) { + // Required data is missing so was not successful + return false; + } + + webhookData.webhookId = responseData.id as string; + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + console.log(webhookData) + if (webhookData.webhookId !== undefined) { + + const endpoint = `/hooks/${webhookData.webhookId}`; + + try { + await calendlyApiRequest.call(this, 'DELETE', endpoint); + } catch (e) { + return false; + } + + // Remove from the static workflow data so that it is clear + // that no webhooks are registred anymore + delete webhookData.webhookId; + } + return true; + }, + }, + }; + + async webhook(this: IWebhookFunctions): Promise { + const bodyData = this.getBodyData(); + return { + workflowData: [ + this.helpers.returnJsonArray(bodyData), + ], + }; + } +} diff --git a/packages/nodes-base/nodes/Calendly/GenericFunctions.ts b/packages/nodes-base/nodes/Calendly/GenericFunctions.ts new file mode 100644 index 0000000000..0d4c229d0b --- /dev/null +++ b/packages/nodes-base/nodes/Calendly/GenericFunctions.ts @@ -0,0 +1,51 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, + IHookFunctions, + IWebhookFunctions +} from 'n8n-workflow'; + +export async function calendlyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('calendlyApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const endpoint = 'https://calendly.com/api/v1'; + + let options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + 'X-TOKEN': credentials.apiKey, + }, + method, + body, + qs: query, + uri: uri || `${endpoint}${resource}`, + json: true + }; + if (!Object.keys(body).length) { + delete options.form; + } + if (!Object.keys(query).length) { + delete options.qs; + } + options = Object.assign({}, options, option); + try { + return await this.helpers.request!(options); + } catch (error) { + if (error.response) { + const errorMessage = error.response.body.message || error.response.body.description || error.message; + throw new Error(`Calendly error response [${error.statusCode}]: ${errorMessage}`); + } + throw error; + } +} diff --git a/packages/nodes-base/nodes/Calendly/calendly.png b/packages/nodes-base/nodes/Calendly/calendly.png new file mode 100644 index 0000000000..6527e03d94 Binary files /dev/null and b/packages/nodes-base/nodes/Calendly/calendly.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index ae32071021..28bb58ed82 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -39,7 +39,8 @@ "dist/credentials/ClickUpApi.credentials.js", "dist/credentials/ClockifyApi.credentials.js", "dist/credentials/CodaApi.credentials.js", - "dist/credentials/CopperApi.credentials.js", + "dist/credentials/CopperApi.credentials.js", + "dist/credentials/CalendlyApi.credentials.js", "dist/credentials/DisqusApi.credentials.js", "dist/credentials/DriftApi.credentials.js", "dist/credentials/DropboxApi.credentials.js", @@ -116,7 +117,8 @@ "dist/nodes/Aws/AwsSns.node.js", "dist/nodes/Aws/AwsSnsTrigger.node.js", "dist/nodes/Bitbucket/BitbucketTrigger.node.js", - "dist/nodes/Bitly/Bitly.node.js", + "dist/nodes/Bitly/Bitly.node.js", + "dist/nodes/Calendly/CalendlyTrigger.node.js", "dist/nodes/Chargebee/Chargebee.node.js", "dist/nodes/Chargebee/ChargebeeTrigger.node.js", "dist/nodes/Clearbit/Clearbit.node.js",