|
|
"""
|
|
|
FastAPI application entry point.
|
|
|
Main application initialization with middleware, routers, and configuration.
|
|
|
"""
|
|
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
from typing import AsyncGenerator, List, Dict, Any
|
|
|
|
|
|
from fastapi import FastAPI, Request, Response
|
|
|
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
|
|
from fastapi.responses import JSONResponse
|
|
|
|
|
|
from .core.config import get_settings
|
|
|
from .core.logger import get_logger
|
|
|
from .core.redis import redis_manager
|
|
|
from .core.auth import clerk_manager
|
|
|
from .core.openapi import OpenAPIConfig, setup_openapi_documentation
|
|
|
from .middleware import (
|
|
|
setup_cors_middleware,
|
|
|
setup_logging_middleware,
|
|
|
setup_compression_middleware,
|
|
|
setup_security_middleware,
|
|
|
setup_performance_middleware,
|
|
|
setup_async_middleware,
|
|
|
ClerkAuthMiddleware,
|
|
|
)
|
|
|
|
|
|
|
|
|
settings = get_settings()
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
|
|
|
@asynccontextmanager
|
|
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
|
"""
|
|
|
Application lifespan manager.
|
|
|
Handles startup and shutdown events.
|
|
|
"""
|
|
|
|
|
|
logger.info(
|
|
|
"Starting FastAPI Video Backend",
|
|
|
app_name=settings.app_name,
|
|
|
version=settings.app_version,
|
|
|
environment=settings.environment,
|
|
|
debug=settings.debug,
|
|
|
)
|
|
|
|
|
|
|
|
|
try:
|
|
|
await redis_manager.initialize()
|
|
|
logger.info("Redis connection initialized successfully")
|
|
|
except Exception as e:
|
|
|
logger.error(f"Failed to initialize Redis: {e}")
|
|
|
|
|
|
if settings.is_production:
|
|
|
raise
|
|
|
|
|
|
|
|
|
try:
|
|
|
clerk_manager.initialize()
|
|
|
logger.info("Clerk authentication initialized successfully")
|
|
|
except Exception as e:
|
|
|
logger.error(f"Failed to initialize Clerk: {e}")
|
|
|
|
|
|
if settings.is_production:
|
|
|
raise
|
|
|
|
|
|
logger.info("Application startup completed")
|
|
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
logger.info("Shutting down application")
|
|
|
|
|
|
|
|
|
try:
|
|
|
await redis_manager.close()
|
|
|
logger.info("Redis connections closed")
|
|
|
except Exception as e:
|
|
|
logger.error(f"Error closing Redis connections: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("Application shutdown completed")
|
|
|
|
|
|
|
|
|
def create_application() -> FastAPI:
|
|
|
"""
|
|
|
Create and configure FastAPI application.
|
|
|
|
|
|
Returns:
|
|
|
FastAPI: Configured application instance
|
|
|
"""
|
|
|
|
|
|
app = FastAPI(
|
|
|
title=settings.app_name,
|
|
|
version=settings.app_version,
|
|
|
description=OpenAPIConfig.get_api_description(),
|
|
|
summary="FastAPI backend for multi-agent video generation system",
|
|
|
terms_of_service="https://example.com/terms/",
|
|
|
contact={
|
|
|
"name": "API Support",
|
|
|
"url": "https://example.com/contact/",
|
|
|
"email": "support@example.com",
|
|
|
},
|
|
|
license_info={
|
|
|
"name": "MIT",
|
|
|
"url": "https://opensource.org/licenses/MIT",
|
|
|
},
|
|
|
docs_url=settings.docs_url if not settings.is_production else None,
|
|
|
redoc_url=settings.redoc_url if not settings.is_production else None,
|
|
|
openapi_url=settings.openapi_url if not settings.is_production else None,
|
|
|
openapi_tags=OpenAPIConfig.get_openapi_tags(),
|
|
|
servers=OpenAPIConfig.get_api_servers(),
|
|
|
lifespan=lifespan,
|
|
|
|
|
|
swagger_ui_parameters={
|
|
|
"syntaxHighlight.activate": True,
|
|
|
"syntaxHighlight.theme": "agate",
|
|
|
"syntaxHighlight.maxLines": 100,
|
|
|
"syntaxHighlight.wrapLines": True,
|
|
|
"displayRequestDuration": True,
|
|
|
"docExpansion": "list",
|
|
|
"deepLinking": True,
|
|
|
"displayOperationId": False,
|
|
|
"defaultModelsExpandDepth": 2,
|
|
|
"defaultModelExpandDepth": 2,
|
|
|
"defaultModelRendering": "example",
|
|
|
"showExtensions": True,
|
|
|
"showCommonExtensions": True,
|
|
|
"tryItOutEnabled": True,
|
|
|
},
|
|
|
)
|
|
|
|
|
|
|
|
|
setup_middleware(app)
|
|
|
|
|
|
|
|
|
setup_routers(app)
|
|
|
|
|
|
|
|
|
setup_exception_handlers(app)
|
|
|
|
|
|
return app
|
|
|
|
|
|
|
|
|
def setup_middleware(app: FastAPI) -> None:
|
|
|
"""
|
|
|
Configure application middleware.
|
|
|
|
|
|
Args:
|
|
|
app: FastAPI application instance
|
|
|
"""
|
|
|
|
|
|
if settings.is_production:
|
|
|
app.add_middleware(
|
|
|
TrustedHostMiddleware,
|
|
|
allowed_hosts=["*"]
|
|
|
)
|
|
|
|
|
|
|
|
|
setup_security_middleware(app)
|
|
|
|
|
|
|
|
|
setup_cors_middleware(app)
|
|
|
|
|
|
|
|
|
setup_compression_middleware(app)
|
|
|
|
|
|
|
|
|
setup_performance_middleware(app)
|
|
|
setup_async_middleware(app)
|
|
|
|
|
|
|
|
|
setup_logging_middleware(app)
|
|
|
|
|
|
|
|
|
app.add_middleware(
|
|
|
ClerkAuthMiddleware,
|
|
|
exclude_paths=[
|
|
|
"/",
|
|
|
"/health",
|
|
|
"/docs",
|
|
|
"/redoc",
|
|
|
"/openapi.json",
|
|
|
"/favicon.ico",
|
|
|
"/.well-known/appspecific/com.chrome.devtools.json",
|
|
|
"/api/docs",
|
|
|
"/api/v1/system/health",
|
|
|
"/api/v1/auth/health",
|
|
|
"/api/v1/auth/status"
|
|
|
]
|
|
|
)
|
|
|
|
|
|
|
|
|
def setup_routers(app: FastAPI) -> None:
|
|
|
"""
|
|
|
Configure application routers.
|
|
|
|
|
|
Args:
|
|
|
app: FastAPI application instance
|
|
|
"""
|
|
|
|
|
|
@app.get("/health")
|
|
|
async def health_check():
|
|
|
"""Basic health check endpoint."""
|
|
|
return {
|
|
|
"status": "healthy",
|
|
|
"app_name": settings.app_name,
|
|
|
"version": settings.app_version,
|
|
|
"environment": settings.environment,
|
|
|
}
|
|
|
|
|
|
|
|
|
@app.get("/")
|
|
|
async def root():
|
|
|
"""Root endpoint with basic information."""
|
|
|
return {
|
|
|
"message": f"Welcome to {settings.app_name}",
|
|
|
"version": settings.app_version,
|
|
|
"docs_url": settings.docs_url,
|
|
|
"health_url": "/health",
|
|
|
}
|
|
|
|
|
|
|
|
|
@app.get("/test-swagger", tags=["testing"])
|
|
|
async def test_swagger_ui():
|
|
|
"""Test endpoint to verify Swagger UI functionality."""
|
|
|
return {
|
|
|
"message": "Swagger UI is working correctly!",
|
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
|
"features": [
|
|
|
"Interactive API documentation",
|
|
|
"Request/response examples",
|
|
|
"Authentication testing",
|
|
|
"Schema validation"
|
|
|
]
|
|
|
}
|
|
|
|
|
|
|
|
|
from .api.v1 import auth, videos, jobs, system, files
|
|
|
app.include_router(auth.router, prefix=settings.api_v1_prefix)
|
|
|
app.include_router(videos.router, prefix=settings.api_v1_prefix, tags=["videos"])
|
|
|
app.include_router(jobs.router, prefix=settings.api_v1_prefix, tags=["jobs"])
|
|
|
app.include_router(system.router, prefix=settings.api_v1_prefix, tags=["system"])
|
|
|
app.include_router(files.router, prefix=settings.api_v1_prefix, tags=["files"])
|
|
|
|
|
|
|
|
|
def setup_exception_handlers(app: FastAPI) -> None:
|
|
|
"""
|
|
|
Configure global exception handlers.
|
|
|
|
|
|
Args:
|
|
|
app: FastAPI application instance
|
|
|
"""
|
|
|
@app.exception_handler(Exception)
|
|
|
async def global_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
|
"""Handle unexpected exceptions."""
|
|
|
logger.error(
|
|
|
"Unhandled exception",
|
|
|
error_type=type(exc).__name__,
|
|
|
error_message=str(exc),
|
|
|
url=str(request.url),
|
|
|
method=request.method,
|
|
|
exc_info=True,
|
|
|
)
|
|
|
|
|
|
|
|
|
if settings.is_production:
|
|
|
return JSONResponse(
|
|
|
status_code=500,
|
|
|
content={
|
|
|
"error": {
|
|
|
"message": "Internal server error",
|
|
|
"error_code": "INTERNAL_ERROR",
|
|
|
}
|
|
|
}
|
|
|
)
|
|
|
else:
|
|
|
return JSONResponse(
|
|
|
status_code=500,
|
|
|
content={
|
|
|
"error": {
|
|
|
"message": str(exc),
|
|
|
"error_code": "INTERNAL_ERROR",
|
|
|
"type": type(exc).__name__,
|
|
|
}
|
|
|
}
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
app = create_application()
|
|
|
|
|
|
|
|
|
setup_openapi_documentation(app)
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
import uvicorn
|
|
|
|
|
|
logger.info(
|
|
|
"Starting development server",
|
|
|
host=settings.host,
|
|
|
port=settings.port,
|
|
|
reload=settings.reload,
|
|
|
)
|
|
|
|
|
|
uvicorn.run(
|
|
|
"src.app.main:app",
|
|
|
host=settings.host,
|
|
|
port=settings.port,
|
|
|
reload=settings.reload,
|
|
|
log_level=settings.log_level.lower(),
|
|
|
) |