Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions packages/nodes-base/credentials/MindeeV2Api.credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type {
ICredentialDataDecryptedObject,
ICredentialType,
IHttpRequestOptions,
INodeProperties,
} from 'n8n-workflow';

export class MindeeV2Api implements ICredentialType {
name = 'mindeeV2Api';

displayName = 'Mindee API V2';

documentationUrl = 'mindee';

properties: INodeProperties[] = [
{
displayName: 'API Key',
name: 'apiKey',
type: 'string',
typeOptions: { password: true },
default: '',
},
];

async authenticate(
credentials: ICredentialDataDecryptedObject,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
// @ts-ignore
requestOptions.headers!.Authorization = `${credentials.apiKey}`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: TypeScript Error Suppression Violates Coding Standards

The @ts-ignore directive suppresses a TypeScript error, which violates coding standards and hides a potential type safety issue. Specifically, requestOptions.headers might be undefined, and the non-null assertion ! could lead to a runtime error.

Fix in Cursor Fix in Web

return requestOptions;
}
}
174 changes: 174 additions & 0 deletions packages/nodes-base/nodes/MindeeV2/GenericFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import type FormData from 'form-data';
import type {
IExecuteFunctions,
ILoadOptionsFunctions,
IDataObject,
JsonObject,
IHttpRequestMethods,
IHttpRequestOptions,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
import { setTimeout } from 'node:timers/promises';

const INITIAL_DELAY_MS = 1500;
const POLL_DELAY_MS = 1000;

/**
* UI params for the MindeeV2 node.
*/
interface MindeeV2UIParams {
modelId: string;
alias?: string;
rag: string;
polygon: string;
confidence: string;
rawText: string;
maxDelayCount: number;
binaryPropertyName?: string;
}

/**
* Makes an authenticated HTTP request to the Mindee API
* @param method - HTTP method.
* @param url - The Mindee API's (complete) URL.
* @param body - The request body data.
* @param option - Additional request options (default: empty object)
* @returns The API response data
* @throws NodeApiError when the API request fails
*/
export async function mindeeApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
url: string,
body: IDataObject | FormData = {},
option = {},
): Promise<any> {
const options: IHttpRequestOptions = {
headers: {
'User-Agent': `mindee-n8n@v${this.getNode().typeVersion ?? 'unknown'}`,
},
method,
url,
body,
};
try {
delete options.qs;
if (Object.keys(body as IDataObject).length === 0) {
delete options.body;
}
if (Object.keys(option).length !== 0) {
Object.assign(options, option);
}
return await this.helpers.httpRequestWithAuthentication.call(this, 'mindeeV2Api', {
...options,
});
} catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}

/**
* Polls the Mindee API for the result of a job.
* Automatically follows the redirect on the last poll attempt.
* @param funcRef The execution function reference.
* @param initialResponse Initial POST request response from the API.
* @param maxDelayCounter Maximum number of attempts to poll the API.
*/
export async function pollMindee(
funcRef: IExecuteFunctions,
initialResponse: IDataObject,
maxDelayCounter: number,
): Promise<IDataObject[]> {
const result: IDataObject[] = [];
let serverResponse = initialResponse;
const jobId: string | undefined = (serverResponse?.job as IDataObject)?.id as string;
if (!jobId || jobId.length === 0) {
throw new NodeApiError(funcRef.getNode(), serverResponse as JsonObject, {
message: 'Mindee POST response does not contain a job id.',
});
}
let jobStatus: string = (serverResponse.job as IDataObject).status as string;
const pollUrl = (serverResponse.job as IDataObject).polling_url as string;

await setTimeout(INITIAL_DELAY_MS);

for (let i = 0; i < maxDelayCounter; i++) {
if (
serverResponse.error ||
(serverResponse?.job as IDataObject).error ||
jobStatus === 'Failed'
) {
throw new NodeApiError(funcRef.getNode(), serverResponse as JsonObject);
}

serverResponse = await mindeeApiRequest.call(funcRef, 'GET', pollUrl);
if ('inference' in (serverResponse as JsonObject)) break;

if (!('job' in serverResponse))
throw new NodeApiError(funcRef.getNode(), serverResponse as JsonObject, {
message: 'The Mindee API replied with an unexpected reply.',
});
jobStatus = (serverResponse.job as IDataObject).status as string;
await setTimeout(POLL_DELAY_MS);
}

if (!('inference' in (serverResponse as JsonObject)))
throw new NodeApiError(funcRef.getNode(), serverResponse as JsonObject, {
message: `Server polling timed out after ${maxDelayCounter} seconds. Status: ${jobStatus}.`,
});
result.push(serverResponse);
return result;
}

/**
* Reads UI params from a given context.
* @param ctx Execution context.
* @param index Index of the parameter.
*/
export function readUIParams(ctx: IExecuteFunctions, index: number): MindeeV2UIParams {
const modelId = ctx.getNodeParameter('modelId', index);
const alias = ctx.getNodeParameter('alias', index);
const rag = ctx.getNodeParameter('rag', index);
const polygon = ctx.getNodeParameter('polygon', index);
const confidence = ctx.getNodeParameter('confidence', index);
const rawText = ctx.getNodeParameter('rawText', index);
const maxDelayCount = ctx.getNodeParameter('maxDelayCount', index);
const binaryPropertyName = ctx.getNodeParameter('binaryPropertyName', index, '');

return {
modelId: typeof modelId === 'string' ? modelId : '',
alias: typeof alias === 'string' ? alias : '',
rag: typeof rag === 'string' ? rag : 'default',
polygon: typeof polygon === 'string' ? polygon : 'default',
confidence: typeof confidence === 'string' ? confidence : 'default',
rawText: typeof rawText === 'string' ? rawText : 'default',
maxDelayCount: typeof maxDelayCount === 'number' ? maxDelayCount : 120,
binaryPropertyName: typeof binaryPropertyName === 'string' ? binaryPropertyName : '',
};
}

/**
* Builds the request body for the Mindee API.
* @param ctx Execution context.
* @param index Index of the parameter.
* @param uiParams UI parameters.
* @param form Form object.
*/
export async function buildRequestBody(
ctx: IExecuteFunctions,
index: number,
uiParams: MindeeV2UIParams,
form: FormData,
) {
const name = uiParams.binaryPropertyName ?? 'data';
const bin = ctx.helpers.assertBinaryData(index, name);
const buf = await ctx.helpers.getBinaryDataBuffer(index, name);
form.append('file', buf, { filename: bin.fileName });

form.append('model_id', uiParams.modelId);
form.append('alias', uiParams.alias ?? '');
if (uiParams.rag !== 'default') form.append('rag', uiParams.rag);
if (uiParams.polygon !== 'default') form.append('polygon', uiParams.polygon);
if (uiParams.confidence !== 'default') form.append('confidence', uiParams.confidence);
if (uiParams.rawText !== 'default') form.append('raw_text', uiParams.rawText);
}
19 changes: 19 additions & 0 deletions packages/nodes-base/nodes/MindeeV2/MindeeV2.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"node": "n8n-nodes-base.mindeeV2",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Utility"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/mindeev2/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.mindeev2/"
}
],
"generic": []
}
}
Loading