backend-mcp
Version:
Generador automático de backends con Node.js, Express, Prisma y módulos configurables. Servidor MCP compatible con npx para agentes IA. Soporta PostgreSQL, MySQL, MongoDB y SQLite.
628 lines (530 loc) • 19.6 kB
JavaScript
const fs = require('fs');
const path = require('path');
const Handlebars = require('handlebars');
class ValidationModuleInitializer {
constructor(projectPath, config = {}) {
this.projectPath = projectPath;
this.config = {
strictMode: config.strictMode !== false,
transform: config.transform !== false,
whitelist: config.whitelist !== false,
forbidNonWhitelisted: config.forbidNonWhitelisted !== false,
skipMissingProperties: config.skipMissingProperties || false,
alwaysTransform: config.alwaysTransform || false,
enableCustomValidators: config.enableCustomValidators !== false,
enableDatabaseValidation: config.enableDatabaseValidation !== false,
enableFileValidation: config.enableFileValidation !== false,
enableI18n: config.enableI18n || false,
defaultLocale: config.defaultLocale || 'en',
...config
};
this.modulePath = path.join(__dirname);
}
async initialize() {
console.log('✅ Inicializando módulo de validación...');
try {
// 1. Configurar estructura de directorios
await this.setupDirectories();
// 2. Generar pipe de validación
await this.generateValidationPipe();
// 3. Generar servicio de validación
await this.generateValidationService();
// 4. Generar decorador de validación
await this.generateValidateDecorator();
// 5. Generar esquemas de validación
await this.generateValidationSchema();
// 6. Generar DTO base
await this.generateBaseDto();
// 7. Generar validadores personalizados
await this.generateCustomValidators();
// 8. Generar decoradores personalizados
await this.generateCustomDecorators();
// 9. Generar interfaces
await this.generateInterfaces();
// 10. Generar controlador (comentado - plantilla no disponible)
// await this.generateController();
// 11. Generar módulo (comentado - plantilla no disponible)
// await this.generateModule();
// 12. Actualizar package.json
await this.updatePackageJson();
console.log('✅ Módulo de validación inicializado correctamente');
return {
success: true,
message: 'Validation module initialized successfully',
files: this.getGeneratedFiles(),
config: this.config
};
} catch (error) {
console.error('❌ Error inicializando módulo de validación:', error);
throw error;
}
}
async setupDirectories() {
const dirs = [
'src/validation',
'src/validation/validators',
'src/validation/decorators',
'src/validation/interfaces',
'src/validation/dto',
'src/validation/schemas',
'src/validation/pipes'
];
for (const dir of dirs) {
const fullPath = path.join(this.projectPath, dir);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
}
}
}
async generateValidationPipe() {
const templatePath = path.join(this.modulePath, 'templates', 'validation.pipe.ts.hbs');
const outputPath = path.join(this.projectPath, 'src/validation/pipes/validation.pipe.ts');
const template = fs.readFileSync(templatePath, 'utf8');
const compiledTemplate = Handlebars.compile(template);
const content = compiledTemplate({
config: this.config,
strictMode: this.config.strictMode,
transform: this.config.transform,
whitelist: this.config.whitelist,
forbidNonWhitelisted: this.config.forbidNonWhitelisted
});
fs.writeFileSync(outputPath, content);
}
async generateValidationService() {
const templatePath = path.join(this.modulePath, 'templates', 'validation.service.ts.hbs');
const outputPath = path.join(this.projectPath, 'src/validation/validation.service.ts');
const template = fs.readFileSync(templatePath, 'utf8');
const compiledTemplate = Handlebars.compile(template);
const content = compiledTemplate({
config: this.config,
enableDatabaseValidation: this.config.enableDatabaseValidation,
enableI18n: this.config.enableI18n
});
fs.writeFileSync(outputPath, content);
}
async generateValidateDecorator() {
const templatePath = path.join(this.modulePath, 'templates', 'validate.decorator.ts.hbs');
const outputPath = path.join(this.projectPath, 'src/validation/decorators/validate.decorator.ts');
const template = fs.readFileSync(templatePath, 'utf8');
const compiledTemplate = Handlebars.compile(template);
const content = compiledTemplate({
config: this.config
});
fs.writeFileSync(outputPath, content);
}
async generateValidationSchema() {
const templatePath = path.join(this.modulePath, 'templates', 'validation.schema.ts.hbs');
const outputPath = path.join(this.projectPath, 'src/validation/schemas/validation.schema.ts');
const template = fs.readFileSync(templatePath, 'utf8');
const compiledTemplate = Handlebars.compile(template);
const content = compiledTemplate({
config: this.config
});
fs.writeFileSync(outputPath, content);
}
async generateBaseDto() {
const templatePath = path.join(this.modulePath, 'templates', 'base.dto.ts.hbs');
const outputPath = path.join(this.projectPath, 'src/validation/dto/base.dto.ts');
const template = fs.readFileSync(templatePath, 'utf8');
const compiledTemplate = Handlebars.compile(template);
const content = compiledTemplate({
config: this.config
});
fs.writeFileSync(outputPath, content);
}
async generateCustomValidators() {
const validatorsTemplate = `import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from 'class-validator';
import { Injectable } from '@nestjs/common';
{{#if enableDatabaseValidation}}
import { PrismaService } from '../../database/prisma.service';
{{/if}}
{{#if enableDatabaseValidation}}
export class IsUniqueConstraint implements ValidatorConstraintInterface {
constructor(private readonly prisma: PrismaService) {}
async validate(value: any, args: ValidationArguments): Promise<boolean> {
const [model, field] = args.constraints;
const record = await this.prisma[model].findFirst({
where: { [field]: value },
});
return !record;
}
defaultMessage(args: ValidationArguments): string {
const [model, field] = args.constraints;
return \`\${field} already exists in \${model}\`;
}
}
export function IsUnique(
model: string,
field: string,
validationOptions?: ValidationOptions,
) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [model, field],
validator: IsUniqueConstraint,
});
};
}
export class ExistsConstraint implements ValidatorConstraintInterface {
constructor(private readonly prisma: PrismaService) {}
async validate(value: any, args: ValidationArguments): Promise<boolean> {
const [model, field] = args.constraints;
const record = await this.prisma[model].findFirst({
where: { [field]: value },
});
return !!record;
}
defaultMessage(args: ValidationArguments): string {
const [model, field] = args.constraints;
return \`\${field} does not exist in \${model}\`;
}
}
export function Exists(
model: string,
field: string = 'id',
validationOptions?: ValidationOptions,
) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [model, field],
validator: ExistsConstraint,
});
};
}
{{/if}}
export class IsStrongPasswordConstraint implements ValidatorConstraintInterface {
validate(password: string, args: ValidationArguments): boolean {
const minLength = args.constraints[0] || 8;
const requireUppercase = args.constraints[1] !== false;
const requireLowercase = args.constraints[2] !== false;
const requireNumbers = args.constraints[3] !== false;
const requireSymbols = args.constraints[4] !== false;
if (password.length < minLength) return false;
if (requireUppercase && !/[A-Z]/.test(password)) return false;
if (requireLowercase && !/[a-z]/.test(password)) return false;
if (requireNumbers && !/\\d/.test(password)) return false;
if (requireSymbols && !/[^\\w\\s]/.test(password)) return false;
return true;
}
defaultMessage(args: ValidationArguments): string {
return 'Password is not strong enough';
}
}
export function IsStrongPassword(
minLength = 8,
requireUppercase = true,
requireLowercase = true,
requireNumbers = true,
requireSymbols = true,
validationOptions?: ValidationOptions,
) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [minLength, requireUppercase, requireLowercase, requireNumbers, requireSymbols],
validator: IsStrongPasswordConstraint,
});
};
}
{{#if enableFileValidation}}
export class IsValidFileConstraint implements ValidatorConstraintInterface {
validate(file: any, args: ValidationArguments): boolean {
if (!file) return false;
const allowedTypes = args.constraints[0] || [];
const maxSize = args.constraints[1] || 5 * 1024 * 1024; // 5MB default
if (allowedTypes.length > 0 && !allowedTypes.includes(file.mimetype)) {
return false;
}
if (file.size > maxSize) {
return false;
}
return true;
}
defaultMessage(args: ValidationArguments): string {
return 'File is not valid';
}
}
export function IsValidFile(
allowedTypes: string[] = [],
maxSize: number = 5 * 1024 * 1024,
validationOptions?: ValidationOptions,
) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [allowedTypes, maxSize],
validator: IsValidFileConstraint,
});
};
}
{{/if}}`;
const outputPath = path.join(this.projectPath, 'src/validation/validators/custom.validators.ts');
const compiledTemplate = Handlebars.compile(validatorsTemplate);
const content = compiledTemplate({
config: this.config,
enableDatabaseValidation: this.config.enableDatabaseValidation,
enableFileValidation: this.config.enableFileValidation
});
fs.writeFileSync(outputPath, content);
}
async generateCustomDecorators() {
const decoratorsTemplate = `import { Transform } from 'class-transformer';
import { applyDecorators } from '@nestjs/common';
import {
IsString,
IsNotEmpty,
IsEmail,
IsOptional,
Length,
Matches,
ValidationOptions,
} from 'class-validator';
// Decorador para emails
export function IsEmailField(validationOptions?: ValidationOptions) {
return applyDecorators(
IsString(validationOptions),
IsEmail({}, validationOptions),
Transform(({ value }) => value?.toLowerCase().trim()),
);
}
// Decorador para contraseñas
export function IsPasswordField(
minLength = 8,
validationOptions?: ValidationOptions,
) {
return applyDecorators(
IsString(validationOptions),
IsNotEmpty(validationOptions),
Length(minLength, 128, validationOptions),
);
}
// Decorador para nombres
export function IsNameField(
minLength = 2,
maxLength = 50,
validationOptions?: ValidationOptions,
) {
return applyDecorators(
IsString(validationOptions),
IsNotEmpty(validationOptions),
Length(minLength, maxLength, validationOptions),
Transform(({ value }) => value?.trim()),
Matches(/^[a-zA-ZÀ-ÿ\\s]+$/, {
message: 'Name can only contain letters and spaces',
...validationOptions,
}),
);
}
// Decorador para teléfonos
export function IsPhoneField(validationOptions?: ValidationOptions) {
return applyDecorators(
IsString(validationOptions),
Matches(/^\\+?[1-9]\\d{1,14}$/, {
message: 'Phone number must be valid',
...validationOptions,
}),
Transform(({ value }) => value?.replace(/\\s+/g, '')),
);
}
// Decorador para URLs
export function IsUrlField(validationOptions?: ValidationOptions) {
return applyDecorators(
IsString(validationOptions),
Matches(/^https?:\/\/.+/, {
message: 'URL must be valid',
...validationOptions,
}),
);
}
// Decorador para campos opcionales de texto
export function IsOptionalText(
minLength = 0,
maxLength = 255,
validationOptions?: ValidationOptions,
) {
return applyDecorators(
IsOptional(),
IsString(validationOptions),
Length(minLength, maxLength, validationOptions),
Transform(({ value }) => value?.trim() || undefined),
);
}
// Decorador para slugs
export function IsSlugField(validationOptions?: ValidationOptions) {
return applyDecorators(
IsString(validationOptions),
IsNotEmpty(validationOptions),
Matches(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {
message: 'Slug must be lowercase letters, numbers and hyphens only',
...validationOptions,
}),
);
}`;
const outputPath = path.join(this.projectPath, 'src/validation/decorators/validation.decorators.ts');
fs.writeFileSync(outputPath, decoratorsTemplate);
}
async generateInterfaces() {
const interfacesTemplate = `export interface ValidationResult {
isValid: boolean;
errors: ValidationError[];
data?: any;
}
export interface ValidationError {
field: string;
value: any;
constraints: Record<string, string>;
children?: ValidationError[];
}
export interface ValidationOptions {
groups?: string[];
skipMissingProperties?: boolean;
whitelist?: boolean;
forbidNonWhitelisted?: boolean;
transform?: boolean;
strictGroups?: boolean;
}
export interface CustomValidatorOptions {
async?: boolean;
message?: string | ((args: any) => string);
groups?: string[];
}
export interface ValidationRule {
field: string;
rules: string[];
message?: string;
conditions?: Record<string, any>;
}
export interface ValidationSchema {
name: string;
version: string;
rules: ValidationRule[];
groups?: string[];
metadata?: Record<string, any>;
}`;
const outputPath = path.join(this.projectPath, 'src/validation/interfaces/validation.interface.ts');
fs.writeFileSync(outputPath, interfacesTemplate);
}
async generateController() {
const templatePath = path.join(this.modulePath, 'templates', 'validation.controller.ts.hbs');
const outputPath = path.join(this.projectPath, 'src/validation/validation.controller.ts');
const template = fs.readFileSync(templatePath, 'utf8');
const compiledTemplate = Handlebars.compile(template);
const content = compiledTemplate({
config: this.config
});
fs.writeFileSync(outputPath, content);
}
async generateModule() {
const templatePath = path.join(this.modulePath, 'templates', 'validation.module.ts.hbs');
const outputPath = path.join(this.projectPath, 'src/validation/validation.module.ts');
const template = fs.readFileSync(templatePath, 'utf8');
const compiledTemplate = Handlebars.compile(template);
const content = compiledTemplate({
config: this.config,
enableDatabaseValidation: this.config.enableDatabaseValidation,
enableCustomValidators: this.config.enableCustomValidators
});
fs.writeFileSync(outputPath, content);
}
async updatePackageJson() {
const packageJsonPath = path.join(this.projectPath, 'package.json');
if (!fs.existsSync(packageJsonPath)) {
console.log('⚠️ package.json no encontrado');
return;
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const dependencies = {
'class-validator': '^0.14.0',
'class-transformer': '^0.5.1'
};
// Agregar dependencias opcionales
if (this.config.enableI18n) {
dependencies['nestjs-i18n'] = '^10.4.0';
}
packageJson.dependencies = {
...packageJson.dependencies,
...dependencies
};
// Agregar scripts de validación
packageJson.scripts = {
...packageJson.scripts,
'validation:generate': 'node scripts/generate-dto.js',
'validation:test': 'node scripts/test-validation.js',
'validation:schemas': 'node scripts/validate-schemas.js'
};
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
console.log('📦 package.json actualizado con dependencias de validación');
}
getGeneratedFiles() {
return [
'src/validation/pipes/validation.pipe.ts',
'src/validation/validation.service.ts',
'src/validation/decorators/validate.decorator.ts',
'src/validation/schemas/validation.schema.ts',
'src/validation/dto/base.dto.ts',
'src/validation/validators/custom.validators.ts',
'src/validation/decorators/validation.decorators.ts',
'src/validation/interfaces/validation.interface.ts'
// 'src/validation/validation.controller.ts', // Comentado - plantilla no disponible
// 'src/validation/validation.module.ts' // Comentado - plantilla no disponible
];
}
}
module.exports = ValidationModuleInitializer;
// CLI usage
if (require.main === module) {
const projectPath = process.argv[2] || process.cwd();
const config = {
strictMode: !process.argv.includes('--no-strict'),
transform: !process.argv.includes('--no-transform'),
enableDatabaseValidation: process.argv.includes('--database'),
enableFileValidation: process.argv.includes('--files'),
enableI18n: process.argv.includes('--i18n')
};
const initializer = new ValidationModuleInitializer(projectPath, config);
initializer.initialize()
.then(result => {
console.log('\n🎉 Módulo de validación configurado exitosamente!');
console.log('\n📁 Archivos generados:');
result.files.forEach(file => console.log(` - ${file}`));
console.log('\n⚙️ Configuración aplicada:');
console.log(` - Strict Mode: ${result.config.strictMode}`);
console.log(` - Transform: ${result.config.transform}`);
console.log(` - Whitelist: ${result.config.whitelist}`);
console.log(` - Database Validation: ${result.config.enableDatabaseValidation}`);
console.log(` - File Validation: ${result.config.enableFileValidation}`);
console.log(` - I18n: ${result.config.enableI18n}`);
console.log('\n🚀 Próximos pasos:');
console.log(' 1. Ejecutar: npm install');
console.log(' 2. Importar ValidationModule en tu AppModule');
console.log(' 3. Configurar ValidationPipe globalmente');
console.log(' 4. Crear DTOs con decoradores de validación');
console.log(' 5. Usar validadores personalizados según necesidad');
})
.catch(error => {
console.error('❌ Error:', error.message);
process.exit(1);
});
}