""" Main FastAPI application for DeployMate. """ from contextlib import asynccontextmanager from fastapi import Depends, FastAPI, HTTPException, Request from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, JSONResponse from slowapi import _rate_limit_exceeded_handler from slowapi.errors import RateLimitExceeded from app.api.deps import get_logger, get_settings from app.api.routes.nginx import router as nginx_router from app.api.routes.scripts import router as scripts_router from app.api.routes.docker import router as docker_router from app.core.config import settings from app.core.logging import logger from app.core.security import limiter, rate_limit_middleware @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan context manager.""" logger.info("Starting DeployMate API") yield logger.info("Shutting down DeployMate API") # Create FastAPI application app = FastAPI( title="DeployMate API", version="1.0.0", description="API for generating VPS setup scripts and nginx configurations", lifespan=lifespan, ) # Rate limiting app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) app.add_middleware(rate_limit_middleware) # CORS middleware app.add_middleware( CORSMiddleware, allow_origins=settings.allowed_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Exception handlers @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): """Handle Pydantic validation errors.""" client_ip = request.client.host if request.client else "unknown" errors = [] for error in exc.errors(): errors.append( { "type": error.get("type"), "loc": error.get("loc"), "msg": error.get("msg"), "input": error.get("input"), } ) logger.error(f"Validation error from {client_ip}: {errors}") return JSONResponse( status_code=422, content={"detail": errors}, ) # Root endpoint @app.get("/") async def root(): """Root endpoint returning API information.""" return { "message": "DeployMate API", "version": "1.0.0", "docs": "/docs", "redoc": "/redoc", } # File download endpoint @app.get("/download/{filename}") async def download_file( filename: str, request: Request, settings=Depends(get_settings), logger=Depends(get_logger) ): """Download a generated file.""" client_ip = request.client.host if request.client else "unknown" logger.info(f"File download request from {client_ip}: {filename}") file_path = settings.generated_dir / filename if not file_path.exists(): logger.warning(f"File not found: {filename}") raise HTTPException(status_code=404, detail="File not found") logger.info(f"Serving file: {filename}") return FileResponse( path=file_path, filename=filename, media_type="application/octet-stream" ) # Include API routers app.include_router(scripts_router, prefix="/api/v1", tags=["scripts"]) app.include_router(nginx_router, prefix="/api/v1", tags=["nginx"]) app.include_router(docker_router, prefix="/api/v1", tags=["docker"])