Spaces:
Running
Running
| """ | |
| 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 | |
| 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 | |
| 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 | |
| async def root(): | |
| """Root endpoint returning API information.""" | |
| return { | |
| "message": "DeployMate API", | |
| "version": "1.0.0", | |
| "docs": "/docs", | |
| "redoc": "/redoc", | |
| } | |
| # File download endpoint | |
| 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"]) | |