diff --git a/packages/nodes-base/credentials/SyncroMspApi.credentials.ts b/packages/nodes-base/credentials/SyncroMspApi.credentials.ts new file mode 100644 index 0000000000..fc4a011db6 --- /dev/null +++ b/packages/nodes-base/credentials/SyncroMspApi.credentials.ts @@ -0,0 +1,24 @@ +import { + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class SyncroMspApi implements ICredentialType { + name = 'syncroMspApi'; + displayName = 'SyncroMSP API'; + documentationUrl = 'syncromsp'; + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + default: '', + }, + { + displayName: 'Subdomain', + name: 'subdomain', + type: 'string', + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/SyncroMSP/SyncroMsp.node.ts b/packages/nodes-base/nodes/SyncroMSP/SyncroMsp.node.ts new file mode 100644 index 0000000000..31745baef6 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/SyncroMsp.node.ts @@ -0,0 +1,28 @@ +import { + INodeTypeBaseDescription, + INodeVersionedType, +} from 'n8n-workflow'; + +import { NodeVersionedType } from '../../src/NodeVersionedType'; + +import { SyncroMspV1 } from './v1/SyncroMspV1.node'; + +export class SyncroMsp extends NodeVersionedType { + constructor() { + const baseDescription: INodeTypeBaseDescription = { + displayName: 'SyncroMSP', + name: 'syncroMsp', + icon: 'file:syncromsp.png', + group: ['output'], + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Manage contacts, tickets and more from Syncro MSP', + defaultVersion: 1, + }; + + const nodeVersions: INodeVersionedType['nodeVersions'] = { + 1: new SyncroMspV1(baseDescription), + }; + + super(nodeVersions, baseDescription); + } +} diff --git a/packages/nodes-base/nodes/SyncroMSP/syncromsp.png b/packages/nodes-base/nodes/SyncroMSP/syncromsp.png new file mode 100644 index 0000000000..a6b3f1bf0a Binary files /dev/null and b/packages/nodes-base/nodes/SyncroMSP/syncromsp.png differ diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/SyncroMspV1.node.ts b/packages/nodes-base/nodes/SyncroMSP/v1/SyncroMspV1.node.ts new file mode 100644 index 0000000000..9255cde1c7 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/SyncroMspV1.node.ts @@ -0,0 +1,57 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + ICredentialsDecrypted, + ICredentialTestFunctions, + INodeType, + INodeTypeBaseDescription, + INodeTypeDescription, + NodeCredentialTestResult, +} from 'n8n-workflow'; + +import { versionDescription } from './actions/versionDescription'; +import { loadOptions } from './methods'; +import { router } from './actions/router'; +import { validateCredentials } from './transport'; + +export class SyncroMspV1 implements INodeType { + + description: INodeTypeDescription; + + constructor(baseDescription: INodeTypeBaseDescription) { + this.description = { + ...baseDescription, + ...versionDescription, + }; + } + + methods = { + loadOptions, + credentialTest: { + async syncroMspApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + try { + await validateCredentials.call(this, credential.data as ICredentialDataDecryptedObject); + } catch (error) { + if (error.statusCode === 401) { + return { + status: 'Error', + message: 'The API Key included in the request is invalid', + }; + } + } + + return { + status: 'OK', + message: 'Connection successful!', + }; + }, + }, + }; + + async execute(this: IExecuteFunctions) { + return await router.call(this); + } +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/Interfaces.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/Interfaces.ts new file mode 100644 index 0000000000..027776e50a --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/Interfaces.ts @@ -0,0 +1,33 @@ +import { + AllEntities, + Entity, + PropertiesOf, +} from 'n8n-workflow'; + +type SyncroMspMap = { + contact: 'create'|'delete'|'get'|'getAll'|'update'; + customer: 'create'|'delete'|'get'|'getAll'|'update'; + rmm: 'create'|'delete'|'get'|'getAll'|'mute' ; + ticket: 'create'|'delete'|'get'|'getAll'|'update'; +}; + +export type SyncroMsp = AllEntities; + +export type SyncroMspMapContact = Entity; +export type SyncroMspMapCustomer = Entity; +export type SyncroMspMapRmm = Entity; +export type SyncroMspMapTicket = Entity; + +export type ContactProperties = PropertiesOf; +export type CustomerProperties = PropertiesOf; +export type RmmProperties = PropertiesOf; +export type TicketProperties = PropertiesOf; + +export interface IAttachment { + fields: { + item?: object[]; + }; + actions: { + item?: object[]; + }; +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/create/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/create/description.ts new file mode 100644 index 0000000000..915e35a679 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/create/description.ts @@ -0,0 +1,81 @@ +import { + ContactProperties, +} from '../../Interfaces'; + +import { + addressFixedCollection +} from '../../../methods/sharedFields'; + +export const contactCreateDescription: ContactProperties = [ + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + addressFixedCollection, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + default: '', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/create/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/create/execute.ts new file mode 100644 index 0000000000..9d99aa1b4e --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/create/execute.ts @@ -0,0 +1,44 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest, +} from '../../../transport'; + + +export async function createContact(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('customerId', index) as IDataObject; + const email = this.getNodeParameter('email', index) as IDataObject; + const { address, notes, phone, name } = this.getNodeParameter('additionalFields', index) as IDataObject; + + const qs = {} as IDataObject; + const requestMethod = 'POST'; + const endpoint = 'contacts'; + let body = {} as IDataObject; + let addressData = address as IDataObject; + + if (addressData) { + addressData = addressData['addressFields'] as IDataObject; + addressData.address1 = addressData.address; + } + + body = { + ...addressData, + customer_id: id, + email, + name, + notes, + phone, + }; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + + return this.helpers.returnJsonArray(responseData); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/create/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/create/index.ts new file mode 100644 index 0000000000..b2b821f766 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/create/index.ts @@ -0,0 +1,7 @@ +import { createContact as execute } from './execute'; +import { contactCreateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/del/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/del/description.ts new file mode 100644 index 0000000000..fda202b287 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/del/description.ts @@ -0,0 +1,24 @@ +import { + ContactProperties, +} from '../../Interfaces'; + +export const contactDeleteDescription: ContactProperties = [ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + description: 'Delete a specific contact by ID', + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/del/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/del/execute.ts new file mode 100644 index 0000000000..9809370eac --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/del/execute.ts @@ -0,0 +1,26 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest +} from '../../../transport'; + + +export async function deleteContact(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('contactId', index) as string; + + const qs = {} as IDataObject; + const requestMethod = 'DELETE'; + const endpoint = `contacts/${id}`; + const body = {} as IDataObject; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/del/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/del/index.ts new file mode 100644 index 0000000000..d87102feae --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/del/index.ts @@ -0,0 +1,7 @@ +import { deleteContact as execute } from './execute'; +import { contactDeleteDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/get/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/get/description.ts new file mode 100644 index 0000000000..5dd45d33bc --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/get/description.ts @@ -0,0 +1,24 @@ +import { + ContactProperties, +} from '../../Interfaces'; + +export const contactGetDescription: ContactProperties = [ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: 'Get specific contact by ID', + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/get/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/get/execute.ts new file mode 100644 index 0000000000..cc3420e1d2 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/get/execute.ts @@ -0,0 +1,26 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest +} from '../../../transport'; + + +export async function getContact(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('contactId', index) as string; + + const qs = {} as IDataObject; + const requestMethod = 'GET'; + const endpoint = `contacts/${id}`; + const body = {} as IDataObject; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/get/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/get/index.ts new file mode 100644 index 0000000000..00abd8c120 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/get/index.ts @@ -0,0 +1,7 @@ +import { getContact as execute } from './execute'; +import { contactGetDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/getAll/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/getAll/description.ts new file mode 100644 index 0000000000..e2671d59a1 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/getAll/description.ts @@ -0,0 +1,43 @@ +import { + ContactProperties, +} from '../../Interfaces'; + +export const contactGetAllDescription: ContactProperties = [ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + }, + }, + noDataExpression: true, + 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, + ], + }, + }, + default: 25, + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/getAll/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/getAll/execute.ts new file mode 100644 index 0000000000..4b6a8dac46 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/getAll/execute.ts @@ -0,0 +1,31 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest, apiRequestAllItems +} from '../../../transport'; + +export async function getAll(this: IExecuteFunctions, index: number): Promise { + const returnAll = this.getNodeParameter('returnAll', index) as boolean; + + const qs = {} as IDataObject; + const requestMethod = 'GET'; + const endpoint = 'contacts'; + const body = {} as IDataObject; + + let responseData; + if (returnAll) { + responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData); + } else { + const limit = this.getNodeParameter('limit', index) as IDataObject; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray((responseData.contacts).splice(0, limit)); + } +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/getAll/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/getAll/index.ts new file mode 100644 index 0000000000..5bfeb146b6 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/getAll/index.ts @@ -0,0 +1,7 @@ +import { getAll as execute } from './execute'; +import { contactGetAllDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/index.ts new file mode 100644 index 0000000000..7ba19a8bed --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/index.ts @@ -0,0 +1,66 @@ + +import * as getAll from './getAll'; +import * as create from './create'; +import * as get from './get'; +import * as update from './update'; +import * as del from './del'; + +import { INodeProperties } from 'n8n-workflow'; + +export { + getAll, + create, + del as delete, + update, + get, +}; + + +export const descriptions = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'contact', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create new contact', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete contact', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve contact', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all contacts', + }, + { + name: 'Update', + value: 'update', + description: 'Update contact', + }, + ], + default: 'getAll', + description: '', + }, + ...getAll.description, + ...create.description, + ...get.description, + ...update.description, + ...del.description, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/update/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/update/description.ts new file mode 100644 index 0000000000..67067a0997 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/update/description.ts @@ -0,0 +1,76 @@ +import { + ContactProperties, +} from '../../Interfaces'; + +import { + addressFixedCollection +} from '../../../methods/sharedFields'; + +export const contactUpdateDescription: ContactProperties = [ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, + default: {}, + options: [ + addressFixedCollection, + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + default: '', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/update/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/update/execute.ts new file mode 100644 index 0000000000..a5aa040273 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/update/execute.ts @@ -0,0 +1,44 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest, +} from '../../../transport'; + + +export async function updateContact(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('contactId', index) as IDataObject; + const { address, customerId, email, name, notes, phone } = this.getNodeParameter('updateFields', index) as IDataObject; + + const qs = {} as IDataObject; + const requestMethod = 'PUT'; + const endpoint = `contacts/${id}`; + let body = {} as IDataObject; + let addressData = address as IDataObject; + + if (addressData) { + addressData = addressData['addressFields'] as IDataObject; + addressData.address1 = addressData.address; + } + + body = { + ...addressData, + contact_id: id, + customer_id: customerId, + email, + name, + notes, + phone, + }; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + + return this.helpers.returnJsonArray(responseData); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/update/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/update/index.ts new file mode 100644 index 0000000000..76e1c8b3f3 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/contact/update/index.ts @@ -0,0 +1,7 @@ +import { updateContact as execute } from './execute'; +import { contactUpdateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/create/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/create/description.ts new file mode 100644 index 0000000000..5bf3a7068c --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/create/description.ts @@ -0,0 +1,118 @@ +import { + CustomerProperties, +} from '../../Interfaces'; + +import { + addressFixedCollection +} from '../../../methods/sharedFields'; + +export const customerCreateDescription: CustomerProperties = [ + { + displayName: 'Email', + name: 'email', + type: 'string', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + addressFixedCollection, + { + displayName: 'Business Name', + name: 'businessName', + type: 'string', + default: '', + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + }, + { + displayName: 'Get SMS', + name: 'getSms', + type: 'boolean', + default: false, + }, + { + displayName: 'Invoice Emails', + name: 'invoiceCcEmails', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Email', + }, + default: '', + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + default: '', + }, + { + displayName: 'No Email', + name: 'noEmail', + type: 'boolean', + default: false, + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + default: '', + }, + { + displayName: 'Notification Email', + name: 'notificationEmail', + type: 'string', + default: '', + displayOptions: { + show: { + noEmail: [ + false, + ], + }, + }, + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Referred By', + name: 'referredBy', + type: 'string', + default: '', + description: 'Source from which customer is referred to the platform like Linkedin, Google, Customer name etc.', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/create/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/create/execute.ts new file mode 100644 index 0000000000..51fd1e81b6 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/create/execute.ts @@ -0,0 +1,48 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest, +} from '../../../transport'; + +export async function addCustomer(this: IExecuteFunctions, index: number): Promise { + const email = this.getNodeParameter('email', index) as IDataObject; + const { address, getSms, businessName, lastname, firstName, invoiceCcEmails, noEmail, notes, notificationEmail, phone, referredBy } = this.getNodeParameter('additionalFields', index) as IDataObject; + + const qs = {} as IDataObject; + const requestMethod = 'POST'; + const endpoint = 'customers'; + let body = {} as IDataObject; + let addressData = address as IDataObject; + + if (addressData) { + addressData = addressData['addressFields'] as IDataObject; + addressData.address_2 = addressData.address2; + } + + body = { + ...addressData, + business_name: businessName, + email, + firstname: firstName, + get_sms: getSms, + invoice_cc_emails: (invoiceCcEmails as string[] || []).join(','), + lastname, + no_email: noEmail, + notes, + notification_email: notificationEmail, + phone, + referred_by: referredBy, + }; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + + return this.helpers.returnJsonArray(responseData.customer); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/create/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/create/index.ts new file mode 100644 index 0000000000..eb10582aa7 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/create/index.ts @@ -0,0 +1,7 @@ +import { addCustomer as execute } from './execute'; +import { customerCreateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/del/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/del/description.ts new file mode 100644 index 0000000000..bcc6c86cd9 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/del/description.ts @@ -0,0 +1,24 @@ +import { + CustomerProperties, +} from '../../Interfaces'; + +export const customerDeleteDescription: CustomerProperties = [ + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + description: 'Delete a specific customer by ID', + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/del/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/del/execute.ts new file mode 100644 index 0000000000..9544e90460 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/del/execute.ts @@ -0,0 +1,26 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest +} from '../../../transport'; + + +export async function deleteCustomer(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('customerId', index) as string; + + const qs = {} as IDataObject; + const requestMethod = 'DELETE'; + const endpoint = `customers/${id}`; + const body = {} as IDataObject; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/del/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/del/index.ts new file mode 100644 index 0000000000..432674f0bc --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/del/index.ts @@ -0,0 +1,7 @@ +import { deleteCustomer as execute } from './execute'; +import { customerDeleteDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/get/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/get/description.ts new file mode 100644 index 0000000000..97219de6c5 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/get/description.ts @@ -0,0 +1,24 @@ +import { + CustomerProperties, +} from '../../Interfaces'; + +export const customerGetDescription: CustomerProperties = [ + { + displayName: 'Cutomer ID', + name: 'customerId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: 'Get specific customer by ID', + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/get/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/get/execute.ts new file mode 100644 index 0000000000..ef19b46939 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/get/execute.ts @@ -0,0 +1,26 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest +} from '../../../transport'; + + +export async function getCustomer(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('customerId', index) as string; + + const qs = {} as IDataObject; + const requestMethod = 'GET'; + const endpoint = `customers/${id}`; + const body = {} as IDataObject; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData.customer); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/get/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/get/index.ts new file mode 100644 index 0000000000..5f17c92b2e --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/get/index.ts @@ -0,0 +1,7 @@ +import { getCustomer as execute } from './execute'; +import { customerGetDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/getAll/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/getAll/description.ts new file mode 100644 index 0000000000..357454fc79 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/getAll/description.ts @@ -0,0 +1,109 @@ +import { + CustomerProperties, +} from '../../Interfaces'; + +export const customerGetAllDescription: CustomerProperties = [ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + noDataExpression: true, + description: 'If all results should be returned or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + default: 25, + description: 'Limit the number of rows returned', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Business Name', + name: 'businessName', + type: 'string', + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + }, + { + displayName: 'Include Disabled', + name: 'includeDisabled', + type: 'boolean', + default: false, + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + default: '', + }, + { + displayName: 'Search Query', + name: 'query', + type: 'string', + default: '', + placeholder: 'John Doe', + description: 'Search query, it can be anything related to customer data like name etc.', + }, + { + displayName: 'Sort', + name: 'sort', + type: 'string', + default: '', + placeholder: 'firstname ASC', + description: 'customer field to order by, eg: "firstname ASC", "city DESC" etc.', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/getAll/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/getAll/execute.ts new file mode 100644 index 0000000000..ec2ab11915 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/getAll/execute.ts @@ -0,0 +1,46 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest, apiRequestAllItems +} from '../../../transport'; + + +export async function getAll(this: IExecuteFunctions, index: number): Promise { + const returnAll = this.getNodeParameter('returnAll', index) as boolean; + const filters = this.getNodeParameter('filters', index) as IDataObject; + + let qs = {} as IDataObject; + const requestMethod = 'GET'; + const endpoint = 'customers'; + const body = {} as IDataObject; + + if (filters) { + qs = filters; + if (qs.businessName) { + qs.business_name = qs.businessName; + } + if (qs.includeDisabled) { + qs.include_disabled = qs.includeDisabled; + } + } + + if (returnAll === false) { + qs.per_page = this.getNodeParameter('limit', index) as number; + } + + let responseData; + if (returnAll) { + responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData); + } else { + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData.customers); + } +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/getAll/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/getAll/index.ts new file mode 100644 index 0000000000..0c41710eb5 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/getAll/index.ts @@ -0,0 +1,7 @@ +import { getAll as execute } from './execute'; +import { customerGetAllDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/index.ts new file mode 100644 index 0000000000..ac57ad014a --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/index.ts @@ -0,0 +1,67 @@ + +import * as getAll from './getAll'; +import * as create from './create'; +import * as del from './del'; +import * as update from './update'; +import * as get from './get'; + +import { INodeProperties } from 'n8n-workflow'; + +export { + getAll, + create, + del as delete, + update, + get, +}; + + +export const descriptions = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'customer', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create new customer', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete customer', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve customer', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all customers', + }, + { + name: 'Update', + value: 'update', + description: 'Update customer', + }, + ], + default: 'getAll', + description: '', + }, + ...getAll.description, + ...get.description, + ...create.description, + ...del.description, + ...update.description, +] as INodeProperties[]; + diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/update/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/update/description.ts new file mode 100644 index 0000000000..5417e1d3b2 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/update/description.ts @@ -0,0 +1,124 @@ +import { + CustomerProperties, +} from '../../Interfaces'; + +import { + addressFixedCollection +} from '../../../methods/sharedFields'; + +export const customerUpdateDescription: CustomerProperties = [ + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + default: {}, + options: [ + addressFixedCollection, + { + displayName: 'Business Name', + name: 'businessName', + type: 'string', + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + }, + { + displayName: 'Get SMS', + name: 'getSms', + type: 'boolean', + default: true, + }, + { + displayName: 'Invoice Emails', + name: 'invoiceCcEmails', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Email', + }, + default: '', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + }, + { + displayName: 'No Email', + name: 'noEmail', + type: 'boolean', + default: false, + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + default: '', + }, + { + displayName: 'Notification Email', + name: 'notificationEmail', + type: 'string', + default: '', + displayOptions: { + show: { + noEmail: [ + false, + ], + }, + }, + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Referred By', + name: 'referredBy', + type: 'string', + default: '', + description: 'Source from which customer is referred to the platform like Linkedin, Google, Customer name etc.', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/update/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/update/execute.ts new file mode 100644 index 0000000000..b68fddd7e7 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/update/execute.ts @@ -0,0 +1,53 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + NodeApiError, +} from 'n8n-workflow'; + +import { + apiRequest, +} from '../../../transport'; + + +export async function updateCustomer(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('customerId', index) as IDataObject; + const { address, businessName, email, firstName, getSms, invoiceCcEmails, + lastName, noEmail, notes, notificationEmail, phone, referredBy } = this.getNodeParameter('updateFields', index) as IDataObject; + + const qs = {} as IDataObject; + const requestMethod = 'PUT'; + const endpoint = `customers/${id}`; + let body = {} as IDataObject; + let addressData = address as IDataObject; + + if (addressData) { + addressData = addressData['addressFields'] as IDataObject; + addressData.address_2 = addressData.address2; + } + + body = { + ...addressData, + business_name: businessName, + email, + firstname: firstName, + get_sms: getSms, + invoice_cc_emails: (invoiceCcEmails as string[] || []).join(','), + lastname: lastName, + no_email: noEmail, + notes, + notification_email: notificationEmail, + phone, + referred_by: referredBy, + }; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + if (!responseData.customer) { + throw new NodeApiError(this.getNode(), responseData, { httpCode: '404', message: 'Customer ID not found' }); + } + return this.helpers.returnJsonArray(responseData.customer); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/update/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/update/index.ts new file mode 100644 index 0000000000..4c952aecf5 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/customer/update/index.ts @@ -0,0 +1,7 @@ +import { updateCustomer as execute } from './execute'; +import { customerUpdateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/create/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/create/description.ts new file mode 100644 index 0000000000..251f20a057 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/create/description.ts @@ -0,0 +1,79 @@ +import { + RmmProperties, +} from '../../Interfaces'; + +export const rmmCreateDescription: RmmProperties = [ + { + displayName: 'Asset ID', + name: 'assetId', + type: 'string', + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + }, + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Resolved', + name: 'resolved', + type: 'boolean', + default: false, + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/create/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/create/execute.ts new file mode 100644 index 0000000000..56e9f4235d --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/create/execute.ts @@ -0,0 +1,38 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest, +} from '../../../transport'; + + +export async function addAlert(this: IExecuteFunctions, index: number): Promise { + const customerId = this.getNodeParameter('customerId', index) as IDataObject; + const assetId = this.getNodeParameter('assetId', index) as IDataObject; + const description = this.getNodeParameter('description', index) as IDataObject; + const additionalFields = this.getNodeParameter('additionalFields', index) as IDataObject; + + const qs = {} as IDataObject; + const requestMethod = 'POST'; + const endpoint = 'rmm_alerts'; + let body = {} as IDataObject; + + if (additionalFields) { + body = additionalFields; + } + + body.customer_id = customerId; + body.asset_id = assetId; + body.description = description; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + + return this.helpers.returnJsonArray(responseData.alert); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/create/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/create/index.ts new file mode 100644 index 0000000000..386e536b3a --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/create/index.ts @@ -0,0 +1,7 @@ +import { addAlert as execute } from './execute'; +import { rmmCreateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/del/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/del/description.ts new file mode 100644 index 0000000000..48a34c8ded --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/del/description.ts @@ -0,0 +1,24 @@ +import { + RmmProperties, +} from '../../Interfaces'; + +export const rmmDeleteDescription: RmmProperties = [ + { + displayName: 'RMM Alert ID', + name: 'alertId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + description: 'Delete the RMM alert by ID', + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/del/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/del/execute.ts new file mode 100644 index 0000000000..787fa91b19 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/del/execute.ts @@ -0,0 +1,26 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest +} from '../../../transport'; + + +export async function deleteAlert(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('alertId', index) as string; + + const qs = {} as IDataObject; + const requestMethod = 'DELETE'; + const endpoint = `rmm_alerts/${id}`; + const body = {} as IDataObject; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/del/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/del/index.ts new file mode 100644 index 0000000000..38e9ec4508 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/del/index.ts @@ -0,0 +1,7 @@ +import { deleteAlert as execute } from './execute'; +import { rmmDeleteDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/get/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/get/description.ts new file mode 100644 index 0000000000..492f77824d --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/get/description.ts @@ -0,0 +1,24 @@ +import { + RmmProperties, +} from '../../Interfaces'; + +export const rmmGetDescription: RmmProperties = [ + { + displayName: 'RMM Alert ID', + name: 'alertId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: 'Get specific RMM alert by ID', + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/get/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/get/execute.ts new file mode 100644 index 0000000000..8784b2ec9c --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/get/execute.ts @@ -0,0 +1,26 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest +} from '../../../transport'; + + +export async function getAlert(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('alertId', index) as string; + + const qs = {} as IDataObject; + const requestMethod = 'GET'; + const endpoint = `rmm_alerts/${id}`; + const body = {} as IDataObject; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData.rmm_alert); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/get/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/get/index.ts new file mode 100644 index 0000000000..6313d25479 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/get/index.ts @@ -0,0 +1,7 @@ +import { getAlert as execute } from './execute'; +import { rmmGetDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/getAll/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/getAll/description.ts new file mode 100644 index 0000000000..386bb88114 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/getAll/description.ts @@ -0,0 +1,83 @@ +import { + RmmProperties, +} from '../../Interfaces'; + +export const rmmGetAllDescription: RmmProperties = [ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + noDataExpression: true, + description: 'If all results should be returned or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + default: 25, + description: 'Limit the number of rows returned', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'getAll', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Active', + value: 'active', + }, + { + name: 'All', + value: 'all', + }, + { + name: 'Resolved', + value: 'resolved', + }, + ], + default: 'all', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/getAll/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/getAll/execute.ts new file mode 100644 index 0000000000..7a740b74e8 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/getAll/execute.ts @@ -0,0 +1,43 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest, apiRequestAllItems +} from '../../../transport'; + +export async function getAll(this: IExecuteFunctions, index: number): Promise { + const returnAll = this.getNodeParameter('returnAll', index) as boolean; + const filters = this.getNodeParameter('filters', index) as IDataObject; + + let qs = {} as IDataObject; + const requestMethod = 'GET'; + const endpoint = 'rmm_alerts'; + const body = {} as IDataObject; + + if (filters) { + qs = filters; + } + + if (qs.status === undefined) { + qs.status = 'all'; + } + + if (returnAll === false) { + qs.per_page = this.getNodeParameter('limit', index) as number; + } + + let responseData; + if (returnAll) { + responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData); + } else { + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData.rmm_alerts); + } +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/getAll/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/getAll/index.ts new file mode 100644 index 0000000000..473b835756 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/getAll/index.ts @@ -0,0 +1,7 @@ +import { getAll as execute } from './execute'; +import { rmmGetAllDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/index.ts new file mode 100644 index 0000000000..49cbfc2edc --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/index.ts @@ -0,0 +1,66 @@ + +import * as get from './get'; +import * as getAll from './getAll'; +import * as create from './create'; +import * as del from './del'; +import * as mute from './mute'; + +import { INodeProperties } from 'n8n-workflow'; + +export { + getAll, + get, + mute, + del as delete, + create, +}; + + +export const descriptions = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'rmm', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create new RMM Alert', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete RMM Alert', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve RMM Alert', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all RMM Alerts', + }, + { + name: 'Mute', + value: 'mute', + description: 'Mute RMM Alert', + }, + ], + default: 'getAll', + description: '', + }, + ...getAll.description, + ...get.description, + ...create.description, + ...del.description, + ...mute.description, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/mute/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/mute/description.ts new file mode 100644 index 0000000000..2550e66359 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/mute/description.ts @@ -0,0 +1,71 @@ +import { + RmmProperties, +} from '../../Interfaces'; + +export const rmmMuteDescription: RmmProperties = [ + { + displayName: 'RMM Alert ID', + name: 'alertId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'mute', + ], + }, + }, + default: '', + description: 'Mute the RMM alert by ID', + }, + { + displayName: 'Mute Period', + name: 'muteFor', + type: 'options', + displayOptions: { + show: { + resource: [ + 'rmm', + ], + operation: [ + 'mute', + ], + }, + }, + options: [ + { + name: '1 hour', + value: '1-hour', + }, + { + name: '1 day', + value: '1-day', + }, + { + name: '2 days', + value: '2-days', + }, + { + name: '1 week', + value: '1-week', + }, + { + name: '2 weeks', + value: '2-weeks', + }, + { + name: '1 month', + value: '1-month', + }, + { + name: 'Forever', + value: 'forever', + }, + ], + default: '', + description: 'Length of time to mute alert for', + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/mute/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/mute/execute.ts new file mode 100644 index 0000000000..3cc2d5cd76 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/mute/execute.ts @@ -0,0 +1,30 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest +} from '../../../transport'; + + +export async function muteAlert(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('alertId', index) as string; + const mute = this.getNodeParameter('muteFor', index) as string; + + const qs = {} as IDataObject; + const requestMethod = 'POST'; + const endpoint = `rmm_alerts/${id}/mute`; + const body = {} as IDataObject; + + body.id = id; + body.mute_for = mute; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/mute/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/mute/index.ts new file mode 100644 index 0000000000..d38bfec5a3 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/rmm/mute/index.ts @@ -0,0 +1,7 @@ +import { muteAlert as execute } from './execute'; +import { rmmMuteDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/router.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/router.ts new file mode 100644 index 0000000000..73522cbbc5 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/router.ts @@ -0,0 +1,52 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + INodeExecutionData, NodeApiError, +} from 'n8n-workflow'; + +import * as customer from './customer'; +import * as ticket from './ticket'; +import * as contact from './contact'; +import * as rmm from './rmm'; + +import { SyncroMsp } from './Interfaces'; + +export async function router(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const operationResult: INodeExecutionData[] = []; + + for (let i = 0; i < items.length; i++) { + const resource = this.getNodeParameter('resource', i); + let operation = this.getNodeParameter('operation', i); + if (operation === 'del') { + operation = 'delete'; + } + + const syncroMsp = { + resource, + operation, + } as SyncroMsp; + + try { + if (syncroMsp.resource === 'customer') { + operationResult.push(...await customer[syncroMsp.operation].execute.call(this, i)); + } else if (syncroMsp.resource === 'ticket') { + operationResult.push(...await ticket[syncroMsp.operation].execute.call(this, i)); + } else if (syncroMsp.resource === 'contact') { + operationResult.push(...await contact[syncroMsp.operation].execute.call(this, i)); + } else if (syncroMsp.resource === 'rmm') { + operationResult.push(...await rmm[syncroMsp.operation].execute.call(this, i)); + } + } catch (err) { + if (this.continueOnFail()) { + operationResult.push({ json: this.getInputData(i)[0].json, error: err }); + } else { + throw new NodeApiError(this.getNode(), err); + } + } + } + + return [operationResult]; +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/syncromsp.png b/packages/nodes-base/nodes/SyncroMSP/v1/actions/syncromsp.png new file mode 100644 index 0000000000..a6b3f1bf0a Binary files /dev/null and b/packages/nodes-base/nodes/SyncroMSP/v1/actions/syncromsp.png differ diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/create/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/create/description.ts new file mode 100644 index 0000000000..8664ea5988 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/create/description.ts @@ -0,0 +1,144 @@ +import { + TicketProperties, +} from '../../Interfaces'; + +export const ticketCreateDescription: TicketProperties = [ + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Asset ID', + name: 'assetId', + type: 'string', + default: '', + }, + { + displayName: 'Assign to Contact', + name: 'contactId', + type: 'string', + default: '', + description: 'The ID of the contact you want to assign the ticket to', + }, + // { + // displayName: 'Due Date', + // name: 'dueDate', + // type: 'dateTime', + // default: '', + // }, + { + displayName: 'Issue Type', + name: 'issueType', + type: 'options', + options: [ + { + name: 'Contract Work', + value: 'Contract Work', + }, + { + name: 'Network Project', + value: 'Network Project', + }, + { + name: 'Other', + value: 'Other', + }, + { + name: 'Regular Maintenance', + value: 'Regular Maintenance', + }, + { + name: 'Remote Support', + value: 'Remote Support', + }, + ], + default: '', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Customer Reply', + value: 'Customer Reply', + }, + { + name: 'In Progress', + value: 'In Progress', + }, + { + name: 'New', + value: 'New', + }, + { + name: 'Resolved', + value: 'Resolved', + }, + { + name: 'Scheduled', + value: 'Scheduled', + }, + { + name: 'Waiting for Parts', + value: 'Waiting for Parts', + }, + { + name: 'Waiting on Customer', + value: 'Waiting on Customer', + }, + + ], + default: 'New', + description: 'If used along the parameter Search Query, only Search Query will be applied', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/create/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/create/execute.ts new file mode 100644 index 0000000000..8543ae4fc0 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/create/execute.ts @@ -0,0 +1,40 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest, +} from '../../../transport'; + +export async function createTicket(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('customerId', index) as IDataObject; + const subject = this.getNodeParameter('subject', index) as IDataObject; + const { assetId, dueDate, issueType, status, contactId } = this.getNodeParameter('additionalFields', index) as IDataObject; + + const qs = {} as IDataObject; + const requestMethod = 'POST'; + const endpoint = 'tickets'; + let body = {} as IDataObject; + + body = { + asset_id: assetId, + //due_date: dueDate, + problem_type: issueType, + status, + contact_id: contactId, + }; + + body.customer_id = id; + body.subject = subject; + + let responseData; + + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + + return this.helpers.returnJsonArray(responseData.ticket); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/create/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/create/index.ts new file mode 100644 index 0000000000..e0423e0b0d --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/create/index.ts @@ -0,0 +1,7 @@ +import { createTicket as execute } from './execute'; +import { ticketCreateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/del/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/del/description.ts new file mode 100644 index 0000000000..90453c9673 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/del/description.ts @@ -0,0 +1,24 @@ +import { + TicketProperties, +} from '../../Interfaces'; + +export const ticketDeleteDescription: TicketProperties = [ + { + displayName: 'Ticket ID', + name: 'ticketId', + required: true, + type: 'string', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + description: 'Delete a specific customer by ID', + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/del/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/del/execute.ts new file mode 100644 index 0000000000..a646e9f52a --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/del/execute.ts @@ -0,0 +1,26 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest +} from '../../../transport'; + + +export async function deleteTicket(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('ticketId', index) as string; + + const qs = {} as IDataObject; + const requestMethod = 'DELETE'; + const endpoint = `tickets/${id}`; + const body = {} as IDataObject; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/del/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/del/index.ts new file mode 100644 index 0000000000..b30a627649 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/del/index.ts @@ -0,0 +1,7 @@ +import { deleteTicket as execute } from './execute'; +import { ticketDeleteDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/get/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/get/description.ts new file mode 100644 index 0000000000..fa220e6e11 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/get/description.ts @@ -0,0 +1,23 @@ +import { + TicketProperties, +} from '../../Interfaces'; + +export const ticketGetDescription: TicketProperties = [ + { + displayName: 'Ticket ID', + name: 'ticketId', + type: 'string', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: 'Get specific customer by ID', + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/get/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/get/execute.ts new file mode 100644 index 0000000000..b87f461769 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/get/execute.ts @@ -0,0 +1,26 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest +} from '../../../transport'; + + +export async function getTicket(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('ticketId', index) as string; + + const qs = {} as IDataObject; + const requestMethod = 'GET'; + const endpoint = `tickets/${id}`; + const body = {} as IDataObject; + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData.ticket); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/get/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/get/index.ts new file mode 100644 index 0000000000..8987de0091 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/get/index.ts @@ -0,0 +1,7 @@ +import { getTicket as execute } from './execute'; +import { ticketGetDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/getAll/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/getAll/description.ts new file mode 100644 index 0000000000..d60fbcc7b7 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/getAll/description.ts @@ -0,0 +1,107 @@ +import { + TicketProperties, +} from '../../Interfaces'; + +export const ticketGetAllDescription: TicketProperties = [ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + noDataExpression: true, + description: 'If all results should be returned or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + default: 25, + description: 'Limit the number of rows returned', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'getAll', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Search Query', + name: 'query', + type: 'string', + default: '', + placeholder: 'John Doe', + description: 'Search query, it can be anything related to ticket data like user etc.', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Customer Reply', + value: 'Customer Reply', + }, + { + name: 'In Progress', + value: 'In Progress', + }, + { + name: 'New', + value: 'New', + }, + { + name: 'Resolved', + value: 'Resolved', + }, + { + name: 'Scheduled', + value: 'Scheduled', + }, + { + name: 'Waiting for Parts', + value: 'Waiting for Parts', + }, + { + name: 'Waiting on Customer', + value: 'Waiting on Customer', + }, + ], + default: 'New', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/getAll/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/getAll/execute.ts new file mode 100644 index 0000000000..ff29e46e39 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/getAll/execute.ts @@ -0,0 +1,40 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, +} from 'n8n-workflow'; + +import { + apiRequest, apiRequestAllItems +} from '../../../transport'; + + +export async function getAll(this: IExecuteFunctions, index: number): Promise { + const returnAll = this.getNodeParameter('returnAll', index) as boolean; + const filters = this.getNodeParameter('filters', index) as IDataObject; + + let qs = {} as IDataObject; + const requestMethod = 'GET'; + const endpoint = 'tickets'; + const body = {} as IDataObject; + + if (filters) { + qs = filters; + } + + if (returnAll === false) { + qs.per_page = this.getNodeParameter('limit', index) as number; + } + + let responseData; + if (returnAll) { + responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData); + } else { + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + return this.helpers.returnJsonArray(responseData.tickets); + } +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/getAll/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/getAll/index.ts new file mode 100644 index 0000000000..3a74352f14 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/getAll/index.ts @@ -0,0 +1,7 @@ +import { getAll as execute } from './execute'; +import { ticketGetAllDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/index.ts new file mode 100644 index 0000000000..afd2d9ea79 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/index.ts @@ -0,0 +1,65 @@ + +import * as getAll from './getAll'; +import * as create from './create'; +import * as get from './get'; +import * as del from './del'; +import * as update from './update'; + +import { INodeProperties } from 'n8n-workflow'; + +export { + getAll, + create, + get, + del as delete, + update, +}; + +export const descriptions = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create new ticket', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete ticket', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve ticket', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all tickets', + }, + { + name: 'Update', + value: 'update', + description: 'Update ticket', + }, + ], + default: 'getAll', + description: '', + }, + ...getAll.description, + ...create.description, + ...get.description, + ...del.description, + ...update.description, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/update/description.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/update/description.ts new file mode 100644 index 0000000000..72549038be --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/update/description.ts @@ -0,0 +1,137 @@ +import { + TicketProperties, +} from '../../Interfaces'; + +export const ticketUpdateDescription: TicketProperties = [ + { + displayName: 'Ticket ID', + name: 'ticketId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'update', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Asset ID', + name: 'assetId', + type: 'string', + default: '', + }, + { + displayName: 'Assign to Contact', + name: 'contactId', + type: 'string', + default: '', + description: 'The ID of the contact you want to assign the ticket to', + }, + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + default: '', + }, + { + displayName: 'Due Date', + name: 'dueDate', + type: 'dateTime', + default: '', + }, + { + displayName: 'Issue Type', + name: 'issueType', + type: 'options', + options: [ + { + name: 'Contract Work', + value: 'Contract Work', + }, + { + name: 'Network Project', + value: 'Network Project', + }, + { + name: 'Other', + value: 'Other', + }, + { + name: 'Regular Maintenance', + value: 'Regular Maintenance', + }, + { + name: 'Remote Support', + value: 'Remote Support', + }, + ], + default: '', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Customer Reply', + value: 'Customer Reply', + }, + { + name: 'In Progress', + value: 'In Progress', + }, + { + name: 'New', + value: 'New', + }, + { + name: 'Resolved', + value: 'Resolved', + }, + { + name: 'Scheduled', + value: 'Scheduled', + }, + { + name: 'Waiting for Parts', + value: 'Waiting for Parts', + }, + { + name: 'Waiting on Customer', + value: 'Waiting on Customer', + }, + ], + default: 'New', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + default: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/update/execute.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/update/execute.ts new file mode 100644 index 0000000000..eb926abecb --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/update/execute.ts @@ -0,0 +1,45 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + NodeOperationError, +} from 'n8n-workflow'; + +import { + apiRequest, +} from '../../../transport'; + + +export async function updateTicket(this: IExecuteFunctions, index: number): Promise { + const id = this.getNodeParameter('ticketId', index) as IDataObject; + const { assetId, customerId, dueDate, issueType, status, subject, ticketType, contactId } = this.getNodeParameter('updateFields', index) as IDataObject; + + + const qs = {} as IDataObject; + const requestMethod = 'PUT'; + const endpoint = `tickets/${id}`; + let body = {} as IDataObject; + + body = { + ...(assetId && { asset_id: assetId }), + ...(customerId && { customer_id: customerId }), + ...(dueDate && { due_date: dueDate }), + ...(issueType && { problem_type: issueType }), + ...(status && { status }), + ...(subject && { subject }), + ...(ticketType && { ticket_type: ticketType }), + ...(contactId && { contact_id: contactId }), + }; + + if (!Object.keys(body).length) { + throw new NodeOperationError(this.getNode(), 'At least one update fields has to be defined'); + } + + let responseData; + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + + return this.helpers.returnJsonArray(responseData.ticket); +} diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/update/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/update/index.ts new file mode 100644 index 0000000000..96e02e7207 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/ticket/update/index.ts @@ -0,0 +1,7 @@ +import { updateTicket as execute } from './execute'; +import { ticketUpdateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/actions/versionDescription.ts b/packages/nodes-base/nodes/SyncroMSP/v1/actions/versionDescription.ts new file mode 100644 index 0000000000..d836c93a05 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/actions/versionDescription.ts @@ -0,0 +1,61 @@ +import { + INodeTypeDescription, +} from 'n8n-workflow'; + +import * as customer from './customer'; +import * as ticket from './ticket'; +import * as contact from './contact'; +import * as rmm from './rmm'; + +export const versionDescription: INodeTypeDescription = { + displayName: 'SyncroMSP', + name: 'syncroMsp', + icon: 'file:syncromsp.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Gets data from SyncroMSP', + defaults: { + name: 'SyncroMSP', + color: '#08a4ab', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'syncroMspApi', + required: true, + testedBy: 'syncroMspApiCredentialTest', + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Contact', + value: 'contact', + }, + { + name: 'Customer', + value: 'customer', + }, + { + name: 'RMM', + value: 'rmm', + }, + { + name: 'Ticket', + value: 'ticket', + }, + ], + default: 'contact', + }, + ...customer.descriptions, + ...ticket.descriptions, + ...contact.descriptions, + ...rmm.descriptions, + ], +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/methods/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/methods/index.ts new file mode 100644 index 0000000000..65ff6192a3 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/methods/index.ts @@ -0,0 +1 @@ +export * as loadOptions from './loadOptions'; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/methods/loadOptions.ts b/packages/nodes-base/nodes/SyncroMSP/v1/methods/loadOptions.ts new file mode 100644 index 0000000000..2cc28e2156 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/methods/loadOptions.ts @@ -0,0 +1,38 @@ +import { + IDataObject, + ILoadOptionsFunctions, + INodePropertyOptions, + NodeOperationError, +} from 'n8n-workflow'; + +import { + apiRequestAllItems, +} from '../transport'; + +// Get all the available channels + +export async function getCustomers(this: ILoadOptionsFunctions): Promise { + const endpoint = 'customers'; + const responseData = await apiRequestAllItems.call(this, 'GET', endpoint, {}); + + if (responseData === undefined) { + throw new NodeOperationError(this.getNode(), 'No data got returned'); + } + + const returnData: INodePropertyOptions[] = []; + for (const data of responseData) { + returnData.push({ + name: data.fullname as string, + value: data.id as number, + }); + } + + returnData.sort((a, b) => { + if (a.name < b.name) { return -1; } + if (a.name > b.name) { return 1; } + return 0; + }); + + return returnData; +} + diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/methods/sharedFields.ts b/packages/nodes-base/nodes/SyncroMSP/v1/methods/sharedFields.ts new file mode 100644 index 0000000000..2afea1f686 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/methods/sharedFields.ts @@ -0,0 +1,49 @@ +import { + INodeProperties +} from 'n8n-workflow'; + +export const addressFixedCollection: INodeProperties = { + displayName: 'Address', + name: 'address', + placeholder: 'Add Address Fields', + type: 'fixedCollection', + default: {}, + options: [ + { + displayName: 'Address Fields', + name: 'addressFields', + values: [ + { + displayName: 'Line 1', + name: 'address', + type: 'string', + default: '', + }, + { + displayName: 'Line 2', + name: 'address2', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'ZIP', + name: 'zip', + type: 'string', + default: '', + }, + ], + }, + ], +}; diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/syncromsp.png b/packages/nodes-base/nodes/SyncroMSP/v1/syncromsp.png new file mode 100644 index 0000000000..a6b3f1bf0a Binary files /dev/null and b/packages/nodes-base/nodes/SyncroMSP/v1/syncromsp.png differ diff --git a/packages/nodes-base/nodes/SyncroMSP/v1/transport/index.ts b/packages/nodes-base/nodes/SyncroMSP/v1/transport/index.ts new file mode 100644 index 0000000000..228f5033a7 --- /dev/null +++ b/packages/nodes-base/nodes/SyncroMSP/v1/transport/index.ts @@ -0,0 +1,93 @@ +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + GenericValue, + ICredentialDataDecryptedObject, + ICredentialTestFunctions, + IDataObject, + IHttpRequestOptions, + NodeApiError, + NodeOperationError, +} from 'n8n-workflow'; + +/** + * Make an API request to Mattermost + */ +export async function apiRequest( + this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, + method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD', + endpoint: string, + body: IDataObject | GenericValue | GenericValue[] = {}, + query: IDataObject = {}, +) { + const credentials = await this.getCredentials('syncroMspApi'); + + if (!credentials) { + throw new NodeOperationError(this.getNode(), 'No credentials returned!'); + } + + query['api_key'] = credentials.apiKey; + + const options: IHttpRequestOptions = { + method, + body, + qs: query, + url: `https://${credentials.subdomain}.syncromsp.com/api/v1/${endpoint}`, + headers: {}, + }; + + try { + return await this.helpers.httpRequest(options); + } catch (error) { + throw new NodeApiError(this.getNode(), error); + } +} + +export async function apiRequestAllItems( + this: IExecuteFunctions | ILoadOptionsFunctions, + method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD', + endpoint: string, + body: IDataObject = {}, + query: IDataObject = {}, +) { + + let returnData: IDataObject[] = []; + + let responseData; + query.page = 1; + + do { + responseData = await apiRequest.call(this, method, endpoint, body, query); + query.page++; + returnData = returnData.concat(responseData[endpoint]); + } while ( + responseData[endpoint].length !== 0 + ); + return returnData; +} + +export async function validateCredentials(this: ICredentialTestFunctions, decryptedCredentials: ICredentialDataDecryptedObject): Promise { // tslint:disable-line:no-any + const credentials = decryptedCredentials; + + const { + subdomain, + apiKey, + } = credentials as { + subdomain: string, + apiKey: string, + }; + + const options: IHttpRequestOptions = { + method: 'GET', + qs: { + api_key: apiKey, + }, + url: `https://${subdomain}.syncromsp.com/api/v1//me`, + }; + + return this.helpers.request(options); +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 1c9b15b45d..2e62794fd9 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -272,6 +272,7 @@ "dist/credentials/StripeApi.credentials.js", "dist/credentials/SurveyMonkeyApi.credentials.js", "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js", + "dist/credentials/SyncroMspApi.credentials.js", "dist/credentials/TaigaApi.credentials.js", "dist/credentials/TapfiliateApi.credentials.js", "dist/credentials/TelegramApi.credentials.js", @@ -605,6 +606,7 @@ "dist/nodes/Stripe/StripeTrigger.node.js", "dist/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.js", "dist/nodes/Switch/Switch.node.js", + "dist/nodes/SyncroMSP/SyncroMsp.node.js", "dist/nodes/Taiga/Taiga.node.js", "dist/nodes/Taiga/TaigaTrigger.node.js", "dist/nodes/Tapfiliate/Tapfiliate.node.js",