From 413edbefda7dfb56375d2c5aed224932c8e3cb05 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 26 Apr 2020 14:56:50 -0500 Subject: [PATCH] :sparkles: Bannerbear Node --- .../credentials/BannerbearApi.credentials.ts | 17 ++ .../nodes/Bannerbear/Bannerbear.node.ts | 159 ++++++++++++++++ .../nodes/Bannerbear/GenericFunctions.ts | 71 +++++++ .../nodes/Bannerbear/ImageDescription.ts | 177 ++++++++++++++++++ .../nodes/Bannerbear/TemplateDescription.ts | 57 ++++++ .../nodes/Bannerbear/bannerbear.png | Bin 0 -> 4615 bytes packages/nodes-base/package.json | 2 + 7 files changed, 483 insertions(+) create mode 100644 packages/nodes-base/credentials/BannerbearApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Bannerbear/Bannerbear.node.ts create mode 100644 packages/nodes-base/nodes/Bannerbear/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Bannerbear/ImageDescription.ts create mode 100644 packages/nodes-base/nodes/Bannerbear/TemplateDescription.ts create mode 100644 packages/nodes-base/nodes/Bannerbear/bannerbear.png diff --git a/packages/nodes-base/credentials/BannerbearApi.credentials.ts b/packages/nodes-base/credentials/BannerbearApi.credentials.ts new file mode 100644 index 000000000..81bb6b3f6 --- /dev/null +++ b/packages/nodes-base/credentials/BannerbearApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class BannerbearApi implements ICredentialType { + name = 'bannerbearApi'; + displayName = 'Bannerbear API'; + properties = [ + { + displayName: 'Project API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Bannerbear/Bannerbear.node.ts b/packages/nodes-base/nodes/Bannerbear/Bannerbear.node.ts new file mode 100644 index 000000000..456827490 --- /dev/null +++ b/packages/nodes-base/nodes/Bannerbear/Bannerbear.node.ts @@ -0,0 +1,159 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + keysToSnakeCase, + bannerbearApiRequest, +} from './GenericFunctions'; + +import { + imageFields, + imageOperations, +} from './ImageDescription'; + +import { + templateFields, + templateOperations, +} from './TemplateDescription'; + +export class Bannerbear implements INodeType { + description: INodeTypeDescription = { + displayName: 'Bannerbear', + name: 'bannerbear', + icon: 'file:bannerbear.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Bannerbear API', + defaults: { + name: 'Bannerbear', + color: '#f9d749', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'bannerbearApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Image', + value: 'image', + }, + { + name: 'Template', + value: 'template', + }, + ], + default: 'image', + description: 'Resource to consume.', + }, + // IMAGE + ...imageOperations, + ...imageFields, + // TEMPLATE + ...templateOperations, + ...templateFields, + ], + }; + + methods = { + loadOptions: { + // Get all the available escalation policies to display them to user so that he can + // select them easily + async getTemplates(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const templates = await bannerbearApiRequest.call(this, 'GET', '/templates'); + for (const template of templates) { + const templateName = template.name; + const templateId = template.uid; + returnData.push({ + name: templateName, + value: templateId, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + const qs: IDataObject = {}; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'image') { + //https://developers.bannerbear.com/#create-an-image + if (operation === 'create') { + const templateId = this.getNodeParameter('templateId', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const modifications = (this.getNodeParameter('modificationsUi', i) as IDataObject).modificationsValues as IDataObject; + const body: IDataObject = { + template: templateId, + }; + if (additionalFields.webhookUrl) { + body.webhook_url = additionalFields.webhookUrl as string; + } + if (additionalFields.metadata) { + body.metadata = additionalFields.metadata as string; + } + if (modifications) { + body.modifications = keysToSnakeCase(modifications) as IDataObject[]; + // delete all fields set to empty + for (const modification of body.modifications as IDataObject[]) { + for (const key of Object.keys(modification)) { + if (modification[key] === '') { + delete modification[key]; + } + } + } + } + responseData = await bannerbearApiRequest.call(this, 'POST', '/images', body); + } + //https://developers.bannerbear.com/#get-a-specific-image + if (operation === 'get') { + const imageId = this.getNodeParameter('imageId', i) as string; + responseData = await bannerbearApiRequest.call(this, 'GET', `/images/${imageId}`); + } + } + if (resource === 'template') { + //https://developers.bannerbear.com/#get-a-specific-template + if (operation === 'get') { + const templateId = this.getNodeParameter('templateId', i) as string; + responseData = await bannerbearApiRequest.call(this, 'GET', `/templates/${templateId}`); + } + //https://developers.bannerbear.com/#list-templates + if (operation === 'getAll') { + responseData = await bannerbearApiRequest.call(this, 'GET', '/templates'); + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Bannerbear/GenericFunctions.ts b/packages/nodes-base/nodes/Bannerbear/GenericFunctions.ts new file mode 100644 index 000000000..396c7e52e --- /dev/null +++ b/packages/nodes-base/nodes/Bannerbear/GenericFunctions.ts @@ -0,0 +1,71 @@ +import { + OptionsWithUri, + } from 'request'; + +import { + IExecuteFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, + IHookFunctions, + IWebhookFunctions, +} from 'n8n-workflow'; + +import { + snakeCase, +} from 'change-case'; + +export async function bannerbearApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('bannerbearApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const options: OptionsWithUri = { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${credentials.apiKey}`, + }, + method, + body, + qs: query, + uri: uri || `https://api.bannerbear.com/v2${resource}`, + json: true, + }; + if (!Object.keys(body).length) { + delete options.form; + } + if (!Object.keys(query).length) { + delete options.qs; + } + options.headers = Object.assign({}, options.headers, headers); + try { + return await this.helpers.request!(options); + } catch (error) { + if (error.response && error.response.body && error.response.body.message) { + // Try to return the error prettier + //@ts-ignore + throw new Error(`Bannerbear error response [${error.statusCode}]: ${error.response.body.message}`); + } + throw error; + } +} + +export function keysToSnakeCase(elements: IDataObject[] | IDataObject) : IDataObject[] { + if (!Array.isArray(elements)) { + elements = [elements]; + } + for (const element of elements) { + for (const key of Object.keys(element)) { + if (key !== snakeCase(key)) { + element[snakeCase(key)] = element[key]; + delete element[key]; + } + } + } + return elements; +} diff --git a/packages/nodes-base/nodes/Bannerbear/ImageDescription.ts b/packages/nodes-base/nodes/Bannerbear/ImageDescription.ts new file mode 100644 index 000000000..a387f57f9 --- /dev/null +++ b/packages/nodes-base/nodes/Bannerbear/ImageDescription.ts @@ -0,0 +1,177 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const imageOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'image', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create an image', + }, + { + name: 'Get', + value: 'get', + description: 'Get an image', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const imageFields = [ + +/* -------------------------------------------------------------------------- */ +/* image:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Template ID', + name: 'templateId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTemplates', + }, + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'image', + ], + operation: [ + 'create', + ], + }, + }, + description: 'The template id you want to use', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'image', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Metadata', + name: 'metadata', + type: 'string', + default: '', + description: 'Metadata that you need to store e.g. ID of a record in your DB', + }, + { + displayName: 'Webhook URL', + name: 'webhookUrl', + type: 'string', + default: '', + description: 'A url to POST the Image object to upon rendering completed', + }, + ], + }, + { + displayName: 'Modifications', + name: 'modificationsUi', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + placeholder: 'Add Modification', + displayOptions: { + show: { + resource: [ + 'image', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Modification', + name: 'modificationsValues', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of the item you want to change', + }, + { + displayName: 'Text', + name: 'text', + type: 'string', + default: '', + description: 'Replacement text you want to use', + }, + { + displayName: 'Color', + name: 'color', + type: 'color', + default: '', + description: 'Color hex of object', + }, + { + displayName: 'Background', + name: 'background', + type: 'color', + default: '', + description: 'Color hex of text background', + }, + { + displayName: 'Image URL', + name: 'imageUrl', + type: 'string', + default: '', + description: 'Replacement image url you want to use (must be publicly viewable)', + } + ], + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* image:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Image ID', + name: 'imageId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'image', + ], + operation: [ + 'get', + ] + }, + }, + description: 'Unique identifier for the image.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Bannerbear/TemplateDescription.ts b/packages/nodes-base/nodes/Bannerbear/TemplateDescription.ts new file mode 100644 index 000000000..692fb1a34 --- /dev/null +++ b/packages/nodes-base/nodes/Bannerbear/TemplateDescription.ts @@ -0,0 +1,57 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const templateOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'template', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get a template', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all templates', + }, + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const templateFields = [ + +/* -------------------------------------------------------------------------- */ +/* template:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Template ID', + name: 'templateId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'template', + ], + operation: [ + 'get', + ] + }, + }, + description: 'Unique identifier for the template.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Bannerbear/bannerbear.png b/packages/nodes-base/nodes/Bannerbear/bannerbear.png new file mode 100644 index 0000000000000000000000000000000000000000..c405940a78e44fc9a42a37b54199741b09738130 GIT binary patch literal 4615 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 BS114g literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 6edf38d2f..1b11be209 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -33,6 +33,7 @@ "dist/credentials/AsanaApi.credentials.js", "dist/credentials/Aws.credentials.js", "dist/credentials/AffinityApi.credentials.js", + "dist/credentials/BannerbearApi.credentials.js", "dist/credentials/BitbucketApi.credentials.js", "dist/credentials/BitlyApi.credentials.js", "dist/credentials/ChargebeeApi.credentials.js", @@ -125,6 +126,7 @@ "dist/nodes/Aws/AwsSes.node.js", "dist/nodes/Aws/AwsSns.node.js", "dist/nodes/Aws/AwsSnsTrigger.node.js", + "dist/nodes/Bannerbear/Bannerbear.node.js", "dist/nodes/Bitbucket/BitbucketTrigger.node.js", "dist/nodes/Bitly/Bitly.node.js", "dist/nodes/Calendly/CalendlyTrigger.node.js",