n8n-nodes-innotes
Version:
N8N node for InNotes CRM API integration
1,038 lines • 53.2 kB
JavaScript
"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: {