using Humanizer; using MycroForge.CLI.Extensions; using MycroForge.Core; namespace MycroForge.CLI.CodeGen; public class CrudRouterGenerator { private static readonly string[] Template = [ "from typing import Annotated", "from fastapi import APIRouter, Depends", "from fastapi.responses import JSONResponse, Response", "from fastapi.encoders import jsonable_encoder", "from %service_import_path% import %service_class_name%", "from %create_entity_request_import_path% import %create_entity_request_class_name%", "from %update_entity_request_import_path% import %update_entity_request_class_name%", "", "router = APIRouter()", "", "@router.get(\"/\")", "async def list(", "\tservice: Annotated[%service_class_name%, Depends()]", "):", "\ttry:", "\t\tresult = await service.list()", "\t\treturn JSONResponse(status_code=200, content=jsonable_encoder(result))", "\texcept Exception as ex:", "\t\tprint(str(ex))", "\t\treturn JSONResponse(status_code=500, content=str(ex))", "", "@router.get(\"/{id}\")", "async def get_by_id(", "\tid: int,", "\tservice: Annotated[%service_class_name%, Depends()]", "):", "\ttry:", "\t\tresult = await service.get_by_id(id)", "\t\treturn JSONResponse(status_code=200 if result is not None else 404, content=jsonable_encoder(result))", "\texcept Exception as ex:", "\t\tprint(str(ex))", "\t\treturn JSONResponse(status_code=500, content=str(ex))", "", "@router.post(\"/\")", "async def create(", "\trequest: Create%entity_class_name%Request,", "\tservice: Annotated[%service_class_name%, Depends()]", "):", "\ttry:", "\t\tawait service.create(request.model_dump())", "\t\treturn JSONResponse(status_code=201, content=None)", "\texcept Exception as ex:", "\t\tprint(str(ex))", "\t\treturn JSONResponse(status_code=500, content=str(ex))", "", "@router.patch(\"/{id}\")", "async def update(", "\tid: int,", "\trequest: Update%entity_class_name%Request,", "\tservice: Annotated[%service_class_name%, Depends()]", "):", "\ttry:", "\t\tupdated = await service.update(id, request.model_dump(exclude_unset=True))", "\t\treturn Response(status_code=204 if updated else 404)", "\texcept Exception as ex:", "\t\tprint(str(ex))", "\t\treturn JSONResponse(status_code=500, content=str(ex))", "", "@router.delete(\"/{id}\")", "async def delete(", "\tid: int,", "\tservice: Annotated[%service_class_name%, Depends()]", "):", "\ttry:", "\t\tdeleted = await service.delete(id)", "\t\treturn JSONResponse(status_code=204 if deleted else 404, content=None)", "\texcept Exception as ex:", "\t\tprint(str(ex))", "\t\treturn JSONResponse(status_code=500, content=str(ex))", ]; private readonly ProjectContext _context; public CrudRouterGenerator(ProjectContext context) { _context = context; } public async Task Generate(string path, string entity) { var entitySnakeCaseName = entity.Underscore().ToLower(); var entityClassName = entity.Pascalize(); var serviceClassName = $"{entityClassName}Service"; var entityRoutePrefix = entity.Kebaberize().Pluralize().ToLower(); var servicesFolderPath = $"{Features.Api.FeatureName}/services/{path}"; var serviceFilePath = $"{servicesFolderPath}/{entitySnakeCaseName}_service.py"; var serviceImportPath = serviceFilePath .Replace('/', '.') .Replace('\\', '.') .Replace(".py", string.Empty) .DeduplicateDots() .Trim(); var routersFolderPath = $"{Features.Api.FeatureName}/routers/{path}"; var routerFilePath = $"{routersFolderPath}/{entitySnakeCaseName}.py"; var routerImportPath = routersFolderPath .Replace('/', '.') .Replace('\\', '.') .Replace(".py", "") .DeduplicateDots() .Trim(); var requestsFolderPath = $"{Features.Api.FeatureName}/requests/{path}"; var createRequestImportPath = $"{requestsFolderPath}/Create{entityClassName}Request" .Replace('/', '.') .Replace('\\', '.') .DeduplicateDots() .Underscore() .ToLower(); var createRequestClassName = $"Create{entityClassName}Request"; var updateRequestImportPath = $"{requestsFolderPath}/Update{entityClassName}Request" .Replace('/', '.') .Replace('\\', '.') .DeduplicateDots() .Underscore() .ToLower(); var updateRequestClassName = $"Update{entityClassName}Request"; var router = string.Join("\n", Template) .Replace("%service_import_path%", serviceImportPath) .Replace("%entity_class_name%", entityClassName) .Replace("%service_class_name%", serviceClassName) .Replace("%create_entity_request_import_path%", createRequestImportPath) .Replace("%create_entity_request_class_name%", createRequestClassName) .Replace("%update_entity_request_import_path%", updateRequestImportPath) .Replace("%update_entity_request_class_name%", updateRequestClassName); await _context.CreateFile(routerFilePath, router); var main = await _context.ReadFile("main.py"); main = new MainModifier(main).Initialize() .Import(from: routerImportPath, import: entitySnakeCaseName) .IncludeRouter(prefix: entityRoutePrefix, router: entitySnakeCaseName) .Rewrite(); await _context.WriteFile("main.py", main); } }