diff --git a/packages/nodes-base/credentials/AffinityApi.credentials.ts b/packages/nodes-base/credentials/AffinityApi.credentials.ts new file mode 100644 index 0000000000..eb0ef3db0a --- /dev/null +++ b/packages/nodes-base/credentials/AffinityApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class AffinityApi implements ICredentialType { + name = 'affinityApi'; + displayName = 'Affinity API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Affinity/Affinity.node.ts b/packages/nodes-base/nodes/Affinity/Affinity.node.ts new file mode 100644 index 0000000000..2b024c7517 --- /dev/null +++ b/packages/nodes-base/nodes/Affinity/Affinity.node.ts @@ -0,0 +1,267 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + ILoadOptionsFunctions, + INodeTypeDescription, + INodeExecutionData, + INodeType, + INodePropertyOptions, +} from 'n8n-workflow'; +import { + affinityApiRequest, + affinityApiRequestAllItems, +} from './GenericFunctions'; +import { + organizationFields, + organizationOperations, +} from './OrganizationDescription'; +import { + personFields, + personOperations, +} from './PersonDescription'; +import { + IOrganization, +} from './OrganizationInterface'; +import { + IPerson, +} from './PersonInterface'; + +import { snakeCase } from 'change-case'; + +export class Affinity implements INodeType { + description: INodeTypeDescription = { + displayName: 'Affinity', + name: 'affinity', + icon: 'file:affinity.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Affinity API', + defaults: { + name: 'Affinity', + color: '#3343df', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'affinityApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Organization', + value: 'organization', + }, + { + name: 'Person', + value: 'person', + }, + ], + default: 'organization', + description: 'Resource to consume.', + }, + ...organizationOperations, + ...organizationFields, + ...personOperations, + ...personFields, + ], + }; + + methods = { + loadOptions: { + // Get all the available organizations to display them to user so that he can + // select them easily + async getOrganizations(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const organizations = await affinityApiRequestAllItems.call(this, 'organizations', 'GET', '/organizations', {}); + for (const organization of organizations) { + const organizationName = organization.name; + const organizationId = organization.id; + returnData.push({ + name: organizationName, + value: organizationId, + }); + } + return returnData; + }, + // Get all the available persons to display them to user so that he can + // select them easily + async getPersons(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const persons = await affinityApiRequestAllItems.call(this, 'persons', 'GET', '/persons', {}); + for (const person of persons) { + let personName = `${person.first_name} ${person.last_name}`; + if (person.primary_email !== null) { + personName+= ` (${person.primary_email})` + } + const personId = person.id; + returnData.push({ + name: personName, + value: personId, + }); + } + 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 === 'person') { + //https://api-docs.affinity.co/#create-a-new-person + if (operation === 'create') { + const firstName = this.getNodeParameter('firstName', i) as string; + const lastName = this.getNodeParameter('lastName', i) as string; + const emails = this.getNodeParameter('emails', i) as string[]; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IPerson = { + first_name: firstName, + last_name: lastName, + emails, + }; + if (additionalFields.organizations) { + body.organization_ids = additionalFields.organizations as number[]; + } + responseData = await affinityApiRequest.call(this, 'POST', '/persons', body); + } + //https://api-docs.affinity.co/#update-a-person + if (operation === 'update') { + const personId = this.getNodeParameter('personId', i) as number; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const emails = this.getNodeParameter('emails', i) as string[]; + const body: IPerson = { + emails, + }; + if (updateFields.firstName) { + body.first_name = updateFields.firstName as string; + } + if (updateFields.lastName) { + body.last_name = updateFields.lastName as string; + } + if (updateFields.organizations) { + body.organization_ids = updateFields.organizations as number[]; + } + responseData = await affinityApiRequest.call(this, 'PUT', `/persons/${personId}`, body); + } + //https://api-docs.affinity.co/#get-a-specific-person + if (operation === 'get') { + const personId = this.getNodeParameter('personId', i) as number; + const options = this.getNodeParameter('options', i) as IDataObject; + if (options.withInteractionDates) { + qs.with_interaction_dates = options.withInteractionDates as boolean; + } + responseData = await affinityApiRequest.call(this,'GET', `/persons/${personId}`, {}, qs); + } + //https://api-docs.affinity.co/#search-for-persons + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + if (options.term) { + qs.term = options.term as string; + } + if (options.withInteractionDates) { + qs.with_interaction_dates = options.withInteractionDates as boolean; + } + if (returnAll === true) { + responseData = await affinityApiRequestAllItems.call(this, 'persons', 'GET', '/persons', {}, qs); + } else { + qs.page_size = this.getNodeParameter('limit', i) as number; + responseData = await affinityApiRequest.call(this, 'GET', '/persons', {}, qs); + responseData = responseData.persons; + } + } + //https://api-docs.affinity.co/#delete-a-person + if (operation === 'delete') { + const personId = this.getNodeParameter('personId', i) as number; + responseData = await affinityApiRequest.call(this, 'DELETE', `/persons/${personId}`, {}, qs); + } + } + if (resource === 'organization') { + //https://api-docs.affinity.co/#create-a-new-organization + if (operation === 'create') { + const name = this.getNodeParameter('name', i) as string; + const domain = this.getNodeParameter('domain', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IOrganization = { + name, + domain, + }; + if (additionalFields.persons) { + body.person_ids = additionalFields.persons as number[]; + } + responseData = await affinityApiRequest.call(this, 'POST', '/organizations', body); + } + //https://api-docs.affinity.co/#update-an-organization + if (operation === 'update') { + const organizationId = this.getNodeParameter('organizationId', i) as number; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IOrganization = {}; + if (updateFields.name) { + body.name = updateFields.name as string; + } + if (updateFields.domain) { + body.domain = updateFields.domain as string; + } + if (updateFields.persons) { + body.person_ids = updateFields.persons as number[]; + } + responseData = await affinityApiRequest.call(this, 'PUT', `/organizations/${organizationId}`, body); + } + //https://api-docs.affinity.co/#get-a-specific-organization + if (operation === 'get') { + const organizationId = this.getNodeParameter('organizationId', i) as number; + const options = this.getNodeParameter('options', i) as IDataObject; + if (options.withInteractionDates) { + qs.with_interaction_dates = options.withInteractionDates as boolean; + } + responseData = await affinityApiRequest.call(this,'GET', `/organizations/${organizationId}`, {}, qs); + } + //https://api-docs.affinity.co/#search-for-organizations + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + if (options.term) { + qs.term = options.term as string; + } + if (options.withInteractionDates) { + qs.with_interaction_dates = options.withInteractionDates as boolean; + } + if (returnAll === true) { + responseData = await affinityApiRequestAllItems.call(this, 'organizations', 'GET', '/organizations', {}, qs); + } else { + qs.page_size = this.getNodeParameter('limit', i) as number; + responseData = await affinityApiRequest.call(this, 'GET', '/organizations', {}, qs); + responseData = responseData.organizations; + } + } + //https://api-docs.affinity.co/#delete-an-organization + if (operation === 'delete') { + const organizationId = this.getNodeParameter('organizationId', i) as number; + responseData = await affinityApiRequest.call(this, 'DELETE', `/organizations/${organizationId}`, {}, qs); + } + } + 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/Affinity/AffinityTrigger.node.ts b/packages/nodes-base/nodes/Affinity/AffinityTrigger.node.ts new file mode 100644 index 0000000000..230afba160 --- /dev/null +++ b/packages/nodes-base/nodes/Affinity/AffinityTrigger.node.ts @@ -0,0 +1,273 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + INodeTypeDescription, + INodeType, + IWebhookResponseData, + IDataObject, +} from 'n8n-workflow'; + +import { + affinityApiRequest, + eventsExist, + mapResource, +} from './GenericFunctions'; + +export class AffinityTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Affinity Trigger', + name: 'affinityTrigger', + icon: 'file:affinity.png', + group: ['trigger'], + version: 1, + description: 'Handle Affinity events via webhooks', + defaults: { + name: 'Affinity Trigger', + color: '#3343df', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'affinityApi', + required: true, + }, + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Events', + name: 'events', + type: 'multiOptions', + options: [ + { + name: 'file.created', + value: 'file.deleted', + }, + { + name: 'file.created', + value: 'file.deleted', + }, + { + name: 'field_value.created', + value: 'field_value.created', + }, + { + name: 'field_value.updated', + value: 'field_value.updated', + }, + { + name: 'field_value.deleted', + value: 'field_value.deleted', + }, + { + name: 'field.created', + value: 'field.created', + }, + { + name: 'field.updated', + value: 'field.updated', + }, + { + name: 'field.deleted', + value: 'field.deleted', + }, + { + name: 'list.created', + value: 'list.created', + }, + { + name: 'list.updated', + value: 'list.updated', + }, + { + name: 'list.deleted', + value: 'list.deleted', + }, + { + name: 'list_entry.created', + value: 'list_entry.created', + }, + { + name: 'list_entry.updated', + value: 'list_entry.updated', + }, + { + name: 'list_entry.deleted', + value: 'list_entry.deleted', + }, + { + name: 'note.created', + value: 'note.created', + }, + { + name: 'note.updated', + value: 'note.updated', + }, + { + name: 'note.deleted', + value: 'note.deleted', + }, + { + name: 'organization.created', + value: 'organization.created', + }, + { + name: 'organization.updated', + value: 'organization.updated', + }, + { + name: 'organization.deleted', + value: 'organization.deleted', + }, + { + name: 'opportunity.created', + value: 'opportunity.created', + }, + { + name: 'opportunity.updated', + value: 'opportunity.updated', + }, + { + name: 'opportunity.deleted', + value: 'organization.deleted', + }, + { + name: 'person.created', + value: 'person.created', + }, + { + name: 'person.updated', + value: 'person.updated', + }, + { + name: 'person.deleted', + value: 'person.deleted', + }, + ], + default: [], + required: true, + description: 'Webhook events that will be enabled for that endpoint.', + }, + { + displayName: 'Resolve Data', + name: 'resolveData', + type: 'boolean', + default: true, + description: 'By default does the webhook-data only contain the ID of the object.
If this option gets activated it will resolve the data automatically.', + }, + ], + + }; + + // @ts-ignore (because of request) + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + // Check all the webhooks which exist already if it is identical to the + // one that is supposed to get created. + const endpoint = '/webhook'; + + const responseData = await affinityApiRequest.call(this, 'GET', endpoint, {}); + + const webhookUrl = this.getNodeWebhookUrl('default'); + + const events = this.getNodeParameter('events') as string[]; + + for (const webhook of responseData) { + if (eventsExist(webhook.subscriptions, events) && webhook.webhook_url === webhookUrl) { + // Set webhook-id to be sure that it can be deleted + const webhookData = this.getWorkflowStaticData('node'); + webhookData.webhookId = webhook.id as string; + return true; + } + } + return false; + }, + async create(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + + const events = this.getNodeParameter('events') as string[]; + + const endpoint = '/webhook/subscribe'; + + const body = { + webhook_url: webhookUrl, + subscriptions: events, + }; + + const responseData = await affinityApiRequest.call(this, 'POST', endpoint, body); + + if (responseData.id === undefined) { + // Required data is missing so was not successful + return false; + } + + const webhookData = this.getWorkflowStaticData('node'); + webhookData.webhookId = responseData.id as string; + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + if (webhookData.webhookId !== undefined) { + + const endpoint = `/webhook/${webhookData.webhookId}`; + + const responseData = await affinityApiRequest.call(this, 'DELETE', endpoint); + + if (!responseData.success) { + return false; + } + // Remove from the static workflow data so that it is clear + // that no webhooks are registred anymore + delete webhookData.webhookId; + } + return true; + }, + }, + }; + + async webhook(this: IWebhookFunctions): Promise { + const bodyData = this.getBodyData(); + const resolveData = this.getNodeParameter('resolveData', false) as boolean; + + if (bodyData.type === 'sample.webhook') { + return {} + } + + if (resolveData === false) { + // Return the data as it got received + return { + workflowData: [ + this.helpers.returnJsonArray(bodyData), + ], + }; + } + + let responseData: IDataObject = {}; + + if (bodyData.type && bodyData.body) { + const resource = (bodyData.type as string).split('.')[0] + //@ts-ignore + const id = bodyData.body.id; + responseData = await affinityApiRequest.call(this, 'GET', `/${mapResource(resource)}/${id}`); + responseData.type = bodyData.type; + } + + return { + workflowData: [ + this.helpers.returnJsonArray(responseData), + ], + }; + } +} diff --git a/packages/nodes-base/nodes/Affinity/GenericFunctions.ts b/packages/nodes-base/nodes/Affinity/GenericFunctions.ts new file mode 100644 index 0000000000..76f73143ee --- /dev/null +++ b/packages/nodes-base/nodes/Affinity/GenericFunctions.ts @@ -0,0 +1,95 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + ILoadOptionsFunctions, + BINARY_ENCODING +} from 'n8n-core'; + +import { IDataObject, IHookFunctions, IWebhookFunctions } from 'n8n-workflow'; + +export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('affinityApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const apiKey = `:${credentials.apiKey}`; + + const endpoint = 'https://api.affinity.co'; + + let options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + Authorization: `Basic ${Buffer.from(apiKey).toString(BINARY_ENCODING)}`, + }, + method, + body, + qs: query, + uri: uri || `${endpoint}${resource}`, + json: true + }; + if (!Object.keys(body).length) { + delete options.body; + } + if (!Object.keys(query).length) { + delete options.qs; + } + options = Object.assign({}, options, option); + try { + return await this.helpers.request!(options); + } catch (error) { + if (error.response) { + let errorMessage = error.response.body.message || error.response.body.description || error.message; + throw new Error(`Affinity error response [${error.statusCode}]: ${errorMessage}`); + } + throw error; + } +} + +export async function affinityApiRequestAllItems(this: IHookFunctions | ILoadOptionsFunctions | IExecuteFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + query.page_size = 500; + + let uri: string | undefined; + + do { + responseData = await affinityApiRequest.call(this, method, resource, body, query, uri); + // @ts-ignore + query.page_token = responseData.page_token; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData.page_token !== undefined && + responseData.page_token !== null + ); + + return returnData; +} + +export function eventsExist(subscriptions: string[], currentSubsriptions: string[]) { + for (const subscription of currentSubsriptions) { + if (!subscriptions.includes(subscription)) { + return false + } + } + return true; +} + +export function mapResource(key: string) { + //@ts-ignore + return { + person: 'persons', + list: 'lists', + note: 'notes', + organization: 'organizatitons', + list_entry: 'list-entries', + field: 'fields', + file: 'files', + }[key] +} diff --git a/packages/nodes-base/nodes/Affinity/OrganizationDescription.ts b/packages/nodes-base/nodes/Affinity/OrganizationDescription.ts new file mode 100644 index 0000000000..047afba290 --- /dev/null +++ b/packages/nodes-base/nodes/Affinity/OrganizationDescription.ts @@ -0,0 +1,326 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const organizationOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'organization', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a organization', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a organization', + }, + { + name: 'Get', + value: 'get', + description: 'Get a organization', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all organizations', + }, + { + name: 'Update', + value: 'update', + description: 'Update a organization', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const organizationFields = [ + +/* -------------------------------------------------------------------------- */ +/* organization:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'organization', + ], + operation: [ + 'create', + ] + }, + }, + description: 'The name of the organization.', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'organization', + ], + operation: [ + 'create', + ] + }, + }, + description: 'The domain name of the organization.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'organization', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Persons', + name: 'persons', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getPersons', + }, + default: [], + description: 'Persons that the new organization will be associated with.', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* organization:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Organization ID', + name: 'organizationId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'organization', + ], + operation: [ + 'update', + ] + }, + }, + description: 'Unique identifier for the organization.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'organization', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Domain', + name: 'domain', + type: 'string', + default: '', + description: 'The domain name of the organization.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of the organization.', + }, + { + displayName: 'Persons', + name: 'persons', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getPersons', + }, + default: [], + description: 'Persons that the new organization will be associated with.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* organization:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Organization ID', + name: 'organizationId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'organization', + ], + operation: [ + 'get', + ] + }, + }, + description: 'Unique identifier for the organization.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'organization', + ], + operation: [ + 'get', + ], + }, + }, + options: [ + { + displayName: 'With Interaction Dates', + name: 'withInteractionDates', + type: 'boolean', + default: false, + description: 'When true, interaction dates will be present on the returned resources.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* organization:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'organization', + ], + 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: [ + 'organization', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'organization', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Term', + name: 'term', + type: 'string', + default: '', + description: 'A string used to search all the organizations in your team’s address book. This could be an email address, a first name or a last name.', + }, + { + displayName: 'With Interaction Dates', + name: 'withInteractionDates', + type: 'boolean', + default: false, + description: 'When true, interaction dates will be present on the returned resources.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* organization:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Organization ID', + name: 'organizationId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'organization', + ], + operation: [ + 'delete', + ] + }, + }, + description: 'Unique identifier for the organization.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Affinity/OrganizationInterface.ts b/packages/nodes-base/nodes/Affinity/OrganizationInterface.ts new file mode 100644 index 0000000000..ae29e598ae --- /dev/null +++ b/packages/nodes-base/nodes/Affinity/OrganizationInterface.ts @@ -0,0 +1,6 @@ + +export interface IOrganization { + name?: string; + domain?: string; + person_ids?: number[]; +} diff --git a/packages/nodes-base/nodes/Affinity/PersonDescription.ts b/packages/nodes-base/nodes/Affinity/PersonDescription.ts new file mode 100644 index 0000000000..5cd2c97d22 --- /dev/null +++ b/packages/nodes-base/nodes/Affinity/PersonDescription.ts @@ -0,0 +1,370 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const personOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'person', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a person', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a person', + }, + { + name: 'Get', + value: 'get', + description: 'Get a person', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all persons', + }, + { + name: 'Update', + value: 'update', + description: 'Update a person', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const personFields = [ + +/* -------------------------------------------------------------------------- */ +/* person:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Fist Name', + name: 'firstName', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'create', + ] + }, + }, + description: 'The first name of the person.', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'create', + ] + }, + }, + description: 'The last name of the person.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Organizations', + name: 'organizations', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getOrganizations', + }, + default: [], + description: 'Organizations that the person is associated with.', + }, + ], + }, + { + displayName: 'Emails', + name: 'emails', + type: 'string', + description: 'The email addresses of the person', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add To Email', + }, + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'create', + ] + }, + }, + placeholder: 'info@example.com', + default: [], + }, +/* -------------------------------------------------------------------------- */ +/* person:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Person ID', + name: 'personId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'update', + ] + }, + }, + description: 'Unique identifier for the person.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Fist Name', + name: 'firstName', + type: 'string', + default: '', + description: 'The first name of the person.', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'The last name of the person.', + }, + { + displayName: 'Organizations', + name: 'organizations', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getOrganizations', + }, + default: [], + description: 'Organizations that the person is associated with.', + }, + ] + }, + { + displayName: 'Emails', + name: 'emails', + type: 'string', + description: 'The email addresses of the person', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add To Email', + }, + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'update', + ] + }, + }, + placeholder: 'info@example.com', + default: [], + }, +/* -------------------------------------------------------------------------- */ +/* person:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Person ID', + name: 'personId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'get', + ] + }, + }, + description: 'Unique identifier for the person.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'get', + ], + }, + }, + options: [ + { + displayName: 'With Interaction Dates', + name: 'withInteractionDates', + type: 'boolean', + default: false, + description: 'When true, interaction dates will be present on the returned resources.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* person:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'person', + ], + 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: [ + 'person', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Term', + name: 'term', + type: 'string', + default: '', + description: 'A string used to search all the persons in your team’s address book. This could be an email address, a first name or a last name.', + }, + { + displayName: 'With Interaction Dates', + name: 'withInteractionDates', + type: 'boolean', + default: false, + description: 'When true, interaction dates will be present on the returned resources.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* person:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Person ID', + name: 'personId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'person', + ], + operation: [ + 'delete', + ] + }, + }, + description: 'Unique identifier for the person.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Affinity/PersonInterface.ts b/packages/nodes-base/nodes/Affinity/PersonInterface.ts new file mode 100644 index 0000000000..3bf495d3fa --- /dev/null +++ b/packages/nodes-base/nodes/Affinity/PersonInterface.ts @@ -0,0 +1,7 @@ + +export interface IPerson { + first_name?: string; + last_name?: string; + emails?: string[]; + organization_ids?: number[]; +} diff --git a/packages/nodes-base/nodes/Affinity/affinity.png b/packages/nodes-base/nodes/Affinity/affinity.png new file mode 100644 index 0000000000..e097a35e96 Binary files /dev/null and b/packages/nodes-base/nodes/Affinity/affinity.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index c83d7f80ab..c3206dd1a9 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -32,6 +32,7 @@ "dist/credentials/Amqp.credentials.js", "dist/credentials/AsanaApi.credentials.js", "dist/credentials/Aws.credentials.js", + "dist/credentials/AffinityApi.credentials.js", "dist/credentials/BitbucketApi.credentials.js", "dist/credentials/BitlyApi.credentials.js", "dist/credentials/ChargebeeApi.credentials.js", @@ -110,6 +111,8 @@ "dist/nodes/Amqp/AmqpTrigger.node.js", "dist/nodes/Asana/Asana.node.js", "dist/nodes/Asana/AsanaTrigger.node.js", + "dist/nodes/Affinity/Affinity.node.js", + "dist/nodes/Affinity/AffinityTrigger.node.js", "dist/nodes/Aws/AwsLambda.node.js", "dist/nodes/Aws/AwsSes.node.js", "dist/nodes/Aws/AwsSns.node.js", @@ -241,7 +244,7 @@ "@types/moment-timezone": "^0.5.12", "@types/mongodb": "^3.3.6", "@types/node": "^10.10.1", - "@types/nodemailer": "^4.6.5", + "@types/nodemailer": "^4.6.5", "@types/redis": "^2.8.11", "@types/request-promise-native": "~1.0.15", "@types/uuid": "^3.4.6",