UNPKG

n8n-nodes-innotes

Version:

N8N node for InNotes CRM API integration

1,038 lines 53.2 kB
"use strict"; /** * @author Marco * @date 2025-06-28 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.InNotes = void 0; exports.contactDetailsFromCollection = contactDetailsFromCollection; 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"); const AutomationDescription_1 = require("../../descriptions/AutomationDescription"); /** * Turn the "Phone & Email" fixedCollection into the array the API takes. * * n8n hands a fixedCollection over as `{ entry: [{...}, ...] }`, and the update * operation posts its collection of fields straight through as the request body * — so without this the API would receive an object where it requires an array * and answer 400, for a field the user filled in correctly. * * Returns undefined when the field was never used, which leaves the stored * recapiti alone. Rows that were configured but are all unusable raise, on * update only — see `raiseWhenUnusable`. * * Undefined and NOT an empty array, deliberately. An empty array is a request to * DELETE every number and address the contact has, and a workflow run before its * upstream data arrived would quietly empty the field on a live contact and * answer 200. Clearing is not expressible through this node; the REST route and * the MCP tool both take an explicit `[]` for anyone who means it. * Author: Marco, August 10, 2026 */ function contactDetailsFromCollection(raw, raiseWhenUnusable = false) { if (raw === undefined || raw === null) return undefined; // A fixedCollection can be set to an expression, and `{{ $json.contact_details }}` // carrying the API's own shape arrives as a bare array rather than {entry: [...]}. // Reading only `.entry` there returns undefined and writes nothing, under a 200. const entries = Array.isArray(raw) ? raw : raw.entry; if (!Array.isArray(entries) || entries.length === 0) return undefined; const details = entries .map((entry) => { const row = entry; const label = typeof row.label === 'string' ? row.label.trim() : ''; // Coerced, not discarded: an expression is the normal way to fill this // in, and `{{ $json.phone }}` resolving to a NUMBER is a filled-in row. const value = row.value === undefined || row.value === null ? '' : String(row.value).trim(); return { type: row.type, value, ...(label ? { label } : {}) }; }) // `type` is a dropdown but expression-settable, so a workflow can resolve // it to 'mobile'. Dropping beats defaulting to email, which would send a // phone number as an address and earn a 400 about the value, not the type. .filter((detail) => detail.value !== '' && (detail.type === 'email' || detail.type === 'phone')); // Rows were configured and none survived. On UPDATE that has to raise: the // field would be deleted from the body, nothing would be written, and the // node would report 200 to an operator who filled in three rows. // // On CREATE it must NOT raise. There the contact itself is the point and the // recapiti are optional, so a row whose expression resolved to "" for this // item would abort the whole create — importing 100 contacts would silently // skip the 30 with no phone number. Author: Marco, 2026-08-10 if (details.length === 0) { if (raiseWhenUnusable) { throw new Error('Phone & Email: no usable entry. Each row needs a Value, and a Type of exactly "email" or "phone".'); } return undefined; } return details; } 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'], outputs: ['main'], credentials: [ { name: 'inNotesApi', required: true, }, ], properties: [ { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Automation', value: 'automation', }, { 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', }, ...AutomationDescription_1.automationOperations, ...ContactDescription_1.contactOperations, ...NoteDescription_1.noteOperations, ...JobDescription_1.jobOperations, ...StatusDescription_1.statusOperations, ...TagDescription_1.tagOperations, ...UserDescription_1.userOperations, ...AutomationDescription_1.automationFields, ...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() { var _a, _b; 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)); if (resource === 'job' && operation === 'batchExists') { const credentials = await this.getCredentials('inNotesApi'); const baseUrl = credentials.baseUrl; const batchLimit = 200; const idsRaw = this.getNodeParameter('ids', 0, ''); const allExtIds = idsRaw.split(',').map(id => id.trim()).filter(id => id.length > 0); if (allExtIds.length === 0) { return [this.helpers.returnJsonArray([{ error: 'No valid IDs provided' }])]; } const allResults = {}; for (let offset = 0; offset < allExtIds.length; offset += batchLimit) { const chunk = allExtIds.slice(offset, offset + batchLimit); try { const response = await this.helpers.httpRequestWithAuthentication.call(this, 'inNotesApi', { method: 'POST', url: `${baseUrl}/api/job/batch-exists`, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: { ext_ids: chunk }, }); const results = (_a = response.results) !== null && _a !== void 0 ? _a : {}; Object.assign(allResults, results); } catch (error) { if (this.continueOnFail()) { for (const extId of chunk) { allResults[extId] = false; } } else { throw error; } } } for (const extId of allExtIds) { returnData.push({ ext_id: extId, exists: (_b = allResults[extId]) !== null && _b !== void 0 ? _b : false, }); } return [this.helpers.returnJsonArray(returnData)]; } // 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 === 'automation') { responseData = await nodeInstance.executeAutomationOperation(this, actualIndex, operation); } else 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 executeAutomationOperation(executeFunctions, itemIndex, operation) { const credentials = await executeFunctions.getCredentials('inNotesApi'); const baseUrl = credentials.baseUrl; if (operation === 'getConfig') { try { const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/automation/config`, headers: { Accept: 'application/json', }, }); return response; } catch (error) { throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to get automation config: ${error.message}`, }); } } if (operation === 'reportJobCreated') { const jobsAdded = executeFunctions.getNodeParameter('jobsAdded', itemIndex, 1); const status = executeFunctions.getNodeParameter('status', itemIndex, 'success'); const errorMessage = executeFunctions.getNodeParameter('errorMessage', itemIndex, ''); // Get the callback secret from credentials const callbackSecret = credentials.callbackSecret; if (!callbackSecret) { throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: 'Callback secret not configured in InNotes API credentials. Please add the callback secret to your credentials.', }); } // Get user ID from the API let userId; try { const userResponse = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/user`, headers: { Accept: 'application/json', }, }); userId = userResponse.id; } catch (error) { throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to get user ID: ${error.message}`, }); } const body = { userId, status, jobsAdded, secret: callbackSecret, ...(status === 'error' && errorMessage && { error: errorMessage }), }; try { const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'POST', url: `${baseUrl}/api/automation/callback`, body, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); return { success: true, ...response, jobsAdded, status, }; } catch (error) { throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to report job created: ${error.message}`, }); } } throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `The operation "${operation}" is not supported for resource "automation"!`, }); } 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 contact_details = contactDetailsFromCollection(executeFunctions.getNodeParameter('contact_details', 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 }), ...(contact_details && { contact_details }), }; try { 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; } catch (error) { const requestParams = { 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 }), // The COUNT, never the values: this object is interpolated into a // NodeApiError message and n8n persists execution data, so the // numbers and addresses would sit in a third party's workflow // history. Author: Marco, 2026-08-10 ...(contact_details && { contact_details: `${contact_details.length} entr(y|ies)` }), }; throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to create contact: ${error.message}. Request parameters: ${JSON.stringify(requestParams, null, 2)}`, }); } } 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); // The collection goes through as the request body verbatim, so the one // field whose n8n shape differs from the API's is rewritten here rather // than shipped as an object the route refuses. // Raises when rows were configured and none are usable: on update the // alternative is dropping the field and reporting success. const contact_details = contactDetailsFromCollection(updateFields.contact_details, true); const body = { ...updateFields, ...(contact_details ? { contact_details } : {}), }; if (contact_details === undefined) delete body.contact_details; const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'PUT', url: `${baseUrl}/api/contacts/${contactId}`, body, 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 provider = executeFunctions.getNodeParameter('provider', itemIndex, 'external'); const url = executeFunctions.getNodeParameter('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) => { var _a; return ((_a = s.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === status.toLowerCase(); }); if (matchingStatus) { status_id = parseInt(matchingStatus.id, 10); } 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, ...(description && { description }), ...(location && { location }), ...(remote_setting && { remote_setting }), ...(company_url && { company_url }), ...(provider && { provider }), ...(url && { url }), ...(status_id && { status_id }), ...(ext_id && { ext_id }), ...(picture_url && picture_url !== 'Not Available' && { picture_url }), ...(tags && { tags: tags.split(',').map((tag) => tag.trim()) }), }; try { 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; } catch (error) { const requestParams = { name, company_name, ...(description && { description }), ...(location && { location }), ...(remote_setting && { remote_setting }), ...(company_url && { company_url }), ...(provider && { provider }), ...(url && { url }), ...(status_id && { status_id }), ...(ext_id && { ext_id }), ...(picture_url && picture_url !== 'Not Available' && { picture_url }), ...(tags && { tags: tags.split(',').map((tag) => tag.trim()) }), }; throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to create job: ${error.message}. Request parameters: ${JSON.stringify(requestParams, null, 2)}`, }); } } if (operation === 'get') { const jobId = executeFunctions.getNodeParameter('jobId', itemIndex); try { const response = await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'GET', url: `${baseUrl}/api/job/${jobId}`, headers: { Accept: 'application/json', }, }); return response; } catch (error) { throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to get job: ${error.message}. Request parameters: ${JSON.stringify({ jobId }, null, 2)}`, }); } } 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; } try { 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]; } catch (error) { throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to get all jobs: ${error.message}. Request parameters: ${JSON.stringify({ returnAll, search, tags, queryParams: qs }, null, 2)}`, }); } } 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) => { var _a; return ((_a = s.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === updateFields.status.toLowerCase(); }); if (matchingStatus) { // Replace status with status_id and remove status field updateFields.status_id = parseInt(matchingStatus.id, 10); 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()); } try { 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; } catch (error) { throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to update job: ${error.message}. Request parameters: ${JSON.stringify({ jobId, updateFields }, null, 2)}`, }); } } if (operation === 'delete') { const jobId = executeFunctions.getNodeParameter('jobId', itemIndex); try { await executeFunctions.helpers.httpRequestWithAuthentication.call(executeFunctions, 'inNotesApi', { method: 'DELETE', url: `${baseUrl}/api/job/${jobId}`, headers: { Accept: 'application/json', }, }); return { success: true }; } catch (error) { throw new n8n_workflow_1.NodeApiError(executeFunctions.getNode(), { message: `Failed to delete job: ${error.message}. Request parameters: ${JSON.stringify({ jobId }, null, 2)}`, }); } } 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 { // 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) { var _a, _b, _c, _d, _e, _f, _g; 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: {