From c4fd9427ed2f02f18771b3499544b58e11bea3a9 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Sun, 14 Sep 2025 15:57:08 -0700 Subject: [PATCH] feat: integrate configuration system into FastAPI application - Use configurable app title, description, and API settings - Add template context injection for all HTML routes - Implement file upload validation with size/extension limits - Add enhanced health check and info endpoints with feature flags - Support conditional API documentation based on settings --- backend/app/main.py | 76 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index 5ecc4b4..189c819 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -16,22 +16,23 @@ from fastapi.staticfiles import StaticFiles # type: ignore from fastapi.templating import Jinja2Templates # type: ignore from pydantic import BaseModel +from .core.config import settings from .core.templates import UnitTemplate, template_registry from .core.unit_file import SystemdUnitFile, UnitType, ValidationError, create_unit_file # Create FastAPI app app = FastAPI( - title="UnitForge", - description="Create, validate, and manage systemd unit files", - version="1.0.0", - docs_url="/api/docs", - redoc_url="/api/redoc", + title=settings.api_title, + description=settings.api_description, + version=settings.api_version, + docs_url=settings.api_docs_url if settings.api_docs_enabled else None, + redoc_url=settings.api_redoc_url if settings.redoc_enabled else None, ) # Add CORS middleware app.add_middleware( CORSMiddleware, - allow_origins=["*"], + allow_origins=settings.cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -39,8 +40,8 @@ app.add_middleware( # Setup templates and static files BASE_DIR = Path(__file__).resolve().parent.parent.parent -TEMPLATES_DIR = BASE_DIR / "frontend" / "templates" -STATIC_DIR = BASE_DIR / "frontend" / "static" +TEMPLATES_DIR = BASE_DIR / settings.templates_dir +STATIC_DIR = BASE_DIR / settings.static_dir templates = Jinja2Templates(directory=str(TEMPLATES_DIR)) @@ -134,19 +135,25 @@ def template_to_dict(template: UnitTemplate) -> Dict[str, Any]: @app.get("/", response_class=HTMLResponse) async def index(request: Request): """Serve the main web interface.""" - return templates.TemplateResponse("index.html", {"request": request}) + context = {"request": request} + context.update(settings.get_template_context()) + return templates.TemplateResponse("index.html", context) @app.get("/editor", response_class=HTMLResponse) async def editor(request: Request): """Serve the unit file editor interface.""" - return templates.TemplateResponse("editor.html", {"request": request}) + context = {"request": request} + context.update(settings.get_template_context()) + return templates.TemplateResponse("editor.html", context) @app.get("/templates", response_class=HTMLResponse) async def templates_page(request: Request): """Serve the templates browser interface.""" - return templates.TemplateResponse("templates.html", {"request": request}) + context = {"request": request} + context.update(settings.get_template_context()) + return templates.TemplateResponse("templates.html", context) # API Routes @@ -343,6 +350,22 @@ async def download_unit_file(unit_file: UnitFileContent): async def upload_unit_file(file: UploadFile = File(...)): """Upload and validate a unit file.""" try: + # Check file size + if file.size and file.size > settings.max_upload_size: + raise HTTPException( + status_code=413, + detail=f"File too large. Maximum size is {settings.max_upload_size} bytes" + ) + + # Check file extension + if file.filename: + file_ext = Path(file.filename).suffix.lower() + if file_ext not in settings.allowed_extensions: + raise HTTPException( + status_code=400, + detail=f"File type not allowed. Allowed types: {', '.join(settings.allowed_extensions)}" + ) + content = await file.read() content_str = content.decode("utf-8") @@ -386,11 +409,23 @@ async def upload_unit_file(file: UploadFile = File(...)): async def get_info(): """Get application information.""" return { - "name": "UnitForge", - "version": "1.0.0", - "description": "Create, validate, and manage systemd unit files", + "name": settings.app_name, + "version": settings.app_version, + "description": settings.app_description, + "environment": settings.environment, + "debug": settings.debug, "supported_types": [t.value for t in UnitType], "template_count": len(template_registry.list_templates()), + "max_upload_size": settings.max_upload_size, + "allowed_extensions": settings.allowed_extensions, + "features": { + "api_metrics": settings.enable_api_metrics, + "template_caching": settings.enable_template_caching, + "validation_caching": settings.enable_validation_caching, + "api_docs": settings.api_docs_enabled, + "swagger_ui": settings.swagger_ui_enabled, + "redoc": settings.redoc_enabled, + } } @@ -398,10 +433,19 @@ async def get_info(): @app.get("/health") async def health_check(): """Health check endpoint.""" - return {"status": "healthy", "service": "unitforge"} + if not settings.health_check_enabled: + raise HTTPException(status_code=404, detail="Health check disabled") + + return { + "status": "healthy", + "service": settings.app_name.lower(), + "version": settings.app_version, + "environment": settings.environment, + "timestamp": __import__("datetime").datetime.utcnow().isoformat() + } if __name__ == "__main__": import uvicorn # type: ignore - uvicorn.run(app, host="0.0.0.0", port=8000) # nosec B104 + uvicorn.run(app, host=settings.host, port=settings.port) # nosec B104