| 
									
										
										
										
											2025-07-10 16:01:04 -05:00
										 |  |  | import { INodeType, INodeTypeDescription, NodeConnectionType, IExecuteFunctions, INodeExecutionData, IHttpRequestMethods } from 'n8n-workflow'; | 
					
						
							|  |  |  | import { ApiHelper } from './ApiHelper'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class S4DSMain implements INodeType { | 
					
						
							|  |  |  | 	description: INodeTypeDescription = { | 
					
						
							| 
									
										
										
										
											2025-07-15 10:34:43 -05:00
										 |  |  | 		displayName: 'S4DS\'s APIs', | 
					
						
							| 
									
										
										
										
											2025-07-10 16:01:04 -05:00
										 |  |  | 		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: { | 
					
						
							| 
									
										
										
										
											2025-07-15 10:34:43 -05:00
										 |  |  | 			name: 'S4DS\'s APIs', | 
					
						
							| 
									
										
										
										
											2025-07-10 16:01:04 -05:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		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<INodeExecutionData[][]> { | 
					
						
							|  |  |  | 		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; | 
					
						
							|  |  |  | 				 | 
					
						
							| 
									
										
										
										
											2025-07-22 08:19:06 -05:00
										 |  |  | 				// Get API definition (now async)
 | 
					
						
							|  |  |  | 				const apiDefinition = await ApiHelper.getApiDefinition(resource, operation); | 
					
						
							| 
									
										
										
										
											2025-07-10 16:01:04 -05:00
										 |  |  | 				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<string, string> = { | 
					
						
							|  |  |  | 					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<string, any> = {}; | 
					
						
							|  |  |  | 				if (apiDefinition.parameters) { | 
					
						
							|  |  |  | 					const queryParameters = this.getNodeParameter('queryParameters', i, {}) as Record<string, any>; | 
					
						
							|  |  |  | 					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) { | 
					
						
							| 
									
										
										
										
											2025-07-19 16:41:02 -05:00
										 |  |  | 					// Usar DTO para request body (POST, PATCH, PUT, etc.)
 | 
					
						
							| 
									
										
										
										
											2025-07-10 16:01:04 -05:00
										 |  |  | 					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<string, any>; | 
					
						
							|  |  |  | 					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]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | }  |