UNPKG

n8n-nodes-innotes

Version:

N8N node for InNotes CRM API integration

1,106 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: {
                    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 = [];
            let titleBlacklist = [];
            let companyBlacklist = [];
            let penalize = [];
            if (job_preferences) {
                try {
                    parsedPreferences = typeof job_preferences === 'string'
                        ? JSON.parse(job_preferences)
                        : job_preferences;
                    searchJobTitles = (_a = parsedPreferences.searchJobTitles) !== null && _a !== void 0 ? _a : [];
                    locations = (_b = parsedPreferences.locations) !== null && _b !== void 0 ? _b : [];
                    // Handle both new blacklist structure and legacy structure
                    if (parsedPreferences.blacklist) {
                        titleBlacklist = (_c = parsedPreferences.blacklist.titleBlacklist) !== null && _c !== void 0 ? _c : [];
                        companyBlacklist = (_d = parsedPreferences.blacklist.companyBlacklist) !== null && _d !== void 0 ? _d : [];
                        penalize = (_e = parsedPreferences.blacklist.penalize) !== null && _e !== void 0 ? _e : [];
                    }
                    else {
                        // Legacy support
                        titleBlacklist = (_f = parsedPreferences.titleBlacklist) !== null && _f !== void 0 ? _f : [];
                        companyBlacklist = (_g = parsedPreferences.companyBlacklist) !== null && _g !== void 0 ? _g : [];
                    }
                }
                catch {
                    // If parsing fails, keep empty arrays
                }
            }
            return {
                cv,
                job_preferences: parsedPreferences,
                cv_updated_at,
                searchJobTitles: searchJobTitles.join(', '),
                locations: locations.join(', '),
                titleBlacklist: titleBlacklist.join(', '),
                companyBlacklist: companyBlacklist.join(', '),
                penalize: penalize.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