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
JavaScript
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;