From 21ebf11db79e355e2cfc0fe6fdc822e6a8d93183 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 15 Jan 2020 08:55:46 -0500 Subject: [PATCH] :sparkles: mautic integration --- .../credentials/MauticApi.credentials.ts | 29 + .../nodes/Mautic/ContactDescription.ts | 549 ++++++++++++++++++ .../nodes/Mautic/GenericFunctions.ts | 79 +++ .../nodes-base/nodes/Mautic/Mautic.node.ts | 229 ++++++++ .../nodes/Mautic/MauticTrigger.node.ts | 156 +++++ packages/nodes-base/nodes/Mautic/mautic.png | Bin 0 -> 6791 bytes packages/nodes-base/package.json | 7 +- 7 files changed, 1047 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/MauticApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Mautic/ContactDescription.ts create mode 100644 packages/nodes-base/nodes/Mautic/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Mautic/Mautic.node.ts create mode 100644 packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts create mode 100644 packages/nodes-base/nodes/Mautic/mautic.png diff --git a/packages/nodes-base/credentials/MauticApi.credentials.ts b/packages/nodes-base/credentials/MauticApi.credentials.ts new file mode 100644 index 0000000000..28938b2443 --- /dev/null +++ b/packages/nodes-base/credentials/MauticApi.credentials.ts @@ -0,0 +1,29 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class MauticApi implements ICredentialType { + name = 'mauticApi'; + displayName = 'Mautic API'; + properties = [ + { + displayName: 'URL', + name: 'url', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Username', + name: 'username', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Password', + name: 'password', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Mautic/ContactDescription.ts b/packages/nodes-base/nodes/Mautic/ContactDescription.ts new file mode 100644 index 0000000000..092690e7d3 --- /dev/null +++ b/packages/nodes-base/nodes/Mautic/ContactDescription.ts @@ -0,0 +1,549 @@ +import { INodeProperties } from "n8n-workflow"; + +export const contactOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'contact', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new contact', + }, + { + name: 'Update', + value: 'update', + description: 'Update a contact', + }, + { + name: 'Get', + value: 'get', + description: 'Get data of a contact', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get data of all contacts', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a contact', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const contactFields = [ + +/* -------------------------------------------------------------------------- */ +/* contact:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'contact', + ], + }, + }, + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ] + }, + }, + default: '', + description: 'First Name', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + default: '', + description: 'LastName', + }, + { + displayName: 'Primary Company', + name: 'company', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCompanies', + }, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + default: '', + description: 'Primary company', + }, + { + displayName: 'Position', + name: 'position', + type: 'string', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + default: '', + description: 'Position', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + default: '', + description: 'Title', + }, + { + displayName: 'Body', + name: 'bodyJson', + type: 'json', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'contact', + ], + jsonParameters: [ + true, + ], + }, + }, + default: '', + description: 'Contact parameters', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'IP Address', + name: 'ipAddress', + type: 'string', + default: '', + description: 'IP address to associate with the contact', + }, + { + displayName: 'Last Active', + name: 'lastActive', + type: 'dateTime', + default: '', + description: 'Date/time in UTC;', + }, + { + displayName: 'Owner ID', + name: 'ownerId', + type: 'string', + default: '', + description: 'ID of a Mautic user to assign this contact to', + }, + ], + }, + +/* -------------------------------------------------------------------------- */ +/* contact:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'contact', + ], + }, + }, + default: '', + description: 'Contact ID', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'contact', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Body', + name: 'bodyJson', + type: 'json', + displayOptions: { + show: { + '/jsonParameters': [ + true, + ], + }, + }, + default: '', + description: 'Contact parameters', + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + description: 'First Name', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + description: 'LastName', + }, + { + displayName: 'Primary Company', + name: 'company', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCompanies', + }, + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + description: 'Primary company', + }, + { + displayName: 'Position', + name: 'position', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + description: 'Position', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + displayOptions: { + show: { + '/jsonParameters': [ + false, + ], + }, + }, + default: '', + description: 'Title', + }, + { + displayName: 'IP Address', + name: 'ipAddress', + type: 'string', + default: '', + description: 'IP address to associate with the contact', + }, + { + displayName: 'Last Active', + name: 'lastActive', + type: 'dateTime', + default: '', + description: 'Date/time in UTC;', + }, + { + displayName: 'Owner ID', + name: 'ownerId', + type: 'string', + default: '', + description: 'ID of a Mautic user to assign this contact to', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* contact:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'contact', + ], + }, + }, + default: '', + description: 'Contact ID', + }, +/* -------------------------------------------------------------------------- */ +/* contact:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 30, + }, + default: 30, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Search', + name: 'search', + type: 'string', + default: '', + description: 'String or search command to filter entities by.', + }, + { + displayName: 'Order By', + name: 'orderBy', + type: 'string', + default: '', + description: `Column to sort by. Can use any column listed in the response.
+ However, all properties in the response that are written in camelCase need to be
+ changed a bit. Before every capital add an underscore _ and then change the capital
+ letters to non-capital letters. So dateIdentified becomes date_identified, modifiedByUser + becomes modified_by_user etc.`, + }, + { + displayName: 'Order By Dir', + name: 'orderByDir', + type: 'options', + default: '', + options: [ + { + name: 'ASC', + valie: 'asc', + }, + { + name: 'Desc', + valie: 'desc', + }, + ], + description: 'Sort direction: asc or desc.', + }, + { + displayName: 'Published Only', + name: 'publishedOnly', + type: 'boolean', + default: false, + description: 'Only return currently published entities.', + }, + { + displayName: 'Minimal', + name: 'minimal', + type: 'boolean', + default: false, + description: 'Return only array of entities without additional lists in it.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* contact:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'contact', + ], + }, + }, + default: '', + description: 'Contact ID', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Mautic/GenericFunctions.ts b/packages/nodes-base/nodes/Mautic/GenericFunctions.ts new file mode 100644 index 0000000000..f2330e51cf --- /dev/null +++ b/packages/nodes-base/nodes/Mautic/GenericFunctions.ts @@ -0,0 +1,79 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('mauticApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const base64Key = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64'); + const options: OptionsWithUri = { + headers: { Authorization: `Basic ${base64Key}` }, + method, + qs: query, + uri: uri || `${credentials.url}/api${endpoint}`, + body, + json: true + }; + try { + return await this.helpers.request!(options); + } catch (err) { + const errorMessage = err.error || err.error.message; + + if (errorMessage !== undefined) { + throw errorMessage; + } + throw err + } +} + +/** + * Make an API request to paginated mautic endpoint + * and return all results + */ +export async function mauticApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let data: IDataObject[] = []; + query.limit = 30; + query.start = 0; + + let uri: string | undefined; + + do { + responseData = await mauticApiRequest.call(this, method, endpoint, body, query, uri); + const values = Object.values(responseData[propertyName]) + for (const value of values) { + data.push(value as IDataObject) + } + returnData.push.apply(returnData, data); + query.start++; + } while ( + responseData.total !== undefined && + ((query.limit * query.start) - parseInt(responseData.total)) <= 0 + ); + + return returnData; +} + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = undefined; + } + return result; +} diff --git a/packages/nodes-base/nodes/Mautic/Mautic.node.ts b/packages/nodes-base/nodes/Mautic/Mautic.node.ts new file mode 100644 index 0000000000..b223f28e5d --- /dev/null +++ b/packages/nodes-base/nodes/Mautic/Mautic.node.ts @@ -0,0 +1,229 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; +import { + mauticApiRequest, + mauticApiRequestAllItems, + validateJSON, +} from './GenericFunctions'; +import { + contactFields, + contactOperations, +} from './ContactDescription'; + +export class Mautic implements INodeType { + description: INodeTypeDescription = { + displayName: 'Mautic', + name: 'mautic', + icon: 'file:mautic.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Mautic API', + defaults: { + name: 'Mautic', + color: '#52619b', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'mauticApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Contact', + value: 'contact', + description: 'Use this endpoint to manipulate and obtain details on Mautic’s contacts.', + }, + ], + default: 'contact', + description: 'Resource to consume.', + }, + ...contactOperations, + ...contactFields, + ], + }; + + methods = { + loadOptions: { + // Get all the available companies to display them to user so that he can + // select them easily + async getCompanies(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let companies; + try { + companies = await mauticApiRequestAllItems.call(this, 'companies', 'GET', '/companies'); + } catch (err) { + throw new Error(`Mautic Error: ${JSON.stringify(err)}`); + } + for (const company of companies) { + returnData.push({ + name: company.fields.all.companyname, + value: company.id, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let qs: IDataObject; + let responseData; + for (let i = 0; i < length; i++) { + qs = {}; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + if (resource === 'contact') { + //https://developer.mautic.org/?php#create-contact + if (operation === 'create') { + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; + let body: IDataObject = {}; + if (!jsonActive) { + const firstName = this.getNodeParameter('firstName', i) as string; + const lastName = this.getNodeParameter('lastName', i) as string; + const company = this.getNodeParameter('company', i) as string; + const position = this.getNodeParameter('position', i) as string; + const title = this.getNodeParameter('title', i) as string; + body.firstname = firstName; + body.lastname = lastName; + body.company = company; + body.position = position; + body.title = title; + } else { + const json = validateJSON(this.getNodeParameter('bodyJson', i) as string); + if (json !== undefined) { + body = { ...json }; + } else { + throw new Error('Invalid JSON') + } + } + if (additionalFields.ipAddress) { + body.ipAddress = additionalFields.ipAddress as string; + } + if (additionalFields.lastActive) { + body.ipAddress = additionalFields.lastActive as string; + } + if (additionalFields.ownerId) { + body.ownerId = additionalFields.ownerId as string; + } + try { + responseData = await mauticApiRequest.call(this, 'POST', '/contacts/new', body); + responseData = responseData.contact; + } catch (err) { + throw new Error(`Mautic Error: ${JSON.stringify(err)}`); + } + } + //https://developer.mautic.org/?php#edit-contact + if (operation === 'update') { + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const contactId = this.getNodeParameter('contactId', i) as string; + let body: IDataObject = {}; + if (updateFields.firstName) { + body.firstname = updateFields.firstName as string; + } + if (updateFields.lastName) { + body.lastname = updateFields.lastName as string; + } + if (updateFields.company) { + body.company = updateFields.company as string; + } + if (updateFields.position) { + body.position = updateFields.position as string; + } + if (updateFields.title) { + body.title = updateFields.title as string; + } + if (updateFields.bodyJson) { + const json = validateJSON(updateFields.bodyJson as string); + if (json !== undefined) { + body = { ...json }; + } else { + throw new Error('Invalid JSON') + } + } + if (updateFields.ipAddress) { + body.ipAddress = updateFields.ipAddress as string; + } + if (updateFields.lastActive) { + body.ipAddress = updateFields.lastActive as string; + } + if (updateFields.ownerId) { + body.ownerId = updateFields.ownerId as string; + } + try { + responseData = await mauticApiRequest.call(this, 'PATCH', `/contacts/${contactId}/edit`, body); + responseData = responseData.contact; + } catch (err) { + throw new Error(`Mautic Error: ${JSON.stringify(err)}`); + } + } + //https://developer.mautic.org/?php#get-contact + if (operation === 'get') { + const contactId = this.getNodeParameter('contactId', i) as string; + try { + responseData = await mauticApiRequest.call(this, 'GET', `/contacts/${contactId}`); + responseData = responseData.contact; + } catch (err) { + throw new Error(`Mautic Error: ${JSON.stringify(err)}`); + } + } + //https://developer.mautic.org/?php#list-contacts + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const filters = this.getNodeParameter('filters', i) as IDataObject; + qs = Object.assign(qs, filters); + try { + if (returnAll === true) { + responseData = await mauticApiRequestAllItems.call(this, 'contacts', 'GET', '/contacts', {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + qs.start = 0; + responseData = await mauticApiRequest.call(this, 'GET', '/contacts', {}, qs); + responseData = responseData.contacts; + responseData = Object.values(responseData); + } + } catch (err) { + throw new Error(`Mautic Error: ${JSON.stringify(err)}`); + } + } + //https://developer.mautic.org/?php#delete-contact + if (operation === 'delete') { + const contactId = this.getNodeParameter('contactId', i) as string; + try { + responseData = await mauticApiRequest.call(this, 'DELETE', `/contacts/${contactId}/delete`); + responseData = responseData.contact; + } catch (err) { + throw new Error(`Mautic Error: ${JSON.stringify(err)}`); + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts b/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts new file mode 100644 index 0000000000..18ad63225d --- /dev/null +++ b/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts @@ -0,0 +1,156 @@ +import { + parse as urlParse, +} from 'url'; + +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + INodeTypeDescription, + INodeType, + IWebhookResponseData, + IDataObject, + INodePropertyOptions, + ILoadOptionsFunctions, +} from 'n8n-workflow'; + +import { + mauticApiRequest, +} from './GenericFunctions'; + +export class MauticTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Mautic Trigger', + name: 'mauticTrigger', + icon: 'file:mautic.png', + group: ['trigger'], + version: 1, + description: 'Handle Mautic events via webhooks', + defaults: { + name: 'Mautic Trigger', + color: '#52619b', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'mauticApi', + required: true, + } + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Events', + name: 'events', + type: 'multiOptions', + required: true, + typeOptions: { + loadOptionsMethod: 'getEvents', + }, + default: [], + }, { + displayName: 'Events Order', + name: 'eventsOrder', + type: 'options', + default: '', + options: [ + { + name: 'Asc', + value: 'ASC', + }, + { + name: 'Desc', + value: 'DESC', + }, + ], + description: 'Order direction for queued events in one webhook. Can be “DESC” or “ASC”', + }, + ], + }; + methods = { + loadOptions: { + // Get all the events to display them to user so that he can + // select them easily + async getEvents(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const { triggers } = await mauticApiRequest.call(this, 'GET', '/hooks/triggers'); + for (const [key, value] of Object.entries(triggers)) { + const eventId = key; + const eventName = (value as IDataObject).label as string; + const eventDecription = (value as IDataObject).description as string; + returnData.push({ + name: eventName, + value: eventId, + description: eventDecription, + }); + } + return returnData; + }, + } + }; + // @ts-ignore + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + if (webhookData.webhookId === undefined) { + return false; + } + const endpoint = `/hooks/${webhookData.webhookId}`; + try { + await mauticApiRequest.call(this, 'GET', endpoint, {}); + } catch (e) { + return false; + } + return true; + }, + async create(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default') as string; + const webhookData = this.getWorkflowStaticData('node'); + const events = this.getNodeParameter('events', 0) as string[]; + const eventsOrder = this.getNodeParameter('eventsOrder', 0) as string; + const urlParts = urlParse(webhookUrl); + const body: IDataObject = { + name: `n8n-webhook:${urlParts.path}`, + description: `n8n webhook`, + webhookUrl: webhookUrl, + triggers: events, + eventsOrderbyDir: eventsOrder, + isPublished: true, + } + const { hook } = await mauticApiRequest.call(this, 'POST', '/hooks/new', body); + webhookData.webhookId = hook.id; + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + try { + await mauticApiRequest.call(this, 'DELETE', `/hooks/${webhookData.webhookId}/delete`); + } 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/Mautic/mautic.png b/packages/nodes-base/nodes/Mautic/mautic.png new file mode 100644 index 0000000000000000000000000000000000000000..7ed1375e5fd72ce6d7a8e9b56ee7e57d07d9a07f GIT binary patch literal 6791 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