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
This commit is contained in:
William Valentin
2025-09-14 15:57:08 -07:00
parent 4f94583b54
commit c4fd9427ed

View File

@@ -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