import logging from contextlib import asynccontextmanager from fastapi import FastAPI from starlette.middleware.cors import CORSMiddleware from tenacity import retry, stop_after_attempt, wait_fixed, before_log, after_log from app.api.v1.api import api_router from app.core.config import settings from app.core.database import Base, engine from app.core.cache import redis_client from app.core.logging_config import setup_logging from app.core.scheduler import scheduler from app.services.backup_service import BackupService from app.core.init_data import fix_user_names from app.core.database import SessionLocal # Configure Logging logger = setup_logging() # Retry Logic Configuration max_tries = 60 * 5 # 5 minutes wait_seconds = 1 @retry( stop=stop_after_attempt(max_tries), wait=wait_fixed(wait_seconds), before=before_log(logger, logging.INFO), after=after_log(logger, logging.WARN), ) def init_db() -> None: try: # Try to create a connection to check if DB is ready with engine.connect() as conn: pass # Create tables Base.metadata.create_all(bind=engine) logger.info("Database connection established and tables created.") except Exception as e: logger.error(e) raise e @retry( stop=stop_after_attempt(max_tries), wait=wait_fixed(wait_seconds), before=before_log(logger, logging.INFO), after=after_log(logger, logging.WARN), ) def check_redis() -> None: try: redis_client.ping() logger.info("Redis connection established.") except Exception as e: logger.error(e) raise e @asynccontextmanager async def lifespan(app: FastAPI): # Startup: Wait for services logger.info("Waiting for Database and Redis...") init_db() check_redis() # Init Scheduler logger.info("Starting Scheduler...") scheduler.start() try: with SessionLocal() as db: # Check and fix user names/english_names fix_user_names(db) BackupService.init_scheduler(db) except Exception as e: logger.error(f"Failed to init scheduler or fix user names: {e}") yield # Shutdown logic if any (e.g. close connections) scheduler.shutdown() logger.info("Shutting down...") app = FastAPI( title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json", docs_url=f"{settings.API_V1_STR}/docs", redoc_url=f"{settings.API_V1_STR}/redoc", lifespan=lifespan ) # Allow all CORS for development simplicity if config is restrictive origins = [ "http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:8000", "http://127.0.0.1:8000" ] # Set all CORS enabled origins if settings.BACKEND_CORS_ORIGINS: for origin in settings.BACKEND_CORS_ORIGINS: origins.append(str(origin)) app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], expose_headers=["X-New-Token"], ) app.include_router(api_router, prefix=settings.API_V1_STR) @app.get("/") def root(): return {"message": "Welcome to Unified Authentication Platform API"}