Spaces:
Sleeping
Sleeping
| import logging | |
| from contextlib import asynccontextmanager | |
| from typing import AsyncGenerator | |
| from fastapi import FastAPI, Request | |
| from fastapi.responses import JSONResponse | |
| from app.config import settings | |
| from app.database import init_db | |
| from app.lib.graph.auth import GraphAuthProvider | |
| from app.lib.graph.client import GraphClient | |
| from app.routes.health import router as health_router | |
| from app.routes.webhook import router as webhook_router | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s %(levelname)s %(name)s: %(message)s", | |
| ) | |
| logger = logging.getLogger(__name__) | |
| async def lifespan(app: FastAPI) -> AsyncGenerator: | |
| """Minimal lifespan — sets up the DB and Graph client only. | |
| Subscription bootstrap and APScheduler jobs live in the standalone | |
| `app.scheduler_main` process so that the FastAPI server can run with | |
| multiple uvicorn workers without duplicating cron work or racing on | |
| subscription creation. The webhook handler reads the active | |
| subscription's clientState from the DB on each request — it does not | |
| need a live subscription_id on app.state. | |
| """ | |
| # --- Startup --- | |
| await init_db(settings.database_path) | |
| auth = GraphAuthProvider(settings) | |
| client = GraphClient(auth) | |
| app.state.client = client | |
| app.state.settings = settings | |
| logger.info("FastAPI ready — webhook receiver online") | |
| yield | |
| # --- Shutdown --- | |
| await client.aclose() | |
| logger.info("Shutdown complete") | |
| app = FastAPI(title="RCMEmail", version="1.0.0", lifespan=lifespan) | |
| app.include_router(health_router) | |
| app.include_router(webhook_router) | |
| async def _global_exception_handler(request: Request, exc: Exception) -> JSONResponse: | |
| logger.exception("Unhandled exception for %s %s", request.method, request.url.path) | |
| return JSONResponse(status_code=500, content={"detail": "Internal server error"}) | |