from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from pathlib import Path from typing import Optional, Tuple from contextlib import asynccontextmanager from app.config import settings, UNIVERSES, BASE_DIR from app.routers import api, proxy from app.proxy import close_http_client, proxy_request from app.personas import ensure_dirs def get_subdomain_target(host: str) -> Optional[Tuple[str, str]]: """ Inspects Host header and returns (target_backend_url, universe_id) if it matches a subdomain. Examples: tt.hub.yesminedor.tn -> (HERMES_TT_URL, 'tt') nyora.hub.yesminedor.tn -> (HERMES_NYORA_URL, 'nyora') perso.hub.yesminedor.tn -> (HERMES_PERSO_URL, 'perso') nabil.hub.yesminedor.tn -> (HERMES_NABIL_URL, 'nabil') dsh.hub.yesminedor.tn -> (DSH_FILEBROWSER_URL, 'nabil') """ if not host: return None hostname = host.split(":")[0].lower() sub = None if hostname.endswith(".hub.yesminedor.tn"): sub = hostname.rsplit(".hub.yesminedor.tn", 1)[0] elif hostname.endswith(".localhost"): sub = hostname.rsplit(".localhost", 1)[0] if not sub or sub in ("hub", "www"): return None if sub in ("dsh", "files", "dsh-files"): return (settings.dsh_filebrowser_url, "nabil") if sub in UNIVERSES: return (UNIVERSES[sub].backend_url, sub) return None @asynccontextmanager async def lifespan(app: FastAPI): ensure_dirs() yield await close_http_client() app = FastAPI( title=settings.app_name, version="1.0.0", lifespan=lifespan ) # Subdomain Routing Middleware @app.middleware("http") async def subdomain_routing_middleware(request: Request, call_next): host = request.headers.get("host", "") target = get_subdomain_target(host) if target: backend_url, universe_id = target path = request.url.path return await proxy_request( request=request, backend_url=backend_url, path=path, universe_id=universe_id ) return await call_next(request) # Mount static files static_dir = BASE_DIR / "static" static_dir.mkdir(parents=True, exist_ok=True) app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") # Templates templates_dir = BASE_DIR / "app" / "templates" templates = Jinja2Templates(directory=str(templates_dir)) # Include Routers app.include_router(api.router) app.include_router(proxy.router) @app.get("/", response_class=HTMLResponse) @app.head("/", response_class=HTMLResponse) async def index_view(request: Request): return templates.TemplateResponse( request=request, name="index.html", context={ "universes": UNIVERSES, "app_name": settings.app_name } ) if __name__ == "__main__": import uvicorn uvicorn.run("app.main:app", host=settings.host, port=settings.port, reload=settings.debug)