mirror of
https://github.com/n8n-io/n8n-nodes-starter.git
synced 2025-10-30 06:42:25 -05:00
principal node created
This commit is contained in:
parent
de3e6aa70b
commit
fc3d0df193
12 changed files with 1266 additions and 577 deletions
369
nodes/S4DSMain/ApiHelper.ts
Normal file
369
nodes/S4DSMain/ApiHelper.ts
Normal file
|
|
@ -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<string, string>;
|
||||
};
|
||||
}
|
||||
|
||||
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<string, DtoProperty>;
|
||||
}
|
||||
|
||||
export class ApiHelper {
|
||||
static getApiDefinitions(): Record<string, Record<string, ApiDefinition>> {
|
||||
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<string, DtoDefinition> {
|
||||
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<string, any>, 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue