diff --git a/packages/nodes-base/credentials/VeroApi.credentials.ts b/packages/nodes-base/credentials/VeroApi.credentials.ts new file mode 100644 index 0000000000..340224fc44 --- /dev/null +++ b/packages/nodes-base/credentials/VeroApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class VeroApi implements ICredentialType { + name = 'veroApi'; + displayName = 'Vero API'; + properties = [ + { + displayName: 'Auth Token', + name: 'authToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Vero/EventDescripion.ts b/packages/nodes-base/nodes/Vero/EventDescripion.ts new file mode 100644 index 0000000000..8df87fd072 --- /dev/null +++ b/packages/nodes-base/nodes/Vero/EventDescripion.ts @@ -0,0 +1,245 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const eventOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'event', + ], + }, + }, + options: [ + { + name: 'Track', + value: 'track', + description: `This endpoint tracks an event for a specific customer. + If the customer profile doesn’t exist, Vero will create it.`, + }, + ], + default: 'track', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const eventFields = [ + +/* -------------------------------------------------------------------------- */ +/* event:track */ +/* -------------------------------------------------------------------------- */ + + { + displayName: 'ID', + name: 'id', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'track', + ] + }, + }, + description: 'The unique identifier of the customer', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'track', + ] + }, + }, + description: 'Email', + }, + { + displayName: 'Event Name', + name: 'eventName', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'track', + ] + }, + }, + description: 'The name of the event tracked.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'track', + ] + }, + } + }, + { + displayName: 'Data', + name: 'dataAttributesUi', + placeholder: 'Add Data', + description: 'key value pairs that represent any properties you want to track with this event', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'track', + ], + jsonParameters: [ + false + ], + }, + }, + options: [ + { + name: 'dataAttributesValues', + displayName: 'Data', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + description: 'Name of the property to set.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value of the property to set.', + }, + ] + }, + ], + }, + { + displayName: 'Extra', + name: 'extraAttributesUi', + placeholder: 'Add Extra', + description: 'Key value pairs that represent reserved, Vero-specific operators. Refer to the note on “deduplication” below.', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'track', + ], + jsonParameters: [ + false + ], + }, + }, + options: [ + { + name: 'extraAttributesValues', + displayName: 'Extra', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + description: 'Name of the property to set.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value of the property to set.', + }, + ] + }, + ], + }, + { + displayName: 'Data', + name: 'dataAttributesJson', + type: 'json', + default: '', + required: false, + typeOptions: { + alwaysOpenEditWindow: true, + }, + description: 'key value pairs that represent the custom user properties you want to update', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'track', + ], + jsonParameters: [ + true, + ], + }, + }, + }, + { + displayName: 'Extra', + name: 'extraAttributesJson', + type: 'json', + default: '', + required: false, + typeOptions: { + alwaysOpenEditWindow: true, + }, + description: 'Key value pairs that represent reserved, Vero-specific operators. Refer to the note on “deduplication” below.', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'track', + ], + jsonParameters: [ + true, + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Vero/GenericFunctions.ts b/packages/nodes-base/nodes/Vero/GenericFunctions.ts new file mode 100644 index 0000000000..2e5053fb7e --- /dev/null +++ b/packages/nodes-base/nodes/Vero/GenericFunctions.ts @@ -0,0 +1,50 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function veroApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('veroApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + let options: OptionsWithUri = { + method, + qs, + body, + form: { + auth_token: credentials.authToken, + ...body, + }, + uri: uri ||`https://api.getvero.com/api/v2${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) { + let errorMessage = error.message; + if (error.response.body) { + errorMessage = error.response.body.message || error.response.body.Message || error.message; + } + + throw new Error(errorMessage); + } +} + +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/Vero/UserDescription.ts b/packages/nodes-base/nodes/Vero/UserDescription.ts new file mode 100644 index 0000000000..f90cb49a8c --- /dev/null +++ b/packages/nodes-base/nodes/Vero/UserDescription.ts @@ -0,0 +1,376 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const userOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'user', + ], + }, + }, + options: [ + { + name: 'Create/Update', + value: 'create', + description: `Creates a new user profile if the user doesn’t exist yet. + Otherwise, the user profile is updated based on the properties provided.`, + }, + { + name: 'Alias', + value: 'alias', + description: 'Changes a user’s identifier.', + }, + { + name: 'Unsubscribe', + value: 'unsubscribe', + description: 'Unsubscribes a single user.', + }, + { + name: 'Re-subscribe', + value: 'resubscribe', + description: 'Resubscribe a single user.', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a single user.', + }, + { + name: 'Add Tags', + value: 'addTags', + description: 'Adds a tag to a user’s profile.', + }, + { + name: 'Remove Tags', + value: 'removeTags', + description: 'Removes a tag from a user’s profile.', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const userFields = [ + +/* -------------------------------------------------------------------------- */ +/* user:create */ +/* -------------------------------------------------------------------------- */ + + { + displayName: 'ID', + name: 'id', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ] + }, + }, + description: 'The unique identifier of the customer', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ] + }, + } + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'The table to create the row in.', + }, + ] + }, + { + displayName: 'Data', + name: 'dataAttributesUi', + placeholder: 'Add Data', + description: 'key value pairs that represent the custom user properties you want to update', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false + ], + }, + }, + options: [ + { + name: 'dataAttributesValues', + displayName: 'Data', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + description: 'Name of the property to set.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value of the property to set.', + }, + ] + }, + ], + }, + { + displayName: 'Data', + name: 'dataAttributesJson', + type: 'json', + default: '', + required: false, + typeOptions: { + alwaysOpenEditWindow: true, + }, + description: 'key value pairs that represent the custom user properties you want to update', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ], + jsonParameters: [ + true, + ], + }, + }, + }, + +/* -------------------------------------------------------------------------- */ +/* user:alias */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'ID', + name: 'id', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'alias', + ] + }, + }, + description: 'The old unique identifier of the user', + }, + { + displayName: 'New ID', + name: 'newId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'alias', + ] + }, + }, + description: 'The new unique identifier of the user', + }, +/* -------------------------------------------------------------------------- */ +/* user:unsubscribe */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'ID', + name: 'id', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'unsubscribe', + ] + }, + }, + description: 'The unique identifier of the user', + }, +/* -------------------------------------------------------------------------- */ +/* user:resubscribe */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'ID', + name: 'id', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'resubscribe', + ] + }, + }, + description: 'The unique identifier of the user', + }, +/* -------------------------------------------------------------------------- */ +/* user:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'ID', + name: 'id', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'delete', + ] + }, + }, + description: 'The unique identifier of the user', + }, +/* -------------------------------------------------------------------------- */ +/* user:addTags */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'ID', + name: 'id', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'addTags', + ] + }, + }, + description: 'The unique identifier of the user', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'addTags', + ] + }, + }, + description: 'Tags to add separated by ,', + }, +/* -------------------------------------------------------------------------- */ +/* user:removeTags */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'ID', + name: 'id', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'removeTags', + ] + }, + }, + description: 'The unique identifier of the user', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'removeTags', + ] + }, + }, + description: 'Tags to remove separated by ,', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Vero/Vero.node.ts b/packages/nodes-base/nodes/Vero/Vero.node.ts new file mode 100644 index 0000000000..d17fbef375 --- /dev/null +++ b/packages/nodes-base/nodes/Vero/Vero.node.ts @@ -0,0 +1,232 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, +} from 'n8n-workflow'; +import { + veroApiRequest, + validateJSON, +} from './GenericFunctions'; +import { + userOperations, + userFields, +} from './UserDescription'; +import { + eventOperations, + eventFields +} from './EventDescripion'; + +export class Vero implements INodeType { + description: INodeTypeDescription = { + displayName: 'Vero', + name: 'Vero', + icon: 'file:vero.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Vero API', + defaults: { + name: 'Vero', + color: '#c02428', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'veroApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'User', + value: 'user', + description: `Lets you create, update and manage the subscription status of your users.`, + }, + { + name: 'Event', + value: 'event', + description: `Lets you track events based on actions your customers take in real time.`, + }, + ], + default: 'user', + description: 'Resource to consume.', + }, + ...userOperations, + ...eventOperations, + ...userFields, + ...eventFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + for (let i = 0; i < length; i++) { + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + //https://developers.getvero.com/?bash#users + if (resource === 'user') { + //https://developers.getvero.com/?bash#users-identify + if (operation === 'create') { + const id = this.getNodeParameter('id', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; + const body = { + id, + }; + if (additionalFields.email) { + // @ts-ignore + body.email = additionalFields.email as string; + } + if (!jsonActive) { + const dataAttributesValues = (this.getNodeParameter('dataAttributesUi', i) as IDataObject).dataAttributesValues as IDataObject[]; + if (dataAttributesValues) { + const dataAttributes = {}; + for (let i = 0; i < dataAttributesValues.length; i++) { + // @ts-ignore + dataAttributes[dataAttributesValues[i].key] = dataAttributesValues[i].value; + // @ts-ignore + body.data = dataAttributes; + } + } + } else { + const dataAttributesJson = validateJSON(this.getNodeParameter('dataAttributesJson', i) as string); + if (dataAttributesJson) { + // @ts-ignore + body.data = dataAttributesJson; + } + } + try { + responseData = await veroApiRequest.call(this, 'POST', '/users/track', body); + } catch (err) { + throw new Error(`Vero Error: ${err}`); + } + } + //https://developers.getvero.com/?bash#users-alias + if (operation === 'alias') { + const id = this.getNodeParameter('id', i) as string; + const newId = this.getNodeParameter('newId', i) as string; + const body = { + id, + new_id: newId, + }; + try { + responseData = await veroApiRequest.call(this, 'PUT', '/users/reidentify', body); + } catch (err) { + throw new Error(`Vero Error: ${err}`); + } + } + //https://developers.getvero.com/?bash#users-unsubscribe + //https://developers.getvero.com/?bash#users-resubscribe + //https://developers.getvero.com/?bash#users-delete + if (operation === 'unsubscribe' || + operation === 'resubscribe' || + operation === 'delete') { + const id = this.getNodeParameter('id', i) as string; + const body = { + id, + }; + try { + responseData = await veroApiRequest.call(this, 'POST', `/users/${operation}`, body); + } catch (err) { + throw new Error(`Vero Error: ${err}`); + } + } + //https://developers.getvero.com/?bash#tags-add + //https://developers.getvero.com/?bash#tags-remove + if (operation === 'addTags' || + operation === 'removeTags') { + const id = this.getNodeParameter('id', i) as string; + const tags = (this.getNodeParameter('tags', i) as string).split(',') as string[]; + const body = { + id, + }; + if (operation === 'addTags') { + // @ts-ignore + body.add = JSON.stringify(tags); + } + if (operation === 'removeTags') { + // @ts-ignore + body.remove = JSON.stringify(tags); + } + try { + responseData = await veroApiRequest.call(this, 'PUT', '/users/tags/edit', body); + } catch (err) { + throw new Error(`Vero Error: ${err}`); + } + } + } + //https://developers.getvero.com/?bash#events + if (resource === 'event') { + //https://developers.getvero.com/?bash#events-track + if (operation === 'track') { + const id = this.getNodeParameter('id', i) as string; + const email = this.getNodeParameter('email', i) as string; + const eventName = this.getNodeParameter('eventName', i) as string; + const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; + const body = { + identity: { id, email }, + event_name: eventName, + email, + }; + if (!jsonActive) { + const dataAttributesValues = (this.getNodeParameter('dataAttributesUi', i) as IDataObject).dataAttributesValues as IDataObject[]; + if (dataAttributesValues) { + const dataAttributes = {}; + for (let i = 0; i < dataAttributesValues.length; i++) { + // @ts-ignore + dataAttributes[dataAttributesValues[i].key] = dataAttributesValues[i].value; + // @ts-ignore + body.data = JSON.stringify(dataAttributes); + } + } + const extraAttributesValues = (this.getNodeParameter('extraAttributesUi', i) as IDataObject).extraAttributesValues as IDataObject[]; + if (extraAttributesValues) { + const extraAttributes = {}; + for (let i = 0; i < extraAttributesValues.length; i++) { + // @ts-ignore + extraAttributes[extraAttributesValues[i].key] = extraAttributesValues[i].value; + // @ts-ignore + body.extras = JSON.stringify(extraAttributes); + } + } + } else { + const dataAttributesJson = validateJSON(this.getNodeParameter('dataAttributesJson', i) as string); + if (dataAttributesJson) { + // @ts-ignore + body.data = JSON.stringify(dataAttributesJson); + } + const extraAttributesJson = validateJSON(this.getNodeParameter('extraAttributesJson', i) as string); + if (extraAttributesJson) { + // @ts-ignore + body.extras = JSON.stringify(extraAttributesJson); + } + } + try { + responseData = await veroApiRequest.call(this, 'POST', '/events/track', body); + } catch (err) { + throw new Error(`Vero Error: ${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/Vero/vero.png b/packages/nodes-base/nodes/Vero/vero.png new file mode 100644 index 0000000000..bddebcf31b Binary files /dev/null and b/packages/nodes-base/nodes/Vero/vero.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index a5c3e02ce6..510ba8cff3 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -72,7 +72,8 @@ "dist/credentials/TypeformApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", "dist/credentials/TodoistApi.credentials.js", - "dist/credentials/TypeformApi.credentials.js" + "dist/credentials/TypeformApi.credentials.js", + "dist/credentials/VeroApi.credentials.js" ], "nodes": [ "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", @@ -154,6 +155,7 @@ "dist/nodes/Trello/TrelloTrigger.node.js", "dist/nodes/Twilio/Twilio.node.js", "dist/nodes/Typeform/TypeformTrigger.node.js", + "dist/nodes/Vero/Vero.node.js", "dist/nodes/WriteBinaryFile.node.js", "dist/nodes/Webhook.node.js", "dist/nodes/Xml.node.js",