UNPKG

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.

386 lines (325 loc) 12.5 kB
const fs = require('fs'); const path = require('path'); const Handlebars = require('handlebars'); class WebSocketModuleInitializer { constructor(config = {}) { this.config = config; this.modulePath = __dirname; this.projectRoot = config.projectRoot || process.cwd(); this.templatesDir = path.join(this.modulePath, 'templates'); } async initialize() { try { console.log('🔧 Inicializando módulo WebSockets...'); // 1. Detectar configuración de WebSockets const wsConfig = this.detectWebSocketConfig(); console.log(`🔌 Configuración WebSocket: ${JSON.stringify(wsConfig)}`); // 2. Configurar estructura de directorios this.setupDirectories(); // 3. Generar gateway principal await this.generateWebSocketGateway(wsConfig); // 4. Generar servicio de WebSockets await this.generateWebSocketService(wsConfig); // 5. Generar handlers de eventos await this.generateSocketHandlers(wsConfig); // 6. Generar middleware de autenticación await this.generateSocketMiddleware(wsConfig); // 7. Generar definiciones de eventos await this.generateSocketEvents(wsConfig); // 8. Generar controlador y módulo await this.generateControllerAndModule(wsConfig); // 9. Configurar base de datos await this.setupDatabase(); // 10. Actualizar package.json await this.updatePackageJson(wsConfig); const result = { success: true, module: 'websockets', config: wsConfig, features: this.getEnabledFeatures(), files: this.getGeneratedFiles(), events: this.getSocketEvents(), rooms: this.getRoomTypes(), message: `Módulo WebSockets inicializado con ${wsConfig.adapter}` }; console.log('✅ Módulo WebSockets inicializado correctamente'); return result; } catch (error) { console.error('❌ Error inicializando módulo WebSockets:', error.message); throw error; } } detectWebSocketConfig() { const env = process.env; return { port: env.WEBSOCKET_PORT || 3001, corsOrigin: env.WEBSOCKET_CORS_ORIGIN || '*', adapter: env.REDIS_URL ? 'redis' : 'memory', redisUrl: env.REDIS_URL, maxConnections: parseInt(env.MAX_CONNECTIONS || '10000'), heartbeatInterval: parseInt(env.HEARTBEAT_INTERVAL || '25000'), enableAuth: this.config.enableAuth !== false, enableLogging: this.config.enableLogging !== false, enableRooms: this.config.enableRooms !== false, enablePresence: this.config.enablePresence !== false }; } setupDirectories() { const dirs = [ 'src/websockets', 'src/websockets/handlers', 'src/websockets/middleware', 'src/websockets/events', 'src/websockets/rooms' ]; dirs.forEach(dir => { const fullPath = path.join(this.projectRoot, dir); if (!fs.existsSync(fullPath)) { fs.mkdirSync(fullPath, { recursive: true }); } }); } async generateWebSocketGateway(config) { const templatePath = path.join(this.templatesDir, 'websocket.gateway.ts.hbs'); const outputPath = path.join(this.projectRoot, 'src/websockets/websocket.gateway.ts'); const context = { config: config, features: { auth: config.enableAuth, logging: config.enableLogging, rooms: config.enableRooms, presence: config.enablePresence, redis: config.adapter === 'redis' } }; await this.renderTemplate(templatePath, outputPath, context); } async generateWebSocketService(config) { const templatePath = path.join(this.templatesDir, 'websocket.service.ts.hbs'); const outputPath = path.join(this.projectRoot, 'src/websockets/websocket.service.ts'); const context = { config: config, features: { auth: config.enableAuth, logging: config.enableLogging, rooms: config.enableRooms, presence: config.enablePresence, redis: config.adapter === 'redis' } }; await this.renderTemplate(templatePath, outputPath, context); } async generateSocketHandlers(config) { const templatePath = path.join(this.templatesDir, 'socket.handlers.ts.hbs'); const outputPath = path.join(this.projectRoot, 'src/websockets/socket.handlers.ts'); const context = { config: config, events: this.getSocketEvents(), features: { auth: config.enableAuth, logging: config.enableLogging, rooms: config.enableRooms, presence: config.enablePresence } }; await this.renderTemplate(templatePath, outputPath, context); } async generateSocketMiddleware(config) { const templatePath = path.join(this.templatesDir, 'socket.middleware.ts.hbs'); const outputPath = path.join(this.projectRoot, 'src/websockets/socket.middleware.ts'); const context = { config: config, features: { auth: config.enableAuth, logging: config.enableLogging, rateLimiting: true } }; await this.renderTemplate(templatePath, outputPath, context); } async generateSocketEvents(config) { const templatePath = path.join(this.templatesDir, 'socket.events.ts.hbs'); const outputPath = path.join(this.projectRoot, 'src/websockets/socket.events.ts'); const context = { config: config, events: this.getSocketEvents() }; await this.renderTemplate(templatePath, outputPath, context); } async generateControllerAndModule(config) { // Generar controlador const controllerPath = path.join(this.templatesDir, 'websocket.controller.ts.hbs'); const controllerOutput = path.join(this.projectRoot, 'src/websockets/websocket.controller.ts'); const controllerContext = { config: config, features: { auth: config.enableAuth, logging: config.enableLogging } }; await this.renderTemplate(controllerPath, controllerOutput, controllerContext); // Generar módulo const modulePath = path.join(this.templatesDir, 'websocket.module.ts.hbs'); const moduleOutput = path.join(this.projectRoot, 'src/websockets/websocket.module.ts'); await this.renderTemplate(modulePath, moduleOutput, controllerContext); } async setupDatabase() { // Crear esquema de tablas para websockets const schema = ` -- WebSocket module tables CREATE TABLE IF NOT EXISTS socket_connections ( id SERIAL PRIMARY KEY, socket_id VARCHAR(255) UNIQUE NOT NULL, user_id INTEGER, ip_address INET, user_agent TEXT, connected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, disconnected_at TIMESTAMP, is_active BOOLEAN DEFAULT true, last_ping TIMESTAMP DEFAULT CURRENT_TIMESTAMP, rooms JSONB DEFAULT '[]', metadata JSONB DEFAULT '{}' ); CREATE TABLE IF NOT EXISTS chat_messages ( id SERIAL PRIMARY KEY, room_id VARCHAR(255) NOT NULL, user_id INTEGER, socket_id VARCHAR(255), message_type VARCHAR(50) DEFAULT 'text', content TEXT NOT NULL, metadata JSONB DEFAULT '{}', is_edited BOOLEAN DEFAULT false, edited_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS user_presence ( id SERIAL PRIMARY KEY, user_id INTEGER UNIQUE NOT NULL, status VARCHAR(50) DEFAULT 'offline', last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP, socket_id VARCHAR(255), current_room VARCHAR(255), metadata JSONB DEFAULT '{}', updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS websocket_rooms ( id SERIAL PRIMARY KEY, room_id VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(255), type VARCHAR(50) DEFAULT 'public', description TEXT, max_users INTEGER DEFAULT 100, current_users INTEGER DEFAULT 0, is_active BOOLEAN DEFAULT true, metadata JSONB DEFAULT '{}', created_by INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_socket_connections_user_id ON socket_connections(user_id); CREATE INDEX IF NOT EXISTS idx_socket_connections_active ON socket_connections(is_active); CREATE INDEX IF NOT EXISTS idx_chat_messages_room_id ON chat_messages(room_id); CREATE INDEX IF NOT EXISTS idx_chat_messages_created_at ON chat_messages(created_at); CREATE INDEX IF NOT EXISTS idx_user_presence_user_id ON user_presence(user_id); CREATE INDEX IF NOT EXISTS idx_user_presence_status ON user_presence(status); CREATE INDEX IF NOT EXISTS idx_websocket_rooms_type ON websocket_rooms(type); CREATE INDEX IF NOT EXISTS idx_websocket_rooms_active ON websocket_rooms(is_active); `; const schemaPath = path.join(this.projectRoot, 'src/websockets/schema.sql'); fs.writeFileSync(schemaPath, schema); } async updatePackageJson(config) { const packagePath = path.join(this.projectRoot, 'package.json'); let packageJson = {}; if (fs.existsSync(packagePath)) { packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); } if (!packageJson.dependencies) { packageJson.dependencies = {}; } // Dependencias base const baseDependencies = { '@nestjs/websockets': '^10.2.7', '@nestjs/platform-socket.io': '^10.2.7', 'socket.io': '^4.7.4', 'socket.io-client': '^4.7.4' }; // Dependencias opcionales if (config.adapter === 'redis') { baseDependencies['@socket.io/redis-adapter'] = '^8.2.1'; baseDependencies['redis'] = '^4.6.10'; } Object.assign(packageJson.dependencies, baseDependencies); fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2)); } async renderTemplate(templatePath, outputPath, context) { if (!fs.existsSync(templatePath)) { console.warn(`⚠️ Template no encontrado: ${templatePath}`); return; } const templateContent = fs.readFileSync(templatePath, 'utf8'); const template = Handlebars.compile(templateContent); const result = template(context); const outputDir = path.dirname(outputPath); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } fs.writeFileSync(outputPath, result); } getEnabledFeatures() { return [ 'real-time-messaging', 'room-management', 'user-presence', 'message-broadcasting', 'private-messaging', 'typing-indicators', 'connection-management', 'event-handling', 'middleware-support', 'authentication-integration' ]; } getGeneratedFiles() { return [ 'src/websockets/websocket.gateway.ts', 'src/websockets/websocket.service.ts', 'src/websockets/socket.handlers.ts', 'src/websockets/socket.middleware.ts', 'src/websockets/socket.events.ts', 'src/websockets/websocket.controller.ts', 'src/websockets/websocket.module.ts', 'src/websockets/schema.sql' ]; } getSocketEvents() { return { clientToServer: [ { name: 'join_room', description: 'Unirse a una sala' }, { name: 'leave_room', description: 'Salir de una sala' }, { name: 'send_message', description: 'Enviar mensaje' }, { name: 'typing_start', description: 'Iniciar indicador de escritura' }, { name: 'typing_stop', description: 'Parar indicador de escritura' }, { name: 'user_status_change', description: 'Cambiar estado de usuario' } ], serverToClient: [ { name: 'message_received', description: 'Mensaje recibido' }, { name: 'user_joined', description: 'Usuario se unió' }, { name: 'user_left', description: 'Usuario salió' }, { name: 'typing_indicator', description: 'Indicador de escritura' }, { name: 'notification', description: 'Notificación general' }, { name: 'presence_update', description: 'Actualización de presencia' } ] }; } getRoomTypes() { return [ { type: 'public', description: 'Salas públicas accesibles por todos' }, { type: 'private', description: 'Salas privadas con invitación' }, { type: 'group', description: 'Salas de grupo con miembros específicos' }, { type: 'broadcast', description: 'Salas de difusión unidireccional' } ]; } } module.exports = WebSocketModuleInitializer;