""" FastAPI webserver on Modal for serving minigames with subpath routing. Each game lives at a subpath: vibe-sites.suno.fm//* Routes to: .suno.run/* """ import httpx import modal from fastapi import FastAPI, Request, Response # Create Modal app app = modal.App("hackathon-server-martin-dev") # Create Modal image with required dependencies image = modal.Image.debian_slim().pip_install( "fastapi[standard]", "httpx", ) # Create FastAPI app web_app = FastAPI() @web_app.get("/health") async def health(): """Health check endpoint""" return {"status": "ok"} async def proxy_request( request: Request, target_host: str, path: str, ) -> Response: """ Proxy a request to the target host and modify headers to make it iframeable. """ # Build target URL target_url = f"https://{target_host}{path}" if request.url.query: target_url = f"{target_url}?{request.url.query}" # Prepare headers (remove host header) headers = dict(request.headers) headers.pop("host", None) async with httpx.AsyncClient(follow_redirects=True, timeout=30.0) as client: try: # Forward the request response = await client.request( method=request.method, url=target_url, headers=headers, content=await request.body() if request.method in ["POST", "PUT", "PATCH"] else None, ) # Modify response headers to make content iframeable response_headers = dict(response.headers) # Remove headers that prevent iframing response_headers.pop("x-frame-options", None) response_headers.pop("content-security-policy", None) # Add headers to allow iframing response_headers["x-frame-options"] = "ALLOWALL" response_headers["access-control-allow-origin"] = "*" # Return the response return Response( content=response.content, status_code=response.status_code, headers=response_headers, ) except httpx.RequestError as e: return Response( content=f"Error proxying request: {str(e)}", status_code=502, ) @web_app.api_route( "/{game_id}/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] ) async def proxy_game_subpath(request: Request, game_id: str, path: str = ""): """ Proxy requests based on subpath routing. Format: vibe-sites.suno.fm//* -> .suno.run/* Example: vibe-sites.suno.fm/ttunes/index.html -> ttunes.suno.run/index.html """ # Map game_id to target host # game_id is the first path segment, maps to .suno.run target_host = f"{game_id}.suno.run" # Construct the target path target_path = f"/{path}" if path else "/" # Proxy the request return await proxy_request(request, target_host, target_path) @web_app.api_route( "/{game_id}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] ) async def proxy_game_root(request: Request, game_id: str): """ Proxy requests to game root (without trailing slash in path). Format: vibe-sites.suno.fm/ -> .suno.run/ """ target_host = f"{game_id}.suno.run" return await proxy_request(request, target_host, "/") @web_app.get("/") async def root(): """Root endpoint - shows available routes""" return { "message": "Vibe Sites Proxy Server", "usage": "Access games at: vibe-sites.suno.fm//*", "example": "vibe-sites.suno.fm/ttunes/ -> ttunes.suno.run/", } @app.function(image=image) @modal.asgi_app( custom_domains=["vibe-sites.suno.fm"] ) def fastapi_app(): """Modal function that serves the FastAPI app with subpath routing""" return web_app