From 2ee5a16d10bf8defd52800823feeb288f62687f6 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 17 Jan 2020 12:34:36 -0500 Subject: [PATCH] :sparkles: webflow trigger --- .../credentials/WebflowApi.credentials.ts | 17 ++ .../nodes/Webflow/GenericFunctions.ts | 38 ++++ .../nodes/Webflow/WebflowTrigger.node.ts | 171 ++++++++++++++++++ packages/nodes-base/nodes/Webflow/webflow.png | Bin 0 -> 3932 bytes packages/nodes-base/package.json | 2 + 5 files changed, 228 insertions(+) create mode 100644 packages/nodes-base/credentials/WebflowApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Webflow/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts create mode 100644 packages/nodes-base/nodes/Webflow/webflow.png diff --git a/packages/nodes-base/credentials/WebflowApi.credentials.ts b/packages/nodes-base/credentials/WebflowApi.credentials.ts new file mode 100644 index 0000000000..8ab874b449 --- /dev/null +++ b/packages/nodes-base/credentials/WebflowApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class WebflowApi implements ICredentialType { + name = 'webflowApi'; + displayName = 'Webflow API'; + properties = [ + { + displayName: 'Access Token', + name: 'accessToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Webflow/GenericFunctions.ts b/packages/nodes-base/nodes/Webflow/GenericFunctions.ts new file mode 100644 index 0000000000..7531cd0334 --- /dev/null +++ b/packages/nodes-base/nodes/Webflow/GenericFunctions.ts @@ -0,0 +1,38 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function webflowApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('webflowApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + let options: OptionsWithUri = { + headers: { + authorization: `Bearer ${credentials.accessToken}`, + 'accept-version': '1.0.0', + }, + method, + qs, + body, + uri: uri ||`https://api.webflow.com${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('Webflow Error: ' + error.message); + } +} diff --git a/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts new file mode 100644 index 0000000000..9952c256a7 --- /dev/null +++ b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts @@ -0,0 +1,171 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeTypeDescription, + INodeType, + IWebhookResponseData, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; + +import { + webflowApiRequest, +} from './GenericFunctions'; + +export class WebflowTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Webflow Trigger', + name: 'webflow', + icon: 'file:webflow.png', + group: ['trigger'], + version: 1, + description: 'Handle Webflow events via webhooks', + defaults: { + name: 'Webflow Trigger', + color: '#245bf8', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'webflowApi', + required: true, + } + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Site', + name: 'site', + type: 'options', + required: true, + default: '', + typeOptions: { + loadOptionsMethod: 'getSites', + }, + description: 'Site that will trigger the events', + }, + { + displayName: 'Event', + name: 'event', + type: 'options', + required: true, + options: [ + { + name: 'Form submission', + value: 'form_submission', + }, + { + name: 'Site Publish', + value: 'site_publish', + }, + { + name: 'Ecomm New Order', + value: 'ecomm_new_order', + }, + { + name: 'Ecomm Order Changed', + value: 'ecomm_order_changed', + }, + { + name: 'Ecomm Inventory Changed', + value: 'ecomm_inventory_changed', + }, + ], + default: 'form_submission', + }, + ], + }; + + methods = { + loadOptions: { + // Get all the sites to display them to user so that he can + // select them easily + async getSites(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const sites = await webflowApiRequest.call(this, 'GET', '/sites'); + for (const site of sites) { + const siteName = site.name; + const siteId = site._id; + returnData.push({ + name: siteName, + value: siteId, + }); + } + return returnData; + }, + }, + } + + // @ts-ignore + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + const siteId = this.getNodeParameter('site') as string; + if (webhookData.webhookId === undefined) { + return false; + } + const endpoint = `/sites/${siteId}/webhooks/${webhookData.webhookId}`; + try { + await webflowApiRequest.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 siteId = this.getNodeParameter('site') as string; + const event = this.getNodeParameter('event') as string; + const endpoint = `/sites/${siteId}/webhooks`; + const body: IDataObject = { + site_id: siteId, + triggerType: event, + url: webhookUrl, + + }; + const { _id } = await webflowApiRequest.call(this, 'POST', endpoint, body); + webhookData.webhookId = _id; + return true; + }, + async delete(this: IHookFunctions): Promise { + let responseData; + const webhookData = this.getWorkflowStaticData('node'); + const siteId = this.getNodeParameter('site') as string; + const endpoint = `/sites/${siteId}/webhooks/${webhookData.webhookId}`; + try { + responseData = await webflowApiRequest.call(this, 'DELETE', endpoint); + } catch(error) { + return false; + } + if (!responseData.deleted) { + 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/Webflow/webflow.png b/packages/nodes-base/nodes/Webflow/webflow.png new file mode 100644 index 0000000000000000000000000000000000000000..e9b3be66b33b0b2210bafb2006eba4ae0fd66f93 GIT binary patch literal 3932 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 aedi&1Ov96-b0hZr4XdN2uUVmvj`$C}|1&`V literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index cbecd9e9ef..cee23f6223 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -81,6 +81,7 @@ "dist/credentials/TogglApi.credentials.js", "dist/credentials/VeroApi.credentials.js", "dist/credentials/WordpressApi.credentials.js", + "dist/credentials/WebflowApi.credentials.js", "dist/credentials/ZendeskApi.credentials.js" ], "nodes": [ @@ -178,6 +179,7 @@ "dist/nodes/WriteBinaryFile.node.js", "dist/nodes/Webhook.node.js", "dist/nodes/Wordpress/Wordpress.node.js", + "dist/nodes/Webflow/WebflowTrigger.node.js", "dist/nodes/Xml.node.js", "dist/nodes/Zendesk/ZendeskTrigger.node.js", "dist/nodes/Zendesk/Zendesk.node.js"