from pathlib import Path from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.responses import Response from starlette.staticfiles import StaticFiles from starlette.types import ASGIApp, Receive, Scope, Send from app.core.config import get_settings ADMIN_DIST_DIR = Path(__file__).parent / "admin_dist" # Præfikser der altid rammer det almindelige API, uanset domæne — skal # holdes i sync med routerne registreret i app/main.py _API_PREFIXES = ( "/auth", "/participants", "/purchase-rounds", "/wine-offerings", "/wine-categories", "/mail-templates", "/mail-logs", "/orders", "/routes", "/webhooks", "/public", "/health", "/docs", "/redoc", "/openapi.json", ) class SPAStaticFiles(StaticFiles): """Standard SPA-fallback: ukendte stier (client-side routing) falder tilbage til index.html i stedet for 404.""" async def get_response(self, path: str, scope: Scope) -> Response: try: return await super().get_response(path, scope) except StarletteHTTPException as exc: if exc.status_code == 404: return await super().get_response("index.html", scope) raise class AdminDomainDispatch: """Sender requests mod admin-domænet til den byggede React-SPA i stedet for den domæne-opløste offentlige side (public_site.py) — men lader API-kald (/auth, /participants, ...) passere uændret, så admin-UI'et kan kalde API'et uden CORS (samme origin).""" def __init__(self, app: ASGIApp) -> None: self.app = app self.admin_domain = get_settings().admin_domain self.spa_app = SPAStaticFiles(directory=str(ADMIN_DIST_DIR), html=True) if ADMIN_DIST_DIR.is_dir() else None async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] != "http" or self.spa_app is None: await self.app(scope, receive, send) return headers = dict(scope["headers"]) host = headers.get(b"host", b"").decode().split(":")[0] if host == self.admin_domain and not scope["path"].startswith(_API_PREFIXES): await self.spa_app(scope, receive, send) return await self.app(scope, receive, send)