UNPKG

@diagramers/api

Version:

Diagramers API - A comprehensive Node.js API template with TypeScript, Firebase Functions, and Socket.io

424 lines (355 loc) 15.8 kB
#!/usr/bin/env node import * as fs from 'fs'; import * as path from 'path'; async function generateModule(moduleName: string): Promise<void> { // Validate module name if (!moduleName || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(moduleName)) { throw new Error('Invalid module name. Use only letters, numbers, hyphens, and underscores. Must start with a letter.'); } const moduleNameCapitalized = moduleName.charAt(0).toUpperCase() + moduleName.slice(1); const currentDir = process.cwd(); // Check for required API project structure const requiredDirs = ['src/modules']; const missingDirs = requiredDirs.filter(dir => !fs.existsSync(path.join(currentDir, dir))); if (missingDirs.length > 0) { throw new Error(`This command should be run from a diagramers API project. Missing directories: ${missingDirs.join(', ')}`); } // Create module directory structure const modulePath = path.join(currentDir, 'src/modules', moduleName); if (!fs.existsSync(modulePath)) { fs.mkdirSync(modulePath, { recursive: true }); fs.mkdirSync(path.join(modulePath, 'entities'), { recursive: true }); fs.mkdirSync(path.join(modulePath, 'schemas'), { recursive: true }); fs.mkdirSync(path.join(modulePath, 'services'), { recursive: true }); fs.mkdirSync(path.join(modulePath, 'controllers'), { recursive: true }); fs.mkdirSync(path.join(modulePath, 'routes'), { recursive: true }); } console.log('📝 Creating entity...'); const entityContent = generateEntityContent(moduleName, moduleNameCapitalized); fs.writeFileSync(path.join(modulePath, 'entities', `${moduleName}.entity.ts`), entityContent); console.log('📋 Creating schema...'); const schemaContent = generateSchemaContent(moduleName, moduleNameCapitalized); fs.writeFileSync(path.join(modulePath, 'schemas', `${moduleName}.schema.ts`), schemaContent); console.log('🔧 Creating service...'); const serviceContent = generateServiceContent(moduleName, moduleNameCapitalized); fs.writeFileSync(path.join(modulePath, 'services', `${moduleName}.service.ts`), serviceContent); console.log('🎮 Creating controller...'); const controllerContent = generateControllerContent(moduleName, moduleNameCapitalized); fs.writeFileSync(path.join(modulePath, 'controllers', `${moduleName}.controller.ts`), controllerContent); console.log('🛣️ Creating routes...'); const routesContent = generateRoutesContent(moduleName, moduleNameCapitalized); fs.writeFileSync(path.join(modulePath, 'routes', `${moduleName}.routes.ts`), routesContent); // Register routes in server manager console.log('🔗 Registering routes...'); await registerModuleRoutes(currentDir, moduleName, moduleNameCapitalized); // Create database table/collection console.log('🗄️ Creating database table...'); await createDatabaseTable(currentDir, moduleName, moduleNameCapitalized); console.log('✅ Module generation completed!'); console.log(`📁 Module: src/modules/${moduleName}/`); console.log(`🔗 Routes: /api/${moduleName}s`); console.log(`🗄️ Database: ${moduleName} collection`); } function generateEntityContent(moduleName: string, moduleNameCapitalized: string): string { return `import mongoose, { ObjectId } from 'mongoose'; export interface I${moduleNameCapitalized} extends mongoose.Document { _id: ObjectId; name: string; description?: string; status: number; createdAt: Date; updatedAt: Date; } export interface I${moduleNameCapitalized}Create { name: string; description?: string; } export interface I${moduleNameCapitalized}Update { name?: string; description?: string; status?: number; } `; } function generateSchemaContent(moduleName: string, moduleNameCapitalized: string): string { return `import mongoose from 'mongoose'; import { I${moduleNameCapitalized} } from '../entities/${moduleName}.entity'; export const ${moduleName}Schema = new mongoose.Schema<I${moduleNameCapitalized}>({ name: { type: String, required: true, trim: true, maxlength: 255 }, description: { type: String, required: false, trim: true, maxlength: 1000 }, status: { type: Number, default: 1, enum: [0, 1] // 0 = inactive, 1 = active } }, { timestamps: true, collection: '${moduleName}s' }); // Indexes ${moduleName}Schema.index({ name: 1 }); ${moduleName}Schema.index({ status: 1 }); ${moduleName}Schema.index({ createdAt: -1 }); export const ${moduleNameCapitalized}Entity = mongoose.model<I${moduleNameCapitalized}>('${moduleNameCapitalized}', ${moduleName}Schema); `; } function generateServiceContent(moduleName: string, moduleNameCapitalized: string): string { return `import { Result } from '../../shared/types/result'; import { ${moduleNameCapitalized}Entity, I${moduleNameCapitalized}, I${moduleNameCapitalized}Create, I${moduleNameCapitalized}Update } from '../schemas/${moduleName}.schema'; import { logger } from '../../core/logging'; export class ${moduleNameCapitalized}Service { async getAll(): Promise<Result> { try { const ${moduleName}s = await ${moduleNameCapitalized}Entity.find({ status: 1 }).sort({ createdAt: -1 }); return Result.success(${moduleName}s); } catch (error) { logger.error('Get all ${moduleName}s error:', error); return Result.error('Failed to get ${moduleName}s'); } } async getById(id: string): Promise<Result> { try { const ${moduleName} = await ${moduleNameCapitalized}Entity.findById(id); if (!${moduleName}) { return Result.notFound('${moduleNameCapitalized} not found'); } return Result.success(${moduleName}); } catch (error) { logger.error('Get ${moduleName} by ID error:', error); return Result.error('Failed to get ${moduleName}'); } } async create(data: I${moduleNameCapitalized}Create): Promise<Result> { try { const ${moduleName} = new ${moduleNameCapitalized}Entity(data); const saved${moduleNameCapitalized} = await ${moduleName}.save(); return Result.success(saved${moduleNameCapitalized}, '${moduleNameCapitalized} created successfully'); } catch (error) { logger.error('Create ${moduleName} error:', error); return Result.error('Failed to create ${moduleName}'); } } async update(id: string, data: I${moduleNameCapitalized}Update): Promise<Result> { try { const ${moduleName} = await ${moduleNameCapitalized}Entity.findByIdAndUpdate( id, { ...data, updatedAt: new Date() }, { new: true, runValidators: true } ); if (!${moduleName}) { return Result.notFound('${moduleNameCapitalized} not found'); } return Result.success(${moduleName}, '${moduleNameCapitalized} updated successfully'); } catch (error) { logger.error('Update ${moduleName} error:', error); return Result.error('Failed to update ${moduleName}'); } } async delete(id: string): Promise<Result> { try { const ${moduleName} = await ${moduleNameCapitalized}Entity.findByIdAndUpdate( id, { status: 0, updatedAt: new Date() }, { new: true } ); if (!${moduleName}) { return Result.notFound('${moduleNameCapitalized} not found'); } return Result.success(${moduleName}, '${moduleNameCapitalized} deleted successfully'); } catch (error) { logger.error('Delete ${moduleName} error:', error); return Result.error('Failed to delete ${moduleName}'); } } } `; } function generateControllerContent(moduleName: string, moduleNameCapitalized: string): string { return `import { Request, Response } from 'express'; import { ${moduleNameCapitalized}Service } from '../services/${moduleName}.service'; import { logger } from '../../core/logging'; export class ${moduleNameCapitalized}Controller { private ${moduleName}Service: ${moduleNameCapitalized}Service; constructor() { this.${moduleName}Service = new ${moduleNameCapitalized}Service(); } async getAll(req: Request, res: Response): Promise<void> { try { const result = await this.${moduleName}Service.getAll(); res.status(result.code).json(result); } catch (error) { logger.error('Get all ${moduleName}s controller error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } } async getById(req: Request, res: Response): Promise<void> { try { const { id } = req.params; const result = await this.${moduleName}Service.getById(id); res.status(result.code).json(result); } catch (error) { logger.error('Get ${moduleName} by ID controller error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } } async create(req: Request, res: Response): Promise<void> { try { const result = await this.${moduleName}Service.create(req.body); res.status(result.code).json(result); } catch (error) { logger.error('Create ${moduleName} controller error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } } async update(req: Request, res: Response): Promise<void> { try { const { id } = req.params; const result = await this.${moduleName}Service.update(id, req.body); res.status(result.code).json(result); } catch (error) { logger.error('Update ${moduleName} controller error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } } async delete(req: Request, res: Response): Promise<void> { try { const { id } = req.params; const result = await this.${moduleName}Service.delete(id); res.status(result.code).json(result); } catch (error) { logger.error('Delete ${moduleName} controller error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } } } `; } function generateRoutesContent(moduleName: string, moduleNameCapitalized: string): string { return `import { Router } from 'express'; import { ${moduleNameCapitalized}Controller } from '../controllers/${moduleName}.controller'; const router = Router(); const ${moduleName}Controller = new ${moduleNameCapitalized}Controller(); // ${moduleNameCapitalized} routes router.get('/${moduleName}s', ${moduleName}Controller.getAll.bind(${moduleName}Controller)); router.get('/${moduleName}s/:id', ${moduleName}Controller.getById.bind(${moduleName}Controller)); router.post('/${moduleName}s', ${moduleName}Controller.create.bind(${moduleName}Controller)); router.put('/${moduleName}s/:id', ${moduleName}Controller.update.bind(${moduleName}Controller)); router.delete('/${moduleName}s/:id', ${moduleName}Controller.delete.bind(${moduleName}Controller)); export default router; `; } async function registerModuleRoutes(currentDir: string, moduleName: string, moduleNameCapitalized: string): Promise<void> { const serverManagerPath = path.join(currentDir, 'src/core/server/manager.ts'); if (!fs.existsSync(serverManagerPath)) { console.warn('⚠️ Server manager not found, routes will need to be registered manually'); return; } let serverManagerContent = fs.readFileSync(serverManagerPath, 'utf8'); // Add import for the new module routes const importStatement = `const ${moduleName}Routes = require('../../modules/${moduleName}/routes/${moduleName}.routes').default;`; // Find the registerPluginRoutes method const methodRegex = /private registerPluginRoutes\(\): void \{([^}]*)\}/s; const match = serverManagerContent.match(methodRegex); if (match) { const methodContent = match[1]; // Add import if not already present if (!methodContent.includes(importStatement)) { const updatedMethodContent = methodContent + `\n ${importStatement}`; serverManagerContent = serverManagerContent.replace(methodRegex, `private registerPluginRoutes(): void {$1${updatedMethodContent}\n }`); } // Add route registration const routeRegistration = `this.app.use('/api', ${moduleName}Routes);`; if (!methodContent.includes(routeRegistration)) { const updatedMethodContent = methodContent + `\n ${routeRegistration}`; serverManagerContent = serverManagerContent.replace(methodRegex, `private registerPluginRoutes(): void {$1${updatedMethodContent}\n }`); } fs.writeFileSync(serverManagerPath, serverManagerContent); console.log('✅ Routes registered in server manager'); } else { console.warn('⚠️ Could not find registerPluginRoutes method, routes will need to be registered manually'); } } async function createDatabaseTable(currentDir: string, moduleName: string, moduleNameCapitalized: string): Promise<void> { // Create a database seeder script for the new module const seederScript = `import { ${moduleNameCapitalized}Entity } from '../schemas/${moduleName}.schema'; import { logger } from '../../core/logging'; export async function seed${moduleNameCapitalized}Table(): Promise<void> { try { // Create collection and indexes await ${moduleNameCapitalized}Entity.createCollection(); // Create indexes if needed await ${moduleNameCapitalized}Entity.collection.createIndex({ name: 1 }); await ${moduleNameCapitalized}Entity.collection.createIndex({ createdAt: -1 }); logger.info(\`✅ ${moduleNameCapitalized} table/collection created successfully\`); } catch (error) { logger.error(\`❌ Failed to create ${moduleNameCapitalized} table:\`, error); throw error; } } // Auto-run when imported seed${moduleNameCapitalized}Table().catch(console.error); `; const seederPath = path.join(currentDir, 'src/scripts', `seed-${moduleName}.ts`); fs.writeFileSync(seederPath, seederScript); // Add to main seeder const mainSeederPath = path.join(currentDir, 'src/core/database/seeder.ts'); if (fs.existsSync(mainSeederPath)) { let seederContent = fs.readFileSync(mainSeederPath, 'utf8'); // Add import const importStatement = `import { seed${moduleNameCapitalized}Table } from '../../scripts/seed-${moduleName}';`; if (!seederContent.includes(importStatement)) { seederContent = seederContent.replace( /import.*from.*['"];?\s*$/gm, `$&\n${importStatement}` ); } // Add to seeding methods const seedMethodRegex = /async seedAll\(\): Promise<void> \{([^}]*)\}/s; const match = seederContent.match(seedMethodRegex); if (match) { const methodContent = match[1]; const seedCall = `await seed${moduleNameCapitalized}Table();`; if (!methodContent.includes(seedCall)) { const updatedMethodContent = methodContent + `\n ${seedCall}`; seederContent = seederContent.replace(seedMethodRegex, `async seedAll(): Promise<void> {$1${updatedMethodContent}\n }`); } } fs.writeFileSync(mainSeederPath, seederContent); } console.log('✅ Database table creation script added'); } async function main() { const args = process.argv.slice(2); const moduleName = args[0]; if (!moduleName) { console.error('❌ Module name is required'); console.log('Usage: npm run generate:module <module-name>'); console.log('Example: npm run generate:module product'); process.exit(1); } try { await generateModule(moduleName); console.log('✅ Module generated successfully!'); console.log('🚀 Next steps:'); console.log(' 1. Review the generated files'); console.log(' 2. Customize business logic'); console.log(' 3. Run: npm start'); } catch (error: any) { console.error('❌ Failed to generate module:', error.message); process.exit(1); } } main();