using Humanizer; using MycroForge.CLI.Commands; using MycroForge.CLI.Extensions; using MycroForge.Core; namespace MycroForge.CLI.CodeGen; public class CrudRouterGenerator { #region Templates 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))", ]; #endregion private readonly ProjectContext _context; public CrudRouterGenerator(ProjectContext context) { _context = context; } public async Task Generate(FullyQualifiedName fqn) { var serviceClassName = $"{fqn.PascalizedName}Service"; var entityRoutePrefix = fqn.PascalizedName.Kebaberize().Pluralize().ToLower(); var serviceFilePath = Path.Join( Features.Api.FeatureName, "services", fqn.Namespace, $"{fqn.SnakeCasedName}_service" ); var serviceImportPath = serviceFilePath.SlashesToDots(); var routerFolderPath = Path.Join(Features.Api.FeatureName, "routers", fqn.Namespace); var routerFilePath = Path.Join(routerFolderPath, $"{fqn.SnakeCasedName}"); var routerImportPath = routerFolderPath.SlashesToDots(); var requestsFolderPath = Path.Join(Features.Api.FeatureName, "requests", fqn.Namespace); var createRequestImportPath = Path.Join(requestsFolderPath, $"Create{fqn.PascalizedName}Request") .SlashesToDots() .Underscore() .ToLower(); var createRequestClassName = $"Create{fqn.PascalizedName}Request"; var updateRequestImportPath = Path.Join(requestsFolderPath, $"Update{fqn.PascalizedName}Request") .SlashesToDots() .Underscore() .ToLower(); var updateRequestClassName = $"Update{fqn.PascalizedName}Request"; var router = string.Join("\n", Template) .Replace("%service_import_path%", serviceImportPath) .Replace("%entity_class_name%", fqn.PascalizedName) .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}.py", router); var main = await _context.ReadFile("main.py"); main = new MainModifier(main).Initialize() .Import(from: routerImportPath, import: fqn.SnakeCasedName) .IncludeRouter(prefix: entityRoutePrefix, router: fqn.SnakeCasedName) .Rewrite(); await _context.WriteFile("main.py", main); } }