import logging from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.sessions import SessionMiddleware import sentry_sdk from sentry_sdk.integrations.fastapi import FastApiIntegration import os import sys from app.api.api_router import api_router from app.config import settings from app.core.api_config import API_METADATA, API_TAGS from app.auth import fastapi_users, auth_backend from app.schemas.user import UserPublic, UserCreate, UserUpdate from app.core.scheduler import init_scheduler, shutdown_scheduler if settings.SENTRY_DSN: sentry_sdk.init( dsn=settings.SENTRY_DSN, integrations=[ FastApiIntegration(), ], traces_sample_rate=0.1 if settings.is_production else 1.0, environment=settings.ENVIRONMENT, send_default_pii=not settings.is_production ) logging.basicConfig( level=getattr(logging, settings.LOG_LEVEL), format=settings.LOG_FORMAT ) logger = logging.getLogger(__name__) api_metadata = { **API_METADATA, "docs_url": settings.docs_url, "redoc_url": settings.redoc_url, "openapi_url": settings.openapi_url, } app = FastAPI( **api_metadata, openapi_tags=API_TAGS ) app.add_middleware( SessionMiddleware, secret_key=settings.SESSION_SECRET_KEY ) app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], expose_headers=["*"] ) app.include_router( fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"], ) app.include_router( fastapi_users.get_register_router(UserPublic, UserCreate), prefix="/auth", tags=["auth"], ) app.include_router( fastapi_users.get_reset_password_router(), prefix="/auth", tags=["auth"], ) app.include_router( fastapi_users.get_verify_router(UserPublic), prefix="/auth", tags=["auth"], ) app.include_router( fastapi_users.get_users_router(UserPublic, UserUpdate), prefix="/users", tags=["users"], ) app.include_router(api_router, prefix=settings.API_PREFIX) @app.get("/health", tags=["Health"]) async def health_check(): """ Health check endpoint for load balancers and monitoring. """ return { "status": settings.HEALTH_STATUS_OK, "environment": settings.ENVIRONMENT, "version": settings.API_VERSION } @app.get("/", tags=["Root"]) async def read_root(): """ Provides a simple welcome message at the root path. Useful for basic reachability checks. """ logger.info("Root endpoint '/' accessed.") return { "message": settings.ROOT_MESSAGE, "environment": settings.ENVIRONMENT, "version": settings.API_VERSION } async def run_migrations(): """Run database migrations.""" try: logger.info("Running database migrations...") base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) alembic_path = os.path.join(base_path, 'alembic') if alembic_path not in sys.path: sys.path.insert(0, alembic_path) from migrations import run_migrations as run_db_migrations await run_db_migrations() logger.info("Database migrations completed successfully.") except Exception as e: logger.error(f"Error running migrations: {e}") raise @app.on_event("startup") async def startup_event(): """Initialize services on startup.""" logger.info(f"Application startup in {settings.ENVIRONMENT} environment...") # await run_migrations() init_scheduler() logger.info("Application startup complete.") @app.on_event("shutdown") async def shutdown_event(): """Cleanup services on shutdown.""" logger.info("Application shutdown: Disconnecting from database...") shutdown_scheduler() logger.info("Application shutdown complete.")