from contextlib import asynccontextmanager from fastapi import FastAPI, Depends, Request from fastapi.middleware.cors import CORSMiddleware from fastapi_swagger import patch_fastapi from domains.auth.api import router as auth_router from domains.users.api import router as users_router from domains.admin.api import router as admin_router from domains.groups.api.admin import router as groups_admin_router from domains.groups.api.client import router as groups_client_router from domains.realtime.ws import router as realtime_router from domains.notifications.api import router as notifications_router from integrations.livekit.client import close_livekit_api from db.redis import redis_client from core.rate_limit import RateLimiter @asynccontextmanager async def lifespan(app: FastAPI): # ---------- Startup ---------- try: await redis_client.ping() # type: ignore print("Redis connected") async for key in redis_client.scan_iter("speaker:*"): await redis_client.delete(key) async for key in redis_client.scan_iter("presence:*"): await redis_client.delete(key) except Exception as e: print("Redis connection failed:", e) yield # ---------- Shutdown ---------- await close_livekit_api() await redis_client.close() global_limiter = RateLimiter(requests=30, window_seconds=60, scope="global") app = FastAPI( title="NEDA API", description="Realtime Voice Communication Backend", version="1.0.0", lifespan=lifespan, docs_url=None, swagger_ui_oauth2_redirect_url=None, dependencies=[Depends(global_limiter)] ) patch_fastapi(app,docs_url="/swagger") # ------------------------- # CORS # ------------------------- app.add_middleware( CORSMiddleware, allow_origins=[ "https://pathfinder.wikm.ir", "http://localhost:8000", ], allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "*"], # محدود کردن متدها allow_headers=["Authorization", "Content-Type"], # محدود کردن هدرها ) @app.middleware("http") async def add_security_headers(request: Request, call_next): response = await call_next(request) response.headers["X-Content-Type-Options"] = "nosniff" response.headers["X-Frame-Options"] = "DENY" return response # ------------------------- # Routers # ------------------------- app.include_router(auth_router) app.include_router(users_router) app.include_router(admin_router) app.include_router(groups_client_router) app.include_router(groups_admin_router) app.include_router(realtime_router) app.include_router(notifications_router)