426 lines
13 KiB
Python
426 lines
13 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
Controller/Route Generator
|
||
|
|
Creates CRUD controllers and route definitions from models
|
||
|
|
"""
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import os
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
class ControllerGenerator:
|
||
|
|
def __init__(self, framework: str):
|
||
|
|
self.framework = framework
|
||
|
|
|
||
|
|
def generate(self, model_name: str, output_dir: str = "."):
|
||
|
|
"""Generate controller and routes for a model"""
|
||
|
|
if self.framework == "express":
|
||
|
|
return self._generate_express(model_name, output_dir)
|
||
|
|
elif self.framework == "fastapi":
|
||
|
|
return self._generate_fastapi(model_name, output_dir)
|
||
|
|
elif self.framework == "nestjs":
|
||
|
|
return self._generate_nestjs(model_name, output_dir)
|
||
|
|
else:
|
||
|
|
raise ValueError(f"Unsupported framework: {self.framework}")
|
||
|
|
|
||
|
|
def _generate_express(self, model_name: str, output_dir: str):
|
||
|
|
"""Generate Express.js controller and routes"""
|
||
|
|
model_lower = model_name.lower()
|
||
|
|
model_plural = model_lower + 's'
|
||
|
|
|
||
|
|
# Controller file
|
||
|
|
controller_code = f'''const {{ {model_name} }} = require('../models/{model_lower}');
|
||
|
|
const {{ validationResult }} = require('express-validator');
|
||
|
|
|
||
|
|
// @desc Get all {model_plural}
|
||
|
|
// @route GET /api/{model_plural}
|
||
|
|
// @access Public
|
||
|
|
exports.getAll{model_name}s = async (req, res) => {{
|
||
|
|
try {{
|
||
|
|
const {{ page = 1, limit = 20, sort = '-createdAt' }} = req.query;
|
||
|
|
const skip = (page - 1) * limit;
|
||
|
|
|
||
|
|
const {model_plural} = await {model_name}.find()
|
||
|
|
.sort(sort)
|
||
|
|
.skip(skip)
|
||
|
|
.limit(parseInt(limit));
|
||
|
|
|
||
|
|
const total = await {model_name}.countDocuments();
|
||
|
|
|
||
|
|
res.json({{
|
||
|
|
success: true,
|
||
|
|
data: {model_plural},
|
||
|
|
pagination: {{
|
||
|
|
page: parseInt(page),
|
||
|
|
limit: parseInt(limit),
|
||
|
|
total,
|
||
|
|
totalPages: Math.ceil(total / limit)
|
||
|
|
}}
|
||
|
|
}});
|
||
|
|
}} catch (error) {{
|
||
|
|
res.status(500).json({{
|
||
|
|
success: false,
|
||
|
|
error: {{ message: 'Server error', details: error.message }}
|
||
|
|
}});
|
||
|
|
}}
|
||
|
|
}};
|
||
|
|
|
||
|
|
// @desc Get single {model_lower}
|
||
|
|
// @route GET /api/{model_plural}/:id
|
||
|
|
// @access Public
|
||
|
|
exports.get{model_name} = async (req, res) => {{
|
||
|
|
try {{
|
||
|
|
const {model_lower} = await {model_name}.findById(req.params.id);
|
||
|
|
|
||
|
|
if (!{model_lower}) {{
|
||
|
|
return res.status(404).json({{
|
||
|
|
success: false,
|
||
|
|
error: {{ message: '{model_name} not found' }}
|
||
|
|
}});
|
||
|
|
}}
|
||
|
|
|
||
|
|
res.json({{
|
||
|
|
success: true,
|
||
|
|
data: {model_lower}
|
||
|
|
}});
|
||
|
|
}} catch (error) {{
|
||
|
|
res.status(500).json({{
|
||
|
|
success: false,
|
||
|
|
error: {{ message: 'Server error', details: error.message }}
|
||
|
|
}});
|
||
|
|
}}
|
||
|
|
}};
|
||
|
|
|
||
|
|
// @desc Create {model_lower}
|
||
|
|
// @route POST /api/{model_plural}
|
||
|
|
// @access Private
|
||
|
|
exports.create{model_name} = async (req, res) => {{
|
||
|
|
try {{
|
||
|
|
const errors = validationResult(req);
|
||
|
|
if (!errors.isEmpty()) {{
|
||
|
|
return res.status(400).json({{
|
||
|
|
success: false,
|
||
|
|
error: {{
|
||
|
|
message: 'Validation failed',
|
||
|
|
details: errors.array()
|
||
|
|
}}
|
||
|
|
}});
|
||
|
|
}}
|
||
|
|
|
||
|
|
const {model_lower} = await {model_name}.create(req.body);
|
||
|
|
|
||
|
|
res.status(201).json({{
|
||
|
|
success: true,
|
||
|
|
data: {model_lower},
|
||
|
|
message: '{model_name} created successfully'
|
||
|
|
}});
|
||
|
|
}} catch (error) {{
|
||
|
|
res.status(500).json({{
|
||
|
|
success: false,
|
||
|
|
error: {{ message: 'Server error', details: error.message }}
|
||
|
|
}});
|
||
|
|
}}
|
||
|
|
}};
|
||
|
|
|
||
|
|
// @desc Update {model_lower}
|
||
|
|
// @route PUT /api/{model_plural}/:id
|
||
|
|
// @access Private
|
||
|
|
exports.update{model_name} = async (req, res) => {{
|
||
|
|
try {{
|
||
|
|
const {model_lower} = await {model_name}.findByIdAndUpdate(
|
||
|
|
req.params.id,
|
||
|
|
req.body,
|
||
|
|
{{ new: true, runValidators: true }}
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!{model_lower}) {{
|
||
|
|
return res.status(404).json({{
|
||
|
|
success: false,
|
||
|
|
error: {{ message: '{model_name} not found' }}
|
||
|
|
}});
|
||
|
|
}}
|
||
|
|
|
||
|
|
res.json({{
|
||
|
|
success: true,
|
||
|
|
data: {model_lower},
|
||
|
|
message: '{model_name} updated successfully'
|
||
|
|
}});
|
||
|
|
}} catch (error) {{
|
||
|
|
res.status(500).json({{
|
||
|
|
success: false,
|
||
|
|
error: {{ message: 'Server error', details: error.message }}
|
||
|
|
}});
|
||
|
|
}}
|
||
|
|
}};
|
||
|
|
|
||
|
|
// @desc Delete {model_lower}
|
||
|
|
// @route DELETE /api/{model_plural}/:id
|
||
|
|
// @access Private
|
||
|
|
exports.delete{model_name} = async (req, res) => {{
|
||
|
|
try {{
|
||
|
|
const {model_lower} = await {model_name}.findByIdAndDelete(req.params.id);
|
||
|
|
|
||
|
|
if (!{model_lower}) {{
|
||
|
|
return res.status(404).json({{
|
||
|
|
success: false,
|
||
|
|
error: {{ message: '{model_name} not found' }}
|
||
|
|
}});
|
||
|
|
}}
|
||
|
|
|
||
|
|
res.json({{
|
||
|
|
success: true,
|
||
|
|
message: '{model_name} deleted successfully'
|
||
|
|
}});
|
||
|
|
}} catch (error) {{
|
||
|
|
res.status(500).json({{
|
||
|
|
success: false,
|
||
|
|
error: {{ message: 'Server error', details: error.message }}
|
||
|
|
}});
|
||
|
|
}}
|
||
|
|
}};
|
||
|
|
'''
|
||
|
|
|
||
|
|
# Routes file
|
||
|
|
routes_code = f'''const express = require('express');
|
||
|
|
const router = express.Router();
|
||
|
|
const {{ body }} = require('express-validator');
|
||
|
|
const {{
|
||
|
|
getAll{model_name}s,
|
||
|
|
get{model_name},
|
||
|
|
create{model_name},
|
||
|
|
update{model_name},
|
||
|
|
delete{model_name}
|
||
|
|
}} = require('../controllers/{model_lower}Controller');
|
||
|
|
const {{ protect }} = require('../middleware/auth');
|
||
|
|
|
||
|
|
// Validation rules
|
||
|
|
const create{model_name}Validation = [
|
||
|
|
body('name').notEmpty().withMessage('Name is required'),
|
||
|
|
// Add more validation rules as needed
|
||
|
|
];
|
||
|
|
|
||
|
|
// Public routes
|
||
|
|
router.get('/', getAll{model_name}s);
|
||
|
|
router.get('/:id', get{model_name});
|
||
|
|
|
||
|
|
// Protected routes
|
||
|
|
router.post('/', protect, create{model_name}Validation, create{model_name});
|
||
|
|
router.put('/:id', protect, update{model_name});
|
||
|
|
router.delete('/:id', protect, delete{model_name});
|
||
|
|
|
||
|
|
module.exports = router;
|
||
|
|
'''
|
||
|
|
|
||
|
|
# Write files
|
||
|
|
controller_path = Path(output_dir) / "controllers" / f"{model_lower}Controller.js"
|
||
|
|
routes_path = Path(output_dir) / "routes" / f"{model_lower}Routes.js"
|
||
|
|
|
||
|
|
controller_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
routes_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
controller_path.write_text(controller_code)
|
||
|
|
routes_path.write_text(routes_code)
|
||
|
|
|
||
|
|
return {
|
||
|
|
'controller': str(controller_path),
|
||
|
|
'routes': str(routes_path)
|
||
|
|
}
|
||
|
|
|
||
|
|
def _generate_fastapi(self, model_name: str, output_dir: str):
|
||
|
|
"""Generate FastAPI router"""
|
||
|
|
model_lower = model_name.lower()
|
||
|
|
model_plural = model_lower + 's'
|
||
|
|
|
||
|
|
router_code = f'''from fastapi import APIRouter, Depends, HTTPException, Query
|
||
|
|
from sqlalchemy.orm import Session
|
||
|
|
from typing import List, Optional
|
||
|
|
from ..database import get_db
|
||
|
|
from ..models.{model_lower} import {model_name}
|
||
|
|
from ..schemas.{model_lower} import {model_name}Create, {model_name}Update, {model_name}Response
|
||
|
|
from ..auth import get_current_user
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/{model_plural}", tags=["{model_plural}"])
|
||
|
|
|
||
|
|
@router.get("/", response_model=dict)
|
||
|
|
async def get_{model_plural}(
|
||
|
|
page: int = Query(1, ge=1),
|
||
|
|
limit: int = Query(20, ge=1, le=100),
|
||
|
|
db: Session = Depends(get_db)
|
||
|
|
):
|
||
|
|
"""Get all {model_plural} with pagination"""
|
||
|
|
skip = (page - 1) * limit
|
||
|
|
|
||
|
|
{model_plural} = db.query({model_name}).offset(skip).limit(limit).all()
|
||
|
|
total = db.query({model_name}).count()
|
||
|
|
|
||
|
|
return {{
|
||
|
|
"success": True,
|
||
|
|
"data": {model_plural},
|
||
|
|
"pagination": {{
|
||
|
|
"page": page,
|
||
|
|
"limit": limit,
|
||
|
|
"total": total,
|
||
|
|
"totalPages": (total + limit - 1) // limit
|
||
|
|
}}
|
||
|
|
}}
|
||
|
|
|
||
|
|
@router.get("/{{id}}", response_model={model_name}Response)
|
||
|
|
async def get_{model_lower}(id: str, db: Session = Depends(get_db)):
|
||
|
|
"""Get single {model_lower} by ID"""
|
||
|
|
{model_lower} = db.query({model_name}).filter({model_name}.id == id).first()
|
||
|
|
|
||
|
|
if not {model_lower}:
|
||
|
|
raise HTTPException(status_code=404, detail="{model_name} not found")
|
||
|
|
|
||
|
|
return {model_lower}
|
||
|
|
|
||
|
|
@router.post("/", response_model={model_name}Response, status_code=201)
|
||
|
|
async def create_{model_lower}(
|
||
|
|
{model_lower}_data: {model_name}Create,
|
||
|
|
db: Session = Depends(get_db),
|
||
|
|
current_user = Depends(get_current_user)
|
||
|
|
):
|
||
|
|
"""Create new {model_lower}"""
|
||
|
|
{model_lower} = {model_name}(**{model_lower}_data.dict())
|
||
|
|
db.add({model_lower})
|
||
|
|
db.commit()
|
||
|
|
db.refresh({model_lower})
|
||
|
|
|
||
|
|
return {model_lower}
|
||
|
|
|
||
|
|
@router.put("/{{id}}", response_model={model_name}Response)
|
||
|
|
async def update_{model_lower}(
|
||
|
|
id: str,
|
||
|
|
{model_lower}_data: {model_name}Update,
|
||
|
|
db: Session = Depends(get_db),
|
||
|
|
current_user = Depends(get_current_user)
|
||
|
|
):
|
||
|
|
"""Update {model_lower}"""
|
||
|
|
{model_lower} = db.query({model_name}).filter({model_name}.id == id).first()
|
||
|
|
|
||
|
|
if not {model_lower}:
|
||
|
|
raise HTTPException(status_code=404, detail="{model_name} not found")
|
||
|
|
|
||
|
|
for key, value in {model_lower}_data.dict(exclude_unset=True).items():
|
||
|
|
setattr({model_lower}, key, value)
|
||
|
|
|
||
|
|
db.commit()
|
||
|
|
db.refresh({model_lower})
|
||
|
|
|
||
|
|
return {model_lower}
|
||
|
|
|
||
|
|
@router.delete("/{{id}}", status_code=200)
|
||
|
|
async def delete_{model_lower}(
|
||
|
|
id: str,
|
||
|
|
db: Session = Depends(get_db),
|
||
|
|
current_user = Depends(get_current_user)
|
||
|
|
):
|
||
|
|
"""Delete {model_lower}"""
|
||
|
|
{model_lower} = db.query({model_name}).filter({model_name}.id == id).first()
|
||
|
|
|
||
|
|
if not {model_lower}:
|
||
|
|
raise HTTPException(status_code=404, detail="{model_name} not found")
|
||
|
|
|
||
|
|
db.delete({model_lower})
|
||
|
|
db.commit()
|
||
|
|
|
||
|
|
return {{"success": True, "message": "{model_name} deleted successfully"}}
|
||
|
|
'''
|
||
|
|
|
||
|
|
router_path = Path(output_dir) / "routers" / f"{model_lower}.py"
|
||
|
|
router_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
router_path.write_text(router_code)
|
||
|
|
|
||
|
|
return {'router': str(router_path)}
|
||
|
|
|
||
|
|
def _generate_nestjs(self, model_name: str, output_dir: str):
|
||
|
|
"""Generate NestJS controller"""
|
||
|
|
model_lower = model_name.lower()
|
||
|
|
model_plural = model_lower + 's'
|
||
|
|
|
||
|
|
controller_code = f'''import {{
|
||
|
|
Controller,
|
||
|
|
Get,
|
||
|
|
Post,
|
||
|
|
Put,
|
||
|
|
Delete,
|
||
|
|
Body,
|
||
|
|
Param,
|
||
|
|
Query,
|
||
|
|
UseGuards,
|
||
|
|
HttpCode,
|
||
|
|
HttpStatus,
|
||
|
|
}} from '@nestjs/common';
|
||
|
|
import {{ {model_name}Service }} from './{model_lower}.service';
|
||
|
|
import {{ Create{model_name}Dto, Update{model_name}Dto }} from './dto';
|
||
|
|
import {{ JwtAuthGuard }} from '../auth/guards/jwt-auth.guard';
|
||
|
|
|
||
|
|
@Controller('{model_plural}')
|
||
|
|
export class {model_name}Controller {{
|
||
|
|
constructor(private readonly {model_lower}Service: {model_name}Service) {{}}
|
||
|
|
|
||
|
|
@Get()
|
||
|
|
async findAll(
|
||
|
|
@Query('page') page: number = 1,
|
||
|
|
@Query('limit') limit: number = 20,
|
||
|
|
) {{
|
||
|
|
return this.{model_lower}Service.findAll(page, limit);
|
||
|
|
}}
|
||
|
|
|
||
|
|
@Get(':id')
|
||
|
|
async findOne(@Param('id') id: string) {{
|
||
|
|
return this.{model_lower}Service.findOne(id);
|
||
|
|
}}
|
||
|
|
|
||
|
|
@Post()
|
||
|
|
@UseGuards(JwtAuthGuard)
|
||
|
|
@HttpCode(HttpStatus.CREATED)
|
||
|
|
async create(@Body() create{model_name}Dto: Create{model_name}Dto) {{
|
||
|
|
return this.{model_lower}Service.create(create{model_name}Dto);
|
||
|
|
}}
|
||
|
|
|
||
|
|
@Put(':id')
|
||
|
|
@UseGuards(JwtAuthGuard)
|
||
|
|
async update(
|
||
|
|
@Param('id') id: string,
|
||
|
|
@Body() update{model_name}Dto: Update{model_name}Dto,
|
||
|
|
) {{
|
||
|
|
return this.{model_lower}Service.update(id, update{model_name}Dto);
|
||
|
|
}}
|
||
|
|
|
||
|
|
@Delete(':id')
|
||
|
|
@UseGuards(JwtAuthGuard)
|
||
|
|
async remove(@Param('id') id: string) {{
|
||
|
|
return this.{model_lower}Service.remove(id);
|
||
|
|
}}
|
||
|
|
}}
|
||
|
|
'''
|
||
|
|
|
||
|
|
controller_path = Path(output_dir) / model_lower / f"{model_lower}.controller.ts"
|
||
|
|
controller_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
controller_path.write_text(controller_code)
|
||
|
|
|
||
|
|
return {'controller': str(controller_path)}
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
parser = argparse.ArgumentParser(description='Generate CRUD controller and routes')
|
||
|
|
parser.add_argument('--model', required=True, help='Model name (e.g., User)')
|
||
|
|
parser.add_argument('--framework', required=True,
|
||
|
|
choices=['express', 'fastapi', 'nestjs'],
|
||
|
|
help='Backend framework')
|
||
|
|
parser.add_argument('--output', default='.', help='Output directory')
|
||
|
|
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
generator = ControllerGenerator(args.framework)
|
||
|
|
files = generator.generate(args.model, args.output)
|
||
|
|
|
||
|
|
print(f"✅ Generated controller files:")
|
||
|
|
for file_type, path in files.items():
|
||
|
|
print(f" {file_type}: {path}")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
main()
|