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.
563 lines (470 loc) • 17.9 kB
JavaScript
// modules/database/init.js
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
class DatabaseModuleInitializer {
constructor(config = {}) {
// Si se pasa projectConfig, usar las tablas de ahí
const projectConfig = config.projectConfig || {};
const databaseConfig = projectConfig.database || config.database || {};
// Inicializar config primero sin el provider
this.config = {
projectPath: config.projectPath || process.cwd(),
features: config.features || ['prisma-orm', 'health-checks', 'migrations'],
poolSize: config.poolSize || 10,
enableLogging: config.enableLogging || false,
database: databaseConfig,
...config
};
// Ahora detectar el provider después de que this.config esté inicializado
this.config.provider = config.provider || this.detectDatabaseProvider(config);
// Log para debug
console.log('🔍 DatabaseModuleInitializer configurado con:');
console.log(' - Provider:', this.config.provider);
console.log(' - Database config:', JSON.stringify(this.config.database, null, 2));
console.log(' - Tables found:', this.config.database?.tables?.length || 0);
this.metadata = {
module: 'database',
version: '1.0.0',
generatedFiles: [],
dependencies: [],
environment: [],
provider: this.config.provider
};
}
async initialize() {
console.log('🗄️ Inicializando módulo de base de datos...');
try {
// 1. Detectar proveedor de base de datos
await this.detectDatabaseProvider();
// 2. Configurar Prisma
await this.setupPrismaConfiguration();
// 3. Crear schema base
await this.createBaseSchema();
// 4. Configurar pool de conexiones
await this.configureConnectionPool();
// 5. Configurar health checks
await this.setupHealthChecks();
// 6. Crear archivos de seeding
await this.createSeederFiles();
// 7. Retornar metadata
return this.returnDatabaseMetadata();
} catch (error) {
console.error('❌ Error inicializando módulo database:', error.message);
throw error;
}
}
detectDatabaseProvider(config = {}) {
// Detectar desde variables de entorno
if (process.env.DATABASE_URL) {
if (process.env.DATABASE_URL.includes('postgresql')) return 'postgresql';
if (process.env.DATABASE_URL.includes('mysql')) return 'mysql';
if (process.env.DATABASE_URL.includes('mongodb')) return 'mongodb';
if (process.env.DATABASE_URL.includes('sqlite') || process.env.DATABASE_URL.includes('file:')) return 'sqlite';
}
// Detectar desde configuración
if (config.database) {
const db = config.database.toLowerCase();
if (db.includes('postgres')) return 'postgresql';
if (db.includes('mysql')) return 'mysql';
if (db.includes('mongo')) return 'mongodb';
if (db.includes('sqlite')) return 'sqlite';
}
// Detectar desde dependencias existentes
const projectPath = config.projectPath || this.config?.projectPath || process.cwd();
const packageJsonPath = path.join(projectPath, 'package.json');
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
if (deps.pg || deps['@types/pg']) return 'postgresql';
if (deps.mysql2 || deps.mysql) return 'mysql';
if (deps.mongodb || deps.mongoose) return 'mongodb';
if (deps.sqlite3 || deps['better-sqlite3']) return 'sqlite';
}
// Por defecto PostgreSQL para aplicaciones empresariales
return 'postgresql';
}
async setupPrismaConfiguration() {
console.log('⚙️ Configurando Prisma...');
// Crear directorio prisma si no existe
const prismaDir = path.join(this.config.projectPath, 'prisma');
if (!fs.existsSync(prismaDir)) {
fs.mkdirSync(prismaDir, { recursive: true });
}
// Configurar variables de entorno
await this.setupEnvironmentVariables();
// Instalar dependencias de Prisma si no están instaladas
await this.installPrismaDependencies();
this.metadata.generatedFiles.push('prisma/schema.prisma');
}
async setupEnvironmentVariables() {
const envPath = path.join(this.config.projectPath, '.env');
const envExamplePath = path.join(this.config.projectPath, '.env.example');
// Obtener configuración de base de datos
const databaseConfig = this.getDatabaseConfig();
// Usar DATABASE_URL del agente si está disponible
let databaseUrl;
if (process.env.DATABASE_URL && this.config.configuredFromAgent) {
console.log('🔗 Usando DATABASE_URL configurada desde el agente');
databaseUrl = process.env.DATABASE_URL;
} else {
databaseUrl = databaseConfig.url;
}
const envContent = [
'# Database Configuration',
`DATABASE_URL="${databaseUrl}"`,
`DATABASE_PROVIDER="${this.config.provider}"`,
`DATABASE_POOL_SIZE=${this.config.poolSize}`,
`DATABASE_TIMEOUT=5000`,
`DATABASE_SSL=false`,
`DATABASE_LOGGING=${this.config.enableLogging}`,
''
].join('\n');
// Agregar a .env si no existe
if (!fs.existsSync(envPath)) {
fs.writeFileSync(envPath, envContent);
} else {
const existingEnv = fs.readFileSync(envPath, 'utf8');
if (!existingEnv.includes('DATABASE_URL')) {
fs.appendFileSync(envPath, '\n' + envContent);
}
}
// Agregar a .env.example
const exampleContent = envContent.replace(
databaseConfig.url,
databaseConfig.exampleUrl
);
if (!fs.existsSync(envExamplePath)) {
fs.writeFileSync(envExamplePath, exampleContent);
} else {
const existingExample = fs.readFileSync(envExamplePath, 'utf8');
if (!existingExample.includes('DATABASE_URL')) {
fs.appendFileSync(envExamplePath, '\n' + exampleContent);
}
}
this.metadata.environment.push(
'DATABASE_URL', 'DATABASE_PROVIDER', 'DATABASE_POOL_SIZE',
'DATABASE_TIMEOUT', 'DATABASE_SSL', 'DATABASE_LOGGING'
);
}
getDatabaseConfig() {
const configs = {
postgresql: {
url: 'postgresql://username:password@localhost:5432/database_name?schema=public',
exampleUrl: 'postgresql://username:password@localhost:5432/database_name?schema=public'
},
mysql: {
url: 'mysql://username:password@localhost:3306/database_name',
exampleUrl: 'mysql://username:password@localhost:3306/database_name'
},
mongodb: {
url: 'mongodb://username:password@localhost:27017/database_name',
exampleUrl: 'mongodb://username:password@localhost:27017/database_name'
},
sqlite: {
url: 'file:./dev.db',
exampleUrl: 'file:./dev.db'
}
};
return configs[this.config.provider] || configs.postgresql;
}
async installPrismaDependencies() {
const packageJsonPath = path.join(this.config.projectPath, 'package.json');
let packageJson;
if (!fs.existsSync(packageJsonPath)) {
console.log('📦 Creando package.json...');
packageJson = {
name: 'backend-api',
version: '1.0.0',
description: 'Backend API generated by MCP',
main: 'src/index.ts',
scripts: {
'dev': 'tsx watch src/index.ts',
'build': 'tsc',
'start': 'node dist/index.js',
'db:generate': 'prisma generate',
'db:push': 'prisma db push',
'db:migrate': 'prisma migrate dev',
'db:deploy': 'prisma migrate deploy',
'db:seed': 'tsx prisma/seed.ts'
},
dependencies: {},
devDependencies: {}
};
} else {
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
}
const dependencies = ['@prisma/client'];
const devDependencies = ['prisma'];
const providerDeps = {
postgresql: ['pg', '@types/pg'],
mysql: ['mysql2'],
mongodb: [],
sqlite: []
};
// Add provider-specific dependencies
const providerSpecific = providerDeps[this.config.provider] || [];
dependencies.push(...providerSpecific.filter(dep => !dep.startsWith('@types')));
devDependencies.push(...providerSpecific.filter(dep => dep.startsWith('@types')));
// Ensure dependencies and devDependencies objects exist
if (!packageJson.dependencies) packageJson.dependencies = {};
if (!packageJson.devDependencies) packageJson.devDependencies = {};
// Add dependencies to package.json
dependencies.forEach(dep => {
packageJson.dependencies[dep] = 'latest';
});
devDependencies.forEach(dep => {
packageJson.devDependencies[dep] = 'latest';
});
// Write updated package.json
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
this.metadata.dependencies = [...dependencies, ...devDependencies];
}
async createBaseSchema() {
console.log('📋 Creando schema base...');
const schemaPath = path.join(this.config.projectPath, 'prisma', 'schema.prisma');
const schema = this.generatePrismaSchema();
fs.writeFileSync(schemaPath, schema);
this.metadata.generatedFiles.push('prisma/schema.prisma');
}
generatePrismaSchema() {
const providerConfig = {
postgresql: 'postgresql',
mysql: 'mysql',
mongodb: 'mongodb',
sqlite: 'sqlite'
};
let schema = `// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
previewFeatures = ["fullTextSearch", "metrics"]
}
datasource db {
provider = "${providerConfig[this.config.provider]}"
url = env("DATABASE_URL")
}
`;
// Add user-defined tables if they exist
if (this.config.database && this.config.database.tables && this.config.database.tables.length > 0) {
console.log('📋 Generando modelos desde tablas definidas:', this.config.database.tables.map(t => t.tableName));
for (const table of this.config.database.tables) {
schema += this.generateModelFromTable(table);
}
} else {
console.log('📋 No se encontraron tablas definidas, usando modelos por defecto');
// Default models if no tables are defined
schema += `// User model - base entity for most applications
model User {
id String
email String
password String
name String?
role String
isActive Boolean
createdAt DateTime
updatedAt DateTime
@
}
`;
}
// Always add audit and health check models
schema += `// Base model with common fields
model AuditLog {
id String
userId String?
action String
entityType String?
entityId String?
details Json?
ipAddress String?
userAgent String?
createdAt DateTime
@
}
// Health check model for database monitoring
model HealthCheck {
id String
service String
status String
details Json?
checkedAt DateTime
@
}
`;
return schema;
}
generateModelFromTable(table) {
const { tableName, fields, relations = [] } = table;
// Capitalize first letter for model name
const modelName = tableName.charAt(0).toUpperCase() + tableName.slice(1);
let model = `// ${tableName} model
model ${modelName} {
`;
// Add all fields from the table definition
for (const field of fields) {
const prismaType = this.mapToPrismaType(field.type);
const isRequired = field.required ? '' : '?';
const isUnique = field.unique ? ' @unique' : '';
const isId = field.name === 'id' ? (field.type === 'String' ? ' @id @default(cuid())' : ' @id @default(autoincrement())') : '';
const defaultValue = field.defaultValue && field.name !== 'id' ? ` ` : '';
const autoTimestamp = field.name === 'createdAt' ? ' @default(now())' : field.name === 'updatedAt' ? ' @updatedAt' : '';
model += ` ${field.name.padEnd(12)} ${prismaType}${isRequired}${isId}${isUnique}${defaultValue}${autoTimestamp}
`;
}
// Add relations if any
if (relations && relations.length > 0) {
model += `
// Relations
`;
for (const relation of relations) {
if (relation.type === 'manyToOne' && relation.foreignKey) {
// Many-to-one relation (this model has foreign key)
const relatedModelName = relation.relatedTable.charAt(0).toUpperCase() + relation.relatedTable.slice(1);
const relationName = relation.foreignKey.replace('Id', '').replace('_id', '');
model += ` ${relationName.padEnd(12)} ${relatedModelName}?
`;
} else if (relation.type === 'oneToMany') {
// One-to-many relation (this model is referenced by foreign key)
const relatedModelName = relation.relatedTable.charAt(0).toUpperCase() + relation.relatedTable.slice(1);
const relationName = relation.relatedTable.toLowerCase() + 's';
model += ` ${relationName.padEnd(12)} ${relatedModelName}[]
`;
}
}
}
// Add table mapping
model += `
@
`;
model += `}
`;
return model;
}
formatDefaultValue(value, type) {
if (type === 'String') {
return `"${value}"`;
}
if (type === 'Boolean') {
return value.toString().toLowerCase();
}
return value;
}
mapToPrismaType(type) {
const typeMapping = {
'String': 'String',
'Int': 'Int',
'Float': 'Float',
'Boolean': 'Boolean',
'DateTime': 'DateTime',
'Json': 'Json'
};
return typeMapping[type] || 'String';
}
async configureConnectionPool() {
console.log('🔗 Configurando pool de conexiones...');
const connectionPath = path.join(this.config.projectPath, 'src', 'database');
if (!fs.existsSync(connectionPath)) {
fs.mkdirSync(connectionPath, { recursive: true });
}
this.metadata.generatedFiles.push(
'src/database/connection.ts',
'src/database/database.config.ts'
);
}
async setupHealthChecks() {
console.log('🏥 Configurando health checks...');
const healthPath = path.join(this.config.projectPath, 'src', 'health');
if (!fs.existsSync(healthPath)) {
fs.mkdirSync(healthPath, { recursive: true });
}
this.metadata.generatedFiles.push(
'src/health/database.health.ts',
'src/health/health.controller.ts'
);
}
async createSeederFiles() {
console.log('🌱 Creando archivos de seeding...');
const seederPath = path.join(this.config.projectPath, 'prisma', 'seed.ts');
const seederContent = `import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log('🌱 Seeding database...');
// Add your seed data here
console.log('✅ Database seeded successfully');
}
main()
.catch((e) => {
console.error('❌ Error seeding database:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
`;
fs.writeFileSync(seederPath, seederContent);
this.metadata.generatedFiles.push('prisma/seed.ts');
}
returnDatabaseMetadata() {
console.log('✅ Módulo de base de datos inicializado correctamente');
return {
...this.metadata,
config: {
provider: this.config.provider,
features: this.config.features,
poolSize: this.config.poolSize,
loggingEnabled: this.config.enableLogging
},
instructions: {
nextSteps: [
'Instalar dependencias: npm install prisma @prisma/client',
'Generar cliente Prisma: npx prisma generate',
'Aplicar schema: npx prisma db push',
'Ejecutar seeds: npm run db:seed'
],
development: [
'Usar prisma studio: npx prisma studio',
'Ver logs de consultas en desarrollo',
'Configurar backup automático'
],
production: [
'Usar migraciones: npx prisma migrate deploy',
'Configurar SSL para la conexión',
'Monitorear métricas de rendimiento',
'Implementar estrategia de backup'
]
},
endpoints: [
{ method: 'GET', path: '/health/database', description: 'Database health check' },
{ method: 'GET', path: '/admin/database/stats', description: 'Database statistics' }
]
};
}
}
// Función principal para uso con agentes IA
async function initializeDatabaseModule(config = {}) {
const initializer = new DatabaseModuleInitializer(config);
return await initializer.initialize();
}
// Exportar para uso en otros módulos
module.exports = {
DatabaseModuleInitializer,
initializeDatabaseModule
};
// Si se ejecuta directamente
if (require.main === module) {
const config = {
projectPath: process.argv[2] || process.cwd(),
provider: process.argv[3] || undefined,
features: process.argv[4] ? process.argv[4].split(',') : undefined
};
initializeDatabaseModule(config)
.then(result => {
console.log('\n📊 Resultado de inicialización:');
console.log(JSON.stringify(result, null, 2));
})
.catch(error => {
console.error('❌ Error:', error.message);
process.exit(1);
});
}