bookworm-smart-assistant/skills/backend-builder/scripts/generate_controller.py

426 lines
13 KiB
Python
Raw Normal View History

#!/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()