UNPKG

n8n-nodes-innotes

Version:

N8N node for InNotes CRM API integration

805 lines (803 loc) 37.4 kB
"use strict"; /** * @author Marco * @date 2025-06-28 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.InNotes = void 0; const n8n_workflow_1 = require("n8n-workflow"); const ContactDescription_1 = require("../../descriptions/ContactDescription"); const NoteDescription_1 = require("../../descriptions/NoteDescription"); const JobDescription_1 = require("../../descriptions/JobDescription"); const StatusDescription_1 = require("../../descriptions/StatusDescription"); const TagDescription_1 = require("../../descriptions/TagDescription"); const UserDescription_1 = require("../../descriptions/UserDescription"); class InNotes { constructor() { this.description = { displayName: 'InNotes', name: 'inNotes', icon: 'file:Logo_128.png', group: ['output'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume InNotes CRM API', defaults: { name: 'InNotes', }, inputs: ["main" /* NodeConnectionType.Main */], outputs: ["main" /* NodeConnectionType.Main */], credentials: [ { name: 'inNotesApi', required: true, }, ], properties: [ { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Contact', value: 'contact', }, { name: 'Note', value: 'note', }, { name: 'Job', value: 'job', }, { name: 'Status', value: 'status', }, { name: 'Tag', value: 'tag', }, { name: 'User', value: 'user', }, ], default: 'contact', }, ...ContactDescription_1.contactOperations, ...NoteDescription_1.noteOperations, ...JobDescription_1.jobOperations, ...StatusDescription_1.statusOperations, ...TagDescription_1.tagOperations, ...UserDescription_1.userOperations, ...ContactDescription_1.contactFields, ...NoteDescription_1.noteFields, ...JobDescription_1.jobFields, ...StatusDescription_1.statusFields, ...TagDescription_1.tagFields, ...UserDescription_1.userFields, { displayName: 'Options', name: 'options', type: 'collection', placeholder: 'Add Option', default: {}, options: [ { displayName: 'Batch Size', name: 'batchSize', type: 'number', default: 10, description: 'Number of items to process in each batch (to avoid rate limiting)', typeOptions: { minValue: 1, maxValue: 100, }, }, { displayName: 'Timeout Between Batches (ms)', name: 'timeoutBetweenBatches', type: 'number', default: 1000, description: 'Delay in milliseconds between processing batches (to avoid rate limiting)', typeOptions: { minValue: 0, maxValue: 30000, }, }, ], }, ], }; this.methods = { loadOptions: { async jobStatuses() { try { const credentials = await this.getCredentials('inNotesApi'); const baseUrl = credentials.baseUrl; const response = await this.helpers.httpRequestWithAuthentication.call(this, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/statuses`, qs: { type: 'job' }, headers: { Accept: 'application/json', }, }); const statuses = Array.isArray(response) ? response : [response]; return statuses.map((status) => ({ name: status.name, value: status.name, })); } catch (error) { throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: `Failed to load job statuses: ${error.message}`, }); } }, }, }; } async execute() { const items = this.getInputData(); const returnData = []; const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); // Get batch options const options = this.getNodeParameter('options', 0, {}); const batchSize = options.batchSize || 10; const timeoutBetweenBatches = options.timeoutBetweenBatches || 1000; // Create a node instance to access the methods const nodeInstance = new InNotes(); // Helper function to add delay between batches const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); // Process items in batches for (let batchStart = 0; batchStart < items.length; batchStart += batchSize) { const batchEnd = Math.min(batchStart + batchSize, items.length); const currentBatch = items.slice(batchStart, batchEnd); // Process current batch for (let i = 0; i < currentBatch.length; i++) { const actualIndex = batchStart + i; try { let responseData = {}; if (resource === 'contact') { responseData = await nodeInstance.executeContactOperation(this, actualIndex, operation); } else if (resource === 'note') { responseData = await nodeInstance.executeNoteOperation(this, actualIndex, operation); } else if (resource === 'job') { responseData = await nodeInstance.executeJobOperation(this, actualIndex, operation); } else if (resource === 'status') { responseData = await nodeInstance.executeStatusOperation(this, actualIndex, operation); } else if (resource === 'tag') { responseData = await nodeInstance.executeTagOperation(this, actualIndex, operation); } else if (resource === 'user') { responseData = await nodeInstance.executeUserOperation(this, actualIndex, operation); } if (Array.isArray(responseData)) { returnData.push(...responseData); } else { returnData.push(responseData); } } catch (error) { if (this.continueOnFail()) { returnData.push({ error: error.message }); continue; } throw error; } } // Add delay between batches (except after the last batch) if (batchEnd < items.length && timeoutBetweenBatches > 0) { await delay(timeoutBetweenBatches); } } return [this.helpers.returnJsonArray(returnData)]; } async executeContactOperation(executeFunctions, itemIndex, operation) { const credentials = await executeFunctions.getCredentials('inNotesApi'); const baseUrl = credentials.baseUrl; if (operation === 'create') { const name = executeFunctions.getNodeParameter('name', itemIndex); const linkedin_key = executeFunctions.getNodeParameter('linkedin_key', itemIndex); const linkedin_user = executeFunctions.getNodeParameter('linkedin_user', itemIndex, ''); const location = executeFunctions.getNodeParameter('location', itemIndex, ''); const current_company = executeFunctions.getNodeParameter('current_company', itemIndex, ''); const picture_url = executeFunctions.getNodeParameter('picture_url', itemIndex, ''); const tags = executeFunctions.getNodeParameter('tags', itemIndex, ''); const status_id = executeFunctions.getNodeParameter('status_id', itemIndex, ''); const body = { name, linkedin_key, ...(linkedin_user && { linkedin_user }), ...(location && { location }), ...(current_company && { current_company }), ...(picture_url && { picture_url }), ...(tags && { tags: tags.split(',').map((tag) => tag.trim()) }), ...(status_id && { status_id }), }; const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'POST', url: `${baseUrl}/api/contacts`, body, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); return response; } if (operation === 'get') { const contactId = executeFunctions.getNodeParameter('contactId', itemIndex); const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/contacts/${contactId}`, headers: { Accept: 'application/json', }, }); return response; } if (operation === 'getAll') { const returnAll = executeFunctions.getNodeParameter('returnAll', itemIndex, false); const search = executeFunctions.getNodeParameter('search', itemIndex, ''); const tags = executeFunctions.getNodeParameter('tags', itemIndex, ''); const qs = {}; if (search) qs.search = search; if (tags) qs.tags = tags; if (!returnAll) { const limit = executeFunctions.getNodeParameter('limit', itemIndex, 24); qs.pageSize = limit; qs.page = 1; } const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/contacts`, qs, headers: { Accept: 'application/json', }, }); return Array.isArray(response) ? response : [response]; } if (operation === 'update') { const contactId = executeFunctions.getNodeParameter('contactId', itemIndex); const updateFields = executeFunctions.getNodeParameter('updateFields', itemIndex); const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'PUT', url: `${baseUrl}/api/contacts/${contactId}`, body: updateFields, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); return response; } if (operation === 'delete') { const contactId = executeFunctions.getNodeParameter('contactId', itemIndex); await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'DELETE', url: `${baseUrl}/api/contacts/${contactId}`, headers: { Accept: 'application/json', }, }); return { success: true }; } throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `The operation "${operation}" is not supported for resource "contact"!`, }); } async executeNoteOperation(executeFunctions, itemIndex, operation) { const credentials = await executeFunctions.getCredentials('inNotesApi'); const baseUrl = credentials.baseUrl; if (operation === 'create') { const content = executeFunctions.getNodeParameter('content', itemIndex); const contactId = executeFunctions.getNodeParameter('contactId', itemIndex); const visibility = executeFunctions.getNodeParameter('visibility', itemIndex, ''); const ext_table_name = executeFunctions.getNodeParameter('ext_table_name', itemIndex, ''); const body = { content, contact_id: contactId, ...(visibility && { visibility }), ...(ext_table_name && { ext_table_name }), }; const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'POST', url: `${baseUrl}/api/note`, body, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); return response; } if (operation === 'get') { const noteId = executeFunctions.getNodeParameter('noteId', itemIndex); const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/note/${noteId}`, headers: { Accept: 'application/json', }, }); return response; } if (operation === 'getAll') { const contactId = executeFunctions.getNodeParameter('contactId', itemIndex); const returnAll = executeFunctions.getNodeParameter('returnAll', itemIndex, false); const qs = { contact_id: contactId, }; if (!returnAll) { const limit = executeFunctions.getNodeParameter('limit', itemIndex, 24); qs.pageSize = limit; qs.page = 1; } const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/note`, qs, headers: { Accept: 'application/json', }, }); return Array.isArray(response) ? response : [response]; } if (operation === 'update') { const noteId = executeFunctions.getNodeParameter('noteId', itemIndex); const updateFields = executeFunctions.getNodeParameter('updateFields', itemIndex); const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'PUT', url: `${baseUrl}/api/note/${noteId}`, body: updateFields, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); return response; } if (operation === 'delete') { const noteId = executeFunctions.getNodeParameter('noteId', itemIndex); await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'DELETE', url: `${baseUrl}/api/note/${noteId}`, headers: { Accept: 'application/json', }, }); return { success: true }; } throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `The operation "${operation}" is not supported for resource "note"!`, }); } async executeJobOperation(executeFunctions, itemIndex, operation) { const credentials = await executeFunctions.getCredentials('inNotesApi'); const baseUrl = credentials.baseUrl; if (operation === 'create') { const name = executeFunctions.getNodeParameter('name', itemIndex); const company_name = executeFunctions.getNodeParameter('company_name', itemIndex); const status = executeFunctions.getNodeParameter('status', itemIndex, ''); const description = executeFunctions.getNodeParameter('description', itemIndex, ''); const location = executeFunctions.getNodeParameter('location', itemIndex, ''); const remote_setting = executeFunctions.getNodeParameter('remote_setting', itemIndex, ''); const company_url = executeFunctions.getNodeParameter('company_url', itemIndex, ''); const ext_id = executeFunctions.getNodeParameter('ext_id', itemIndex, ''); const picture_url = executeFunctions.getNodeParameter('picture_url', itemIndex, ''); const tags = executeFunctions.getNodeParameter('tags', itemIndex, ''); let status_id; // If status is provided, lookup the status ID if (status) { try { // Get all job statuses const statusResponse = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/statuses`, qs: { type: 'job' }, headers: { Accept: 'application/json', }, }); // Find matching status by name (case-insensitive) const statuses = Array.isArray(statusResponse) ? statusResponse : [statusResponse]; const matchingStatus = statuses.find((s) => s.name && s.name.toLowerCase() === status.toLowerCase()); if (matchingStatus) { status_id = matchingStatus.id.toString(); } else { throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Status "${status}" not found. Available statuses: ${statuses.map((s) => s.name).join(', ')}`, }); } } catch (error) { if (error instanceof n8n_workflow_1.NodeApiError) { throw error; } throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to lookup status "${status}": ${error.message}`, }); } } const body = { name, company_name, status, ...(description && { description }), ...(location && { location }), ...(remote_setting && { remote_setting }), ...(company_url && { company_url }), ...(status_id && { status_id }), ...(ext_id && { ext_id }), ...(picture_url && { picture_url }), ...(tags && { tags: tags.split(',').map((tag) => tag.trim()) }), }; const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'POST', url: `${baseUrl}/api/job`, body, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); return response; } if (operation === 'get') { const jobId = executeFunctions.getNodeParameter('jobId', itemIndex); const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/job/${jobId}`, headers: { Accept: 'application/json', }, }); return response; } if (operation === 'getAll') { const returnAll = executeFunctions.getNodeParameter('returnAll', itemIndex, false); const search = executeFunctions.getNodeParameter('search', itemIndex, ''); const tags = executeFunctions.getNodeParameter('tags', itemIndex, ''); const qs = {}; if (search) qs.search = search; if (tags) qs.tags = tags; if (!returnAll) { const limit = executeFunctions.getNodeParameter('limit', itemIndex, 24); qs.pageSize = limit; qs.page = 1; } const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/job`, qs, headers: { Accept: 'application/json', }, }); return Array.isArray(response) ? response : [response]; } if (operation === 'update') { const jobId = executeFunctions.getNodeParameter('jobId', itemIndex); const updateFields = executeFunctions.getNodeParameter('updateFields', itemIndex); // Handle status string lookup if provided if (updateFields.status && typeof updateFields.status === 'string') { try { // Get all job statuses const statusResponse = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/statuses`, qs: { type: 'job' }, headers: { Accept: 'application/json', }, }); // Find matching status by name (case-insensitive) const statuses = Array.isArray(statusResponse) ? statusResponse : [statusResponse]; const matchingStatus = statuses.find((s) => s.name && s.name.toLowerCase() === updateFields.status.toLowerCase()); if (matchingStatus) { // Replace status with status_id and remove status field updateFields.status_id = matchingStatus.id.toString(); delete updateFields.status; } else { throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Status "${updateFields.status}" not found. Available statuses: ${statuses.map((s) => s.name).join(', ')}`, }); } } catch (error) { if (error instanceof n8n_workflow_1.NodeApiError) { throw error; } throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to lookup status "${updateFields.status}": ${error.message}`, }); } } // Process tags if provided as comma-separated string if (updateFields.tags && typeof updateFields.tags === 'string') { updateFields.tags = (updateFields.tags).split(',').map((tag) => tag.trim()); } const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'PUT', url: `${baseUrl}/api/job/${jobId}`, body: updateFields, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); return response; } if (operation === 'delete') { const jobId = executeFunctions.getNodeParameter('jobId', itemIndex); await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'DELETE', url: `${baseUrl}/api/job/${jobId}`, headers: { Accept: 'application/json', }, }); return { success: true }; } if (operation === 'search') { const searchMethod = executeFunctions.getNodeParameter('searchMethod', itemIndex); const searchQuery = executeFunctions.getNodeParameter('searchQuery', itemIndex); const searchOptions = executeFunctions.getNodeParameter('searchOptions', itemIndex, {}); const qs = {}; // Handle different search methods switch (searchMethod) { case 'general': qs.searchTerm = searchQuery; break; case 'ext_id': qs.ext_id = searchQuery; break; case 'title': qs.searchTerm = searchQuery; qs.searchField = 'title'; break; case 'company': qs.searchTerm = searchQuery; qs.searchField = 'company'; break; case 'location': qs.searchTerm = searchQuery; qs.searchField = 'location'; break; case 'description': qs.searchTerm = searchQuery; qs.searchField = 'description'; break; default: qs.searchTerm = searchQuery; } // Add additional filter parameters if (searchOptions.remote_setting) qs.remote_setting = searchOptions.remote_setting; if (searchOptions.status) qs.status = searchOptions.status; if (searchOptions.tags) qs.tags = searchOptions.tags; // Set limit with default value const limit = searchOptions.limit || 24; qs.pageSize = limit; qs.page = 1; const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/job`, qs, headers: { Accept: 'application/json', }, }); return Array.isArray(response) ? response : [response]; } if (operation === 'exists') { const id = executeFunctions.getNodeParameter('id', itemIndex); try { // First try to get by job ID try { await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/job/${id}`, headers: { Accept: 'application/json', }, }); // If we get here, the job exists by ID return { exists: true, found_by: 'job_id', id }; } catch (error) { // Job not found by ID, try searching by ext_id const searchResponse = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/job`, qs: { ext_id: id }, headers: { Accept: 'application/json', }, }); // Check if any results were returned const results = Array.isArray(searchResponse) ? searchResponse : [searchResponse]; if (results.length > 0 && results[0]) { return { exists: true, found_by: 'ext_id', id }; } // Not found by either ID or ext_id return { exists: false, id }; } } catch (error) { // If there's an error during the search, return false return { exists: false, id, error: error.message }; } } throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `The operation "${operation}" is not supported for resource "job"!`, }); } async executeStatusOperation(executeFunctions, itemIndex, operation) { const credentials = await executeFunctions.getCredentials('inNotesApi'); const baseUrl = credentials.baseUrl; if (operation === 'create') { const name = executeFunctions.getNodeParameter('name', itemIndex); const type = executeFunctions.getNodeParameter('type', itemIndex); const color = executeFunctions.getNodeParameter('color', itemIndex, ''); const body = { name, type, ...(color && { color }), }; const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'POST', url: `${baseUrl}/api/statuses`, body, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); return response; } if (operation === 'getAll') { const type = executeFunctions.getNodeParameter('type', itemIndex, 'contact'); const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/statuses`, qs: { type }, headers: { Accept: 'application/json', }, }); return Array.isArray(response) ? response : [response]; } if (operation === 'update') { const statusId = executeFunctions.getNodeParameter('statusId', itemIndex); const updateFields = executeFunctions.getNodeParameter('updateFields', itemIndex); const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'PUT', url: `${baseUrl}/api/statuses/${statusId}`, body: updateFields, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); return response; } if (operation === 'delete') { const statusId = executeFunctions.getNodeParameter('statusId', itemIndex); await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'DELETE', url: `${baseUrl}/api/statuses/${statusId}`, headers: { Accept: 'application/json', }, }); return { success: true }; } throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `The operation "${operation}" is not supported for resource "status"!`, }); } async executeTagOperation(executeFunctions, _itemIndex, operation) { const credentials = await executeFunctions.getCredentials('inNotesApi'); const baseUrl = credentials.baseUrl; if (operation === 'getAll') { const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/tags`, headers: { Accept: 'application/json', }, }); return Array.isArray(response) ? response : [response]; } throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `The operation "${operation}" is not supported for resource "tag"! Supported operations: getAll`, }); } async executeUserOperation(executeFunctions, itemIndex, operation) { const credentials = await executeFunctions.getCredentials('inNotesApi'); const baseUrl = credentials.baseUrl; if (operation === 'get') { const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/user`, headers: { Accept: 'application/json', }, }); return response; } if (operation === 'getCv') { const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/user`, headers: { Accept: 'application/json', }, }); // Return only CV-related fields const { cv, job_preferences, cv_updated_at } = response; // Parse job_preferences if it's a JSON string let parsedPreferences = null; let searchJobTitles = []; let locations = []; if (job_preferences) { try { parsedPreferences = typeof job_preferences === 'string' ? JSON.parse(job_preferences) : job_preferences; searchJobTitles = parsedPreferences.searchJobTitles || []; locations = parsedPreferences.locations || []; } catch (error) { // If parsing fails, keep empty arrays } } return { cv, job_preferences: parsedPreferences, cv_updated_at, searchJobTitles: searchJobTitles.join(', '), locations: locations.join(', '), }; } if (operation === 'update') { const updateFields = executeFunctions.getNodeParameter('updateFields', itemIndex); const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'PUT', url: `${baseUrl}/api/user`, body: updateFields, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); return response; } throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `The operation "${operation}" is not supported for resource "user"! Supported operations: get, getCv, update`, }); } } exports.InNotes = InNotes; //# sourceMappingURL=InNotes.node.js.map