@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
text/typescript
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();