From fc3d0df1939ce8a5d8d5b45fa7d0826fd6bb25e4 Mon Sep 17 00:00:00 2001 From: MrMatiz2 Date: Thu, 10 Jul 2025 16:01:04 -0500 Subject: [PATCH] principal node created --- credentials/S4DSApi.credentials.ts | 30 +- index.js | 5 +- nodes/S4DSAuth/AuthDescription.ts | 109 ----- nodes/S4DSAuth/S4DSAuth.node.ts | 168 ------- nodes/S4DSAuth/S4DSHelper.ts | 80 ---- nodes/S4DSExample/S4DSExample.node.ts | 194 -------- nodes/S4DSMain/ApiHelper.ts | 369 ++++++++++++++++ nodes/S4DSMain/S4DSMain.node.ts | 196 +++++++++ nodes/S4DSMain/api-definitions.json | 81 ++++ nodes/S4DSMain/dto-definitions.json | 608 ++++++++++++++++++++++++++ nodes/S4DSMain/logo_generic.png | Bin 0 -> 3877 bytes package.json | 3 +- 12 files changed, 1266 insertions(+), 577 deletions(-) delete mode 100644 nodes/S4DSAuth/AuthDescription.ts delete mode 100644 nodes/S4DSAuth/S4DSAuth.node.ts delete mode 100644 nodes/S4DSAuth/S4DSHelper.ts delete mode 100644 nodes/S4DSExample/S4DSExample.node.ts create mode 100644 nodes/S4DSMain/ApiHelper.ts create mode 100644 nodes/S4DSMain/S4DSMain.node.ts create mode 100644 nodes/S4DSMain/api-definitions.json create mode 100644 nodes/S4DSMain/dto-definitions.json create mode 100644 nodes/S4DSMain/logo_generic.png diff --git a/credentials/S4DSApi.credentials.ts b/credentials/S4DSApi.credentials.ts index 034abb2..1135de3 100644 --- a/credentials/S4DSApi.credentials.ts +++ b/credentials/S4DSApi.credentials.ts @@ -14,7 +14,7 @@ export class S4DSApi implements ICredentialType { type: 'options', options: [ { - name: 'Demo Test', + name: 'Demo TEST', value: 'https://demotest.s4ds.com/demoapi-test', }, { @@ -22,32 +22,20 @@ export class S4DSApi implements ICredentialType { value: 'https://demouat.s4ds.com/demoapi-uat', }, { - name: 'Demo Production', - value: 'https://demoprod.s4ds.com/demoapi-prod', + name: 'Demo CORE', + value: 'https://demo.s4ds.com/demoapi-core', }, { - name: 'Cliente 1 Test', - value: 'https://cliente1test.s4ds.com/cliente1api-test', + name: 'Aquasource TEST', + value: 'https://aquasourcetest.s4ds.com/aquasourceapi-test', }, { - name: 'Cliente 1 UAT', - value: 'https://cliente1uat.s4ds.com/cliente1api-uat', + name: 'Aquasource UAT', + value: 'https://aquasourceuat.s4ds.com/aquasourceapi-uat', }, { - name: 'Cliente 1 Production', - value: 'https://cliente1prod.s4ds.com/cliente1api-prod', - }, - { - name: 'Cliente 2 Test', - value: 'https://cliente2test.s4ds.com/cliente2api-test', - }, - { - name: 'Cliente 2 UAT', - value: 'https://cliente2uat.s4ds.com/cliente2api-uat', - }, - { - name: 'Cliente 2 Production', - value: 'https://cliente2prod.s4ds.com/cliente2api-prod', + name: 'Aquasource CORE', + value: 'https://aquasource.s4ds.com/aquasourceapi-core', }, { name: 'Custom URL', diff --git a/index.js b/index.js index fbb47e8..0cead78 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,8 @@ -const { S4DSAuth } = require('./dist/nodes/S4DSAuth/S4DSAuth.node.js'); -const { S4DSExample } = require('./dist/nodes/S4DSExample/S4DSExample.node.js'); +const { S4DSMain } = require('./dist/nodes/S4DSMain/S4DSMain.node.js'); const { S4DSApi } = require('./dist/credentials/S4DSApi.credentials.js'); module.exports = { - nodes: [S4DSAuth, S4DSExample], + nodes: [S4DSMain], credentials: [S4DSApi], }; diff --git a/nodes/S4DSAuth/AuthDescription.ts b/nodes/S4DSAuth/AuthDescription.ts deleted file mode 100644 index 526b999..0000000 --- a/nodes/S4DSAuth/AuthDescription.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { INodeProperties } from 'n8n-workflow'; - -// Operaciones disponibles para el recurso de autenticación -export const authOperations: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: ['auth'], - }, - }, - options: [ - { - name: 'Generate Token', - value: 'generateToken', - description: 'Generate authentication token using credentials', - action: 'Generate authentication token', - routing: { - request: { - method: 'POST', - url: '/login/generateToken', - body: { - username: '={{$credentials.s4dsApi.username}}', - password: '={{$credentials.s4dsApi.password}}', - }, - }, - }, - }, - { - name: 'Validate Token', - value: 'validateToken', - description: 'Validate existing token', - action: 'Validate existing token', - routing: { - request: { - method: 'GET', - url: '/auth/validate', - }, - }, - }, - ], - default: 'generateToken', - }, -]; - -// Campos para la operación de generación de token -const generateTokenOperation: INodeProperties[] = [ - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - resource: ['auth'], - operation: ['generateToken'], - }, - }, - options: [ - { - displayName: 'Store Token in Context', - name: 'storeInContext', - type: 'boolean', - default: true, - description: 'Whether to store the token in the workflow context for other nodes to use', - }, - { - displayName: 'Token Context Key', - name: 'tokenContextKey', - type: 'string', - default: 's4ds_token', - description: 'Key to store the token in the workflow context', - displayOptions: { - show: { - storeInContext: [true], - }, - }, - }, - ], - }, -]; - -// Campos para la operación de validación de token -const validateTokenOperation: INodeProperties[] = [ - { - displayName: 'Token', - name: 'token', - type: 'string', - required: true, - default: '', - displayOptions: { - show: { - resource: ['auth'], - operation: ['validateToken'], - }, - }, - description: 'Token to validate', - }, -]; - -// Exportar todos los campos -export const authFields: INodeProperties[] = [ - ...generateTokenOperation, - ...validateTokenOperation, -]; \ No newline at end of file diff --git a/nodes/S4DSAuth/S4DSAuth.node.ts b/nodes/S4DSAuth/S4DSAuth.node.ts deleted file mode 100644 index dac17aa..0000000 --- a/nodes/S4DSAuth/S4DSAuth.node.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { INodeType, INodeTypeDescription, NodeConnectionType, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; -import { authOperations, authFields } from './AuthDescription'; - -export class S4DSAuth implements INodeType { - description: INodeTypeDescription = { - displayName: 'S4DS Authentication', - name: 's4dsAuth', - icon: { light: 'file:s4ds.svg', dark: 'file:s4ds.svg' }, - group: ['transform'], - version: 1, - subtitle: '={{$parameter["operation"]}}', - description: 'Authenticate with S4DS API and manage tokens', - defaults: { - name: 'S4DS Auth', - }, - inputs: [NodeConnectionType.Main], - outputs: [NodeConnectionType.Main], - usableAsTool: true, - credentials: [ - { - name: 's4dsApi', - required: true, - }, - ], - requestDefaults: { - baseURL: '={{$credentials.s4dsApi.baseUrl === "custom" ? $credentials.s4dsApi.customBaseUrl : $credentials.s4dsApi.baseUrl}}', - url: '', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - }, - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Authentication', - value: 'auth', - }, - ], - default: 'auth', - }, - ...authOperations, - ...authFields, - ], - }; - - async execute(this: IExecuteFunctions): Promise { - const items = this.getInputData(); - const returnData: INodeExecutionData[] = []; - - for (let i = 0; i < items.length; i++) { - try { - const resource = this.getNodeParameter('resource', i) as string; - const operation = this.getNodeParameter('operation', i) as string; - - if (resource === 'auth') { - if (operation === 'generateToken') { - // Obtener credenciales - const credentials = await this.getCredentials('s4dsApi'); - - // Determinar la URL base - let baseUrl = credentials.baseUrl; - if (baseUrl === 'custom') { - baseUrl = credentials.customBaseUrl; - } - - if (!baseUrl) { - throw new Error('Base URL is required. Please configure the S4DS API credentials.'); - } - - // Ejecutar la petición HTTP para generar token - const response = await this.helpers.httpRequest({ - method: 'POST', - url: `${baseUrl}/login/generateToken`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - body: { - username: credentials.username, - password: credentials.password, - }, - }); - - // Almacenar token en contexto - const additionalFields = this.getNodeParameter('additionalFields', i) as { - storeInContext?: boolean; - tokenContextKey?: string; - }; - - const contextKey = additionalFields.tokenContextKey || 's4ds_token'; - const tokenData = { - token: response.token, - token_type: response.token_type, - expires_in: response.expires_in, - expires_at: new Date(Date.now() + response.expires_in * 1000).toISOString(), - authorization_header: `${response.token_type} ${response.token}`, - }; - - // Almacenar en contexto del workflow - this.getWorkflowStaticData('global')[contextKey] = tokenData; - - // Retornar respuesta sin el token por seguridad - const secureResponse = { - success: true, - token_type: response.token_type, - expires_in: response.expires_in, - expires_at: tokenData.expires_at, - message: 'Token generated successfully and stored in workflow context', - context_key: contextKey, - }; - - returnData.push({ - json: secureResponse, - }); - } else if (operation === 'validateToken') { - const token = this.getNodeParameter('token', i) as string; - - // Obtener credenciales - const credentials = await this.getCredentials('s4dsApi'); - - // Determinar la URL base - let baseUrl = credentials.baseUrl; - if (baseUrl === 'custom') { - baseUrl = credentials.customBaseUrl; - } - - if (!baseUrl) { - throw new Error('Base URL is required. Please configure the S4DS API credentials.'); - } - - // Ejecutar validación de token - const response = await this.helpers.httpRequest({ - method: 'GET', - url: `${baseUrl}/auth/validate`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, - }, - }); - - returnData.push({ - json: response, - }); - } - } - } catch (error) { - if (this.continueOnFail()) { - returnData.push({ - json: { - error: error.message, - }, - }); - continue; - } - throw error; - } - } - - return [returnData]; - } -} \ No newline at end of file diff --git a/nodes/S4DSAuth/S4DSHelper.ts b/nodes/S4DSAuth/S4DSHelper.ts deleted file mode 100644 index 56e5432..0000000 --- a/nodes/S4DSAuth/S4DSHelper.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { IExecuteFunctions } from 'n8n-workflow'; - -export interface S4DSTokenData { - token: string; - token_type: string; - expires_in: number; - expires_at: string; - authorization_header: string; -} - -export class S4DSHelper { - /** - * Obtiene el token de autorización del contexto del workflow - * @param this - Contexto de ejecución del nodo - * @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token') - * @returns Objeto con datos del token incluyendo el header de autorización - */ - static getTokenFromContext(this: IExecuteFunctions, contextKey: string = 's4ds_token'): S4DSTokenData { - const tokenData = this.getWorkflowStaticData('global')[contextKey] as S4DSTokenData; - - if (!tokenData || !tokenData.token) { - throw new Error(`No S4DS token found in context with key: ${contextKey}. Please run S4DS Auth node first.`); - } - - // Verificar si el token ha expirado - if (tokenData.expires_at && new Date(tokenData.expires_at) <= new Date()) { - throw new Error('S4DS token has expired. Please regenerate token using S4DS Auth node.'); - } - - return tokenData; - } - - /** - * Obtiene el header de autorización listo para usar - * @param this - Contexto de ejecución del nodo - * @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token') - * @returns Header de autorización completo (ej: "Bearer 8a6c71b3-fa62-434d-8b38-907de24c3176") - */ - static getAuthorizationHeader(this: IExecuteFunctions, contextKey: string = 's4ds_token'): string { - const tokenData = S4DSHelper.getTokenFromContext.call(this, contextKey); - return tokenData.authorization_header; - } - - /** - * Obtiene headers HTTP completos con autorización - * @param this - Contexto de ejecución del nodo - * @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token') - * @param additionalHeaders - Headers adicionales opcionales - * @returns Objeto con headers HTTP completos - */ - static getHeadersWithAuth( - this: IExecuteFunctions, - contextKey: string = 's4ds_token', - additionalHeaders: Record = {} - ): Record { - const authHeader = S4DSHelper.getAuthorizationHeader.call(this, contextKey); - - return { - Authorization: authHeader, - Accept: 'application/json', - 'Content-Type': 'application/json', - ...additionalHeaders, - }; - } - - /** - * Verifica si existe un token válido en el contexto - * @param this - Contexto de ejecución del nodo - * @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token') - * @returns true si existe un token válido, false en caso contrario - */ - static hasValidToken(this: IExecuteFunctions, contextKey: string = 's4ds_token'): boolean { - try { - S4DSHelper.getTokenFromContext.call(this, contextKey); - return true; - } catch { - return false; - } - } -} \ No newline at end of file diff --git a/nodes/S4DSExample/S4DSExample.node.ts b/nodes/S4DSExample/S4DSExample.node.ts deleted file mode 100644 index 012829b..0000000 --- a/nodes/S4DSExample/S4DSExample.node.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { INodeType, INodeTypeDescription, NodeConnectionType, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; -import { S4DSHelper } from '../S4DSAuth/S4DSHelper'; - -export class S4DSExample implements INodeType { - description: INodeTypeDescription = { - displayName: 'S4DS Example', - name: 's4dsExample', - icon: { light: 'file:s4ds.svg', dark: 'file:s4ds.svg' }, - group: ['transform'], - version: 1, - subtitle: '={{$parameter["operation"]}}', - description: 'Example node that uses S4DS authentication token', - defaults: { - name: 'S4DS Example', - }, - inputs: [NodeConnectionType.Main], - outputs: [NodeConnectionType.Main], - credentials: [ - { - name: 's4dsApi', - required: true, - }, - ], - requestDefaults: { - baseURL: '={{$credentials.s4dsApi.baseUrl}}', - url: '', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - }, - properties: [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Get Product Count', - value: 'getProductCount', - description: 'Get the total count of products', - action: 'Get product count', - routing: { - request: { - method: 'GET', - url: '/product/count', - }, - }, - }, - ], - default: 'getProductCount', - }, - { - displayName: 'Token Source', - name: 'tokenSource', - type: 'options', - required: true, - default: 'context', - options: [ - { - name: 'From Context', - value: 'context', - description: 'Use token stored in workflow context', - }, - { - name: 'Custom Token', - value: 'custom', - description: 'Provide custom token', - }, - ], - }, - { - displayName: 'Context Token Key', - name: 'contextTokenKey', - type: 'string', - default: 's4ds_token', - required: true, - displayOptions: { - show: { - tokenSource: ['context'], - }, - }, - description: 'Key used to store token in workflow context', - }, - { - displayName: 'Custom Token', - name: 'customToken', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - tokenSource: ['custom'], - }, - }, - description: 'Custom authentication token', - }, - ], - }; - - async execute(this: IExecuteFunctions): Promise { - const items = this.getInputData(); - const returnData: INodeExecutionData[] = []; - - for (let i = 0; i < items.length; i++) { - try { - const operation = this.getNodeParameter('operation', i) as string; - const tokenSource = this.getNodeParameter('tokenSource', i) as string; - - // Obtener headers según la fuente - let headers: Record; - - if (tokenSource === 'context') { - const contextKey = this.getNodeParameter('contextTokenKey', i) as string; - - // Debug: Verificar qué hay en el contexto - const contextData = this.getWorkflowStaticData('global'); - const availableKeys = Object.keys(contextData); - - // Usar el helper para obtener headers con autorización - try { - headers = S4DSHelper.getHeadersWithAuth.call(this, contextKey); - } catch (error) { - // Si falla, retornar información de debug - returnData.push({ - json: { - error: error.message, - debug_info: { - context_key_requested: contextKey, - available_context_keys: availableKeys, - context_data: contextData, - message: 'Please ensure S4DS Auth node runs before this node and generates a token successfully.' - } - }, - }); - continue; - } - } else { - const customToken = this.getNodeParameter('customToken', i) as string; - headers = { - Authorization: customToken, - Accept: 'application/json', - 'Content-Type': 'application/json', - }; - } - - // Ejecutar la operación - let response; - if (operation === 'getProductCount') { - // Obtener credenciales para construir la URL base - const credentials = await this.getCredentials('s4dsApi'); - - // Determinar la URL base - let baseUrl = credentials.baseUrl; - if (baseUrl === 'custom') { - baseUrl = credentials.customBaseUrl; - } - - if (!baseUrl) { - throw new Error('Base URL is required. Please configure the S4DS API credentials.'); - } - - response = await this.helpers.httpRequest({ - method: 'GET', - url: `${baseUrl}/product/count`, - headers, - }); - } - - returnData.push({ - json: { - ...response, - _token_info: { - source: tokenSource, - }, - }, - }); - } catch (error) { - if (this.continueOnFail()) { - returnData.push({ - json: { - error: error.message, - }, - }); - continue; - } - throw error; - } - } - - return [returnData]; - } -} \ No newline at end of file diff --git a/nodes/S4DSMain/ApiHelper.ts b/nodes/S4DSMain/ApiHelper.ts new file mode 100644 index 0000000..a1b732d --- /dev/null +++ b/nodes/S4DSMain/ApiHelper.ts @@ -0,0 +1,369 @@ +import { INodeProperties, NodePropertyTypes } from 'n8n-workflow'; +import * as apiDefinitions from './api-definitions.json'; +import * as dtoDefinitions from './dto-definitions.json'; + +export interface ApiDefinition { + method: string; + endpoint: string; + description: string; + parameters: ApiParameter[]; + requiresAuth: boolean; + requestBody?: { + schema: string; + required: boolean; + }; + response: { + type: string; + properties: Record; + }; +} + +export interface ApiParameter { + name: string; + type: string; + required: boolean; + description: string; + in: 'query' | 'body' | 'path'; +} + +export interface DtoProperty { + type: string; + description: string; + required: boolean; + schema?: string; + items?: { + type: string; + schema?: string; + }; +} + +export interface DtoDefinition { + type: string; + properties: Record; +} + +export class ApiHelper { + static getApiDefinitions(): Record> { + return apiDefinitions as any; + } + + static getResources(): INodeProperties[] { + const definitions = this.getApiDefinitions(); + const resources = Object.keys(definitions).map(resource => ({ + name: this.capitalizeFirst(resource), + value: resource, + description: `${resource} operations`, + })); + + return [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + noDataExpression: true, + options: resources, + default: resources[0]?.value || 'authentication', + }, + ]; + } + + static getOperations(): INodeProperties[] { + const definitions = this.getApiDefinitions(); + const operations: INodeProperties[] = []; + + // Crear operaciones para cada recurso + Object.keys(definitions).forEach(resource => { + const resourceOperations = Object.keys(definitions[resource]).map(operation => ({ + name: this.formatOperationName(operation), + value: operation, + description: definitions[resource][operation].description, + action: this.formatOperationName(operation), + })); + + operations.push({ + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: [resource], + }, + }, + options: resourceOperations, + default: resourceOperations[0]?.value || '', + }); + }); + + return operations; + } + + static getFields(): INodeProperties[] { + const definitions = this.getApiDefinitions(); + const fields: INodeProperties[] = []; + + // Ya no agregamos campos de autenticación + + Object.keys(definitions).forEach(resource => { + Object.keys(definitions[resource]).forEach(operation => { + const apiDef = definitions[resource][operation]; + + // Campos específicos de la operación + if (apiDef.parameters && apiDef.parameters.length > 0) { + fields.push(...this.getOperationFields(resource, operation, apiDef.parameters)); + } + + // Campos para request body con DTOs + if (apiDef.requestBody && apiDef.requestBody.schema) { + fields.push(...this.getRequestBodyFields(resource, operation, apiDef.requestBody.schema)); + } + }); + }); + + return fields; + } + + private static getOperationFields(resource: string, operation: string, parameters: ApiParameter[]): INodeProperties[] { + const fields: INodeProperties[] = []; + + // Agrupar parámetros por tipo + const queryParams = parameters.filter(p => p.in === 'query'); + const bodyParams = parameters.filter(p => p.in === 'body'); + + // Campos para parámetros de query + if (queryParams.length > 0) { + fields.push({ + displayName: 'Query Parameters', + name: 'queryParameters', + type: 'collection', + placeholder: 'Add Query Parameter', + default: {}, + displayOptions: { + show: { + resource: [resource], + operation: [operation], + }, + }, + options: queryParams.map(param => ({ + displayName: this.capitalizeFirst(param.name), + name: param.name, + type: this.mapParameterType(param.type), + default: '', + required: param.required, + description: param.description, + })), + }); + } + + // Campos para parámetros de body + if (bodyParams.length > 0) { + fields.push({ + displayName: 'Body Parameters', + name: 'bodyParameters', + type: 'collection', + placeholder: 'Add Body Parameter', + default: {}, + displayOptions: { + show: { + resource: [resource], + operation: [operation], + }, + }, + options: bodyParams.map(param => ({ + displayName: this.capitalizeFirst(param.name), + name: param.name, + type: this.mapParameterType(param.type), + default: '', + required: param.required, + description: param.description, + })), + }); + } + + return fields; + } + + private static getRequestBodyFields(resource: string, operation: string, dtoSchema: string): INodeProperties[] { + const example = this.generateDtoExample(dtoSchema); + return [ + { + displayName: 'Request Body (JSON)', + name: 'requestBody', + type: 'json', + default: JSON.stringify(example, null, 2), + required: false, + description: 'Pega aquí el JSON completo según el esquema del DTO.', + displayOptions: { + show: { + resource: [resource], + operation: [operation], + }, + }, + }, + ]; + } + + // Helper para generar un ejemplo de JSON para un DTO + private static generateDtoExample(dtoName: string): any { + const dtoDef = this.getDtoDefinition(dtoName); + if (!dtoDef) return {}; + const result: any = {}; + Object.keys(dtoDef.properties).forEach(propName => { + const prop = dtoDef.properties[propName]; + if (prop.type === 'object' && prop.schema) { + result[propName] = this.generateDtoExample(prop.schema); + } else if (prop.type === 'object') { + result[propName] = {}; + } else if (prop.type === 'array' && prop.items?.schema) { + result[propName] = [this.generateDtoExample(prop.items.schema)]; + } else if (prop.type === 'array') { + result[propName] = []; + } else if (prop.type === 'boolean') { + result[propName] = false; + } else if (prop.type === 'number' || prop.type === 'integer') { + result[propName] = 0; + } else { + result[propName] = ''; + } + }); + return result; + } + + private static mapParameterType(apiType: string): NodePropertyTypes { + switch (apiType.toLowerCase()) { + case 'string': + return 'string'; + case 'number': + case 'integer': + return 'number'; + case 'boolean': + return 'boolean'; + case 'array': + return 'fixedCollection'; + case 'object': + return 'json'; + default: + return 'string'; + } + } + + private static capitalizeFirst(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); + } + + private static formatOperationName(operation: string): string { + // Convertir camelCase a palabras separadas + return operation + .replace(/([A-Z])/g, ' $1') + .replace(/^./, str => str.toUpperCase()) + .trim(); + } + + static getApiDefinition(resource: string, operation: string): ApiDefinition | null { + const definitions = this.getApiDefinitions(); + return definitions[resource]?.[operation] || null; + } + + static getDtoDefinitions(): Record { + return dtoDefinitions as any; + } + + static getDtoDefinition(dtoName: string): DtoDefinition | null { + const definitions = this.getDtoDefinitions(); + return definitions[dtoName] || null; + } + + static generateDtoFields(dtoName: string, prefix: string = ''): INodeProperties[] { + const dtoDef = this.getDtoDefinition(dtoName); + if (!dtoDef) return []; + + const fields: INodeProperties[] = []; + + Object.keys(dtoDef.properties).forEach(propName => { + const prop = dtoDef.properties[propName]; + const fieldName = prefix ? `${prefix}.${propName}` : propName; + const displayName = this.capitalizeFirst(propName); + + if (prop.type === 'object' && prop.schema) { + // Campo de objeto anidado + fields.push({ + displayName: `${displayName} (${prop.schema})`, + name: fieldName, + type: 'collection', + placeholder: `Add ${displayName}`, + default: {}, + required: prop.required, + description: prop.description, + options: this.generateDtoFields(prop.schema, fieldName), + }); + } else if (prop.type === 'array' && prop.items?.schema) { + // Campo de array de objetos + fields.push({ + displayName: `${displayName} (Array of ${prop.items.schema})`, + name: fieldName, + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + placeholder: `Add ${displayName} Item`, + default: {}, + required: prop.required, + description: prop.description, + options: [ + { + displayName: prop.items.schema, + name: 'item', + values: this.generateDtoFields(prop.items.schema, `${fieldName}.item`), + }, + ], + }); + } else { + // Campo simple + fields.push({ + displayName, + name: fieldName, + type: this.mapParameterType(prop.type), + default: '', + required: prop.required, + description: prop.description, + }); + } + }); + + return fields; + } + + static buildDtoObject(data: Record, dtoSchema: string): any { + const dtoDef = this.getDtoDefinition(dtoSchema); + if (!dtoDef) return data; + + const result: any = {}; + + Object.keys(dtoDef.properties).forEach(propName => { + const prop = dtoDef.properties[propName]; + const value = data[propName]; + + if (value !== undefined && value !== '') { + if (prop.type === 'object' && prop.schema) { + // Objeto anidado + result[propName] = this.buildDtoObject(value, prop.schema); + } else if (prop.type === 'array' && prop.items?.schema) { + // Array de objetos + if (Array.isArray(value)) { + result[propName] = value.map(item => this.buildDtoObject(item, prop.items!.schema!)); + } else if (value && typeof value === 'object') { + // Para fixedCollection de n8n + result[propName] = Object.values(value).map((item: any) => + this.buildDtoObject(item, prop.items!.schema!) + ); + } + } else { + // Campo simple + result[propName] = value; + } + } + }); + + return result; + } +} \ No newline at end of file diff --git a/nodes/S4DSMain/S4DSMain.node.ts b/nodes/S4DSMain/S4DSMain.node.ts new file mode 100644 index 0000000..02d4fcd --- /dev/null +++ b/nodes/S4DSMain/S4DSMain.node.ts @@ -0,0 +1,196 @@ +import { INodeType, INodeTypeDescription, NodeConnectionType, IExecuteFunctions, INodeExecutionData, IHttpRequestMethods } from 'n8n-workflow'; +import { ApiHelper } from './ApiHelper'; + +export class S4DSMain implements INodeType { + description: INodeTypeDescription = { + displayName: 'S4DS', + name: 's4ds', + icon: { light: 'file:logo_generic.png', dark: 'file:logo_generic.png' }, + group: ['transform'], + version: 1, + subtitle: '={{$parameter["resource"]}} - {{$parameter["operation"]}}', + description: 'S4DS API operations including authentication and product management', + defaults: { + name: 'S4DS', + }, + inputs: [NodeConnectionType.Main], + outputs: [NodeConnectionType.Main], + credentials: [ + { + name: 's4dsApi', + required: true, + }, + ], + requestDefaults: { + baseURL: '={{$credentials.s4dsApi.baseUrl === "custom" ? $credentials.s4dsApi.customBaseUrl : $credentials.s4dsApi.baseUrl}}', + url: '', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + }, + properties: [ + ...ApiHelper.getResources(), + ...ApiHelper.getOperations(), + ...ApiHelper.getFields(), + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + + for (let i = 0; i < items.length; i++) { + try { + const resource = this.getNodeParameter('resource', i) as string; + const operation = this.getNodeParameter('operation', i) as string; + + // Obtener definición de la API + const apiDefinition = ApiHelper.getApiDefinition(resource, operation); + if (!apiDefinition) { + throw new Error(`API definition not found for resource: ${resource}, operation: ${operation}`); + } + + // Obtener credenciales + const credentials = await this.getCredentials('s4dsApi'); + + // Determinar la URL base + let baseUrl = credentials.baseUrl; + if (baseUrl === 'custom') { + baseUrl = credentials.customBaseUrl; + } + + if (!baseUrl) { + throw new Error('Base URL is required. Please configure the S4DS API credentials.'); + } + + // Preparar headers + let headers: Record = { + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + + // Agregar autenticación si es requerida + if (apiDefinition.requiresAuth) { + // Siempre usar la key por defecto 's4ds_token' + const contextKey = 's4ds_token'; + const contextData = this.getWorkflowStaticData('global'); + const tokenData = contextData[contextKey] as any; + if (!tokenData || !tokenData.authorization_header) { + throw new Error(`Token not found in context with key 's4ds_token'. Please run an authentication operation first.`); + } + headers.Authorization = tokenData.authorization_header; + } + + // Preparar parámetros de query + const queryParams: Record = {}; + if (apiDefinition.parameters) { + const queryParameters = this.getNodeParameter('queryParameters', i, {}) as Record; + apiDefinition.parameters + .filter(p => p.in === 'query') + .forEach(param => { + if (queryParameters[param.name] !== undefined && queryParameters[param.name] !== '') { + queryParams[param.name] = queryParameters[param.name]; + } + }); + } + + // Preparar body si es necesario + let body: any = undefined; + if (apiDefinition.method === 'POST' && resource === 'authentication') { + body = { + username: credentials.username, + password: credentials.password, + }; + } else if (apiDefinition.requestBody && apiDefinition.requestBody.schema) { + // Usar DTO para request body + let requestBodyData = this.getNodeParameter('requestBody', i, {}); + if (typeof requestBodyData === 'string') { + try { + requestBodyData = JSON.parse(requestBodyData); + } catch (e) { + throw new Error('El JSON del body no es válido: ' + e.message); + } + } + body = requestBodyData; + } else if (apiDefinition.parameters) { + const bodyParameters = this.getNodeParameter('bodyParameters', i, {}) as Record; + const bodyParams = apiDefinition.parameters.filter(p => p.in === 'body'); + if (bodyParams.length > 0) { + body = {}; + bodyParams.forEach(param => { + if (bodyParameters[param.name] !== undefined && bodyParameters[param.name] !== '') { + body[param.name] = bodyParameters[param.name]; + } + }); + } + } + + // Construir URL con parámetros de query + let url = `${baseUrl}${apiDefinition.endpoint}`; + if (Object.keys(queryParams).length > 0) { + const queryString = Object.keys(queryParams) + .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`) + .join('&'); + url += `?${queryString}`; + } + + // Ejecutar la petición HTTP + const response = await this.helpers.httpRequest({ + method: apiDefinition.method as IHttpRequestMethods, + url, + headers, + body, + }); + + // Procesar respuesta según el tipo de operación + if (resource === 'authentication' && operation === 'generateToken') { + // Almacenar token en contexto + const contextKey = 's4ds_token'; + const tokenData = { + token: response.token, + token_type: response.token_type, + expires_in: response.expires_in, + expires_at: new Date(Date.now() + response.expires_in * 1000).toISOString(), + authorization_header: `${response.token_type} ${response.token}`, + }; + // Almacenar en contexto del workflow + this.getWorkflowStaticData('global')[contextKey] = tokenData; + + // Retornar respuesta sin el token por seguridad + const secureResponse = { + success: true, + token_type: response.token_type, + expires_in: response.expires_in, + expires_at: tokenData.expires_at, + message: 'Token generated successfully and stored in workflow context', + context_key: contextKey, + }; + + returnData.push({ + json: secureResponse, + }); + } else { + // Retornar respuesta normal + returnData.push({ + json: response, + }); + } + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ + json: { + error: error.message, + }, + }); + continue; + } + throw error; + } + } + + return [returnData]; + } + + +} \ No newline at end of file diff --git a/nodes/S4DSMain/api-definitions.json b/nodes/S4DSMain/api-definitions.json new file mode 100644 index 0000000..eb9f71e --- /dev/null +++ b/nodes/S4DSMain/api-definitions.json @@ -0,0 +1,81 @@ +{ + "authentication": { + "generateToken": { + "method": "POST", + "endpoint": "/login/generateToken", + "description": "Generate authentication token using credentials", + "parameters": [], + "requiresAuth": false, + "response": { + "type": "object", + "properties": { + "token": "string", + "token_type": "string", + "expires_in": "number" + } + } + } + }, + "products": { + "getProductCount": { + "method": "GET", + "endpoint": "/product/count", + "description": "Get the total count of products", + "parameters": [], + "requiresAuth": true, + "response": { + "type": "object", + "properties": { + "count": "number" + } + } + } + }, + "customers": { + "getCustomerByDocument": { + "method": "GET", + "endpoint": "/customer/specificCustomer", + "description": "Get customer information by document ID and type", + "parameters": [ + { + "name": "documentId", + "type": "string", + "required": false, + "description": "Document ID of the customer", + "in": "query" + }, + { + "name": "documentType", + "type": "string", + "required": false, + "description": "Type of document (e.g., CC, CE, NIT)", + "in": "query" + } + ], + "requiresAuth": true, + "response": { + "type": "object", + "properties": { + "customer": "object" + } + } + }, + "createCustomer": { + "method": "POST", + "endpoint": "/customer", + "description": "Create a new customer", + "parameters": [], + "requiresAuth": true, + "requestBody": { + "schema": "SetNewCustomerDTO", + "required": true + }, + "response": { + "type": "object", + "properties": { + "customer": "object" + } + } + } + } +} \ No newline at end of file diff --git a/nodes/S4DSMain/dto-definitions.json b/nodes/S4DSMain/dto-definitions.json new file mode 100644 index 0000000..7dbd3e9 --- /dev/null +++ b/nodes/S4DSMain/dto-definitions.json @@ -0,0 +1,608 @@ +{ + "SetNewCustomerDTO": { + "type": "object", + "properties": { + "documentType": { + "type": "string", + "description": "Type of document", + "required": false + }, + "docType": { + "type": "string", + "description": "Document type code", + "required": false + }, + "docTypeDesc": { + "type": "string", + "description": "Document type description", + "required": false + }, + "documentId": { + "type": "string", + "description": "Document ID", + "required": false + }, + "newDocumentId": { + "type": "string", + "description": "New document ID", + "required": false + }, + "newDocumentType": { + "type": "string", + "description": "New document type", + "required": false + }, + "verificationDigit": { + "type": "string", + "description": "Verification digit", + "required": false + }, + "internalCode": { + "type": "string", + "description": "Internal code", + "required": false + }, + "customerType": { + "type": "string", + "description": "Customer type", + "required": false + }, + "status": { + "type": "string", + "description": "Status", + "required": false + }, + "customerStatus": { + "type": "string", + "description": "Customer status", + "required": false + }, + "name": { + "type": "string", + "description": "First name", + "required": false + }, + "secondName": { + "type": "string", + "description": "Second name", + "required": false + }, + "lastName": { + "type": "string", + "description": "Last name", + "required": false + }, + "lastName1": { + "type": "string", + "description": "First last name", + "required": false + }, + "lastName2": { + "type": "string", + "description": "Second last name", + "required": false + }, + "email": { + "type": "string", + "description": "Email address", + "required": false + }, + "gender": { + "type": "string", + "description": "Gender", + "required": false + }, + "genderDesc": { + "type": "string", + "description": "Gender description", + "required": false + }, + "language": { + "type": "string", + "description": "Language", + "required": false + }, + "languageCode": { + "type": "string", + "description": "Language code", + "required": false + }, + "username": { + "type": "string", + "description": "Username", + "required": false + }, + "password": { + "type": "string", + "description": "Password", + "required": false + }, + "loginType": { + "type": "string", + "description": "Login type", + "required": false + }, + "roles": { + "type": "array", + "description": "User roles", + "items": { + "type": "string" + }, + "required": false + }, + "metadata": { + "type": "object", + "description": "Metadata del cliente", + "required": false + }, + "priceList": { + "type": "string", + "description": "Price list", + "required": false + }, + "shippingAddress": { + "type": "object", + "description": "Shipping address", + "schema": "AddressDTO", + "required": false + }, + "homeAddress": { + "type": "object", + "description": "Home address", + "schema": "AddressDTO", + "required": false + }, + "phone": { + "type": "array", + "description": "Phone numbers", + "items": { + "type": "object", + "schema": "PhoneDTO" + }, + "required": false + }, + "birthdate": { + "type": "string", + "description": "Birth date", + "required": false + }, + "sellerId": { + "type": "string", + "description": "Seller ID", + "required": false + }, + "balanceDate": { + "type": "string", + "description": "Balance date", + "required": false + }, + "balance": { + "type": "string", + "description": "Balance", + "required": false + }, + "balanceMoneyFormat": { + "type": "string", + "description": "Balance money format", + "required": false + }, + "paymentTerms": { + "type": "string", + "description": "Payment terms", + "required": false + }, + "creditDays": { + "type": "string", + "description": "Credit days", + "required": false + }, + "creditQuota": { + "type": "string", + "description": "Credit quota", + "required": false + }, + "creditSegment": { + "type": "string", + "description": "Credit segment", + "required": false + }, + "creditStatus": { + "type": "string", + "description": "Credit status", + "required": false + }, + "territorialDivision": { + "type": "string", + "description": "Territorial division", + "required": false + }, + "territoryDescription": { + "type": "string", + "description": "Territory description", + "required": false + }, + "zoneDivisionId": { + "type": "string", + "description": "Zone division ID", + "required": false + }, + "zoneDivisionDesc": { + "type": "string", + "description": "Zone division description", + "required": false + }, + "zoneDivisionCode": { + "type": "string", + "description": "Zone division code", + "required": false + }, + "tdDivisionId": { + "type": "string", + "description": "TD division ID", + "required": false + }, + "tdDivisionCode": { + "type": "string", + "description": "TD division code", + "required": false + }, + "regionDivisionId": { + "type": "string", + "description": "Region division ID", + "required": false + }, + "regionDivisionDescription": { + "type": "string", + "description": "Region division description", + "required": false + }, + "regionDivisionCode": { + "type": "string", + "description": "Region division code", + "required": false + }, + "countryDivisionId": { + "type": "string", + "description": "Country division ID", + "required": false + }, + "countryDivisionCode": { + "type": "string", + "description": "Country division code", + "required": false + }, + "countryDivisionDesc": { + "type": "string", + "description": "Country division description", + "required": false + }, + "bankCode": { + "type": "string", + "description": "Bank code", + "required": false + }, + "bankDescription": { + "type": "string", + "description": "Bank description", + "required": false + }, + "accountType": { + "type": "string", + "description": "Account type", + "required": false + }, + "taxRegime": { + "type": "string", + "description": "Tax regime", + "required": false + }, + "taxRegimeCode": { + "type": "string", + "description": "Tax regime code", + "required": false + }, + "taxRegimeDescription": { + "type": "string", + "description": "Tax regime description", + "required": false + }, + "accountNumber": { + "type": "string", + "description": "Account number", + "required": false + }, + "segment": { + "type": "string", + "description": "Segment", + "required": false + }, + "registration": { + "type": "string", + "description": "Registration", + "required": false + }, + "reactivationDate": { + "type": "string", + "description": "Reactivation date", + "required": false + }, + "referent": { + "type": "object", + "description": "Referent information", + "schema": "ReferentDTO", + "required": false + }, + "sponsor": { + "type": "object", + "description": "Sponsor information", + "schema": "SponsorDTO", + "required": false + }, + "sync": { + "type": "string", + "description": "Sync status", + "required": false + }, + "currencyId": { + "type": "string", + "description": "Currency ID", + "required": false + }, + "continuity": { + "type": "string", + "description": "Continuity", + "required": false + }, + "accumulatedPoints": { + "type": "string", + "description": "Accumulated points", + "required": false + }, + "rankCode": { + "type": "string", + "description": "Rank code", + "required": false + }, + "rankDescription": { + "type": "string", + "description": "Rank description", + "required": false + }, + "honorificRankCode": { + "type": "string", + "description": "Honorific rank code", + "required": false + }, + "honorificRankDescription": { + "type": "string", + "description": "Honorific rank description", + "required": false + }, + "honorificDate": { + "type": "string", + "description": "Honorific date", + "required": false + }, + "compensationStructure": { + "type": "string", + "description": "Compensation structure", + "required": false + }, + "inactivityCampaigns": { + "type": "string", + "description": "Inactivity campaigns", + "required": false + }, + "commercialStatus": { + "type": "string", + "description": "Commercial status", + "required": false + }, + "desertionDate": { + "type": "string", + "description": "Desertion date", + "required": false + }, + "outstandingCreditBalanceSync": { + "type": "string", + "description": "Outstanding credit balance sync", + "required": false + }, + "outstandingCreditBalanceSyncFormatted": { + "type": "string", + "description": "Outstanding credit balance sync formatted", + "required": false + }, + "notificationType": { + "type": "string", + "description": "Notification type", + "required": false + }, + "needsAuditing": { + "type": "string", + "description": "Needs auditing", + "required": false + }, + "rut": { + "type": "string", + "description": "RUT", + "required": false + }, + "georeference": { + "type": "string", + "description": "Georeference", + "required": false + }, + "warehouseId": { + "type": "string", + "description": "Warehouse ID", + "required": false + }, + "warehouseCode": { + "type": "string", + "description": "Warehouse code", + "required": false + }, + "currency": { + "type": "string", + "description": "Currency", + "required": false + }, + "postalCode": { + "type": "string", + "description": "Postal code", + "required": false + }, + "shippingPostalCode": { + "type": "string", + "description": "Shipping postal code", + "required": false + }, + "tdivisionDescription": { + "type": "string", + "description": "T division description", + "required": false + } + } + }, + "AddressDTO": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Address line", + "required": false + }, + "address2": { + "type": "string", + "description": "Address line 2", + "required": false + }, + "address3": { + "type": "string", + "description": "Address line 3", + "required": false + }, + "postalCode": { + "type": "string", + "description": "Postal code", + "required": false + }, + "neighborhood": { + "type": "string", + "description": "Neighborhood", + "required": false + }, + "city": { + "type": "string", + "description": "City", + "required": false + }, + "cityCode": { + "type": "string", + "description": "City code", + "required": false + }, + "state": { + "type": "string", + "description": "State", + "required": false + }, + "stateCode": { + "type": "string", + "description": "State code", + "required": false + }, + "country": { + "type": "string", + "description": "Country", + "required": false + }, + "countryCode": { + "type": "string", + "description": "Country code", + "required": false + }, + "addressName": { + "type": "string", + "description": "Address name", + "required": false + }, + "addressDescription": { + "type": "string", + "description": "Address description", + "required": false + }, + "addressMetadata": { + "type": "object", + "description": "Address metadata", + "required": false + } + } + }, + "PhoneDTO": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Phone type", + "required": false + }, + "countryCode": { + "type": "string", + "description": "Country code", + "required": false + }, + "number": { + "type": "string", + "description": "Phone number", + "required": false + }, + "modifiedAt": { + "type": "string", + "description": "Modified at timestamp", + "required": false + } + } + }, + "ReferentDTO": { + "type": "object", + "properties": { + "documentId": { + "type": "string", + "description": "Document ID", + "required": false + }, + "docType": { + "type": "string", + "description": "Document type", + "required": false + }, + "docTypeDesc": { + "type": "string", + "description": "Document type description", + "required": false + } + } + }, + "SponsorDTO": { + "type": "object", + "properties": { + "documentId": { + "type": "string", + "description": "Document ID", + "required": false + }, + "docType": { + "type": "string", + "description": "Document type", + "required": false + }, + "docTypeDesc": { + "type": "string", + "description": "Document type description", + "required": false + } + } + } +} \ No newline at end of file diff --git a/nodes/S4DSMain/logo_generic.png b/nodes/S4DSMain/logo_generic.png new file mode 100644 index 0000000000000000000000000000000000000000..38b92354f7fc206fa497a61669aefeebc911f21a GIT binary patch literal 3877 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 7bf00ee..e94201f 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,7 @@ "dist/credentials/S4DSApi.credentials.js" ], "nodes": [ - "dist/nodes/S4DSAuth/S4DSAuth.node.js", - "dist/nodes/S4DSExample/S4DSExample.node.js" + "dist/nodes/S4DSMain/S4DSMain.node.js" ] }, "devDependencies": {