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 000000000..c405940a7 Binary files /dev/null and b/packages/nodes-base/nodes/Bannerbear/bannerbear.png differ 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",