Squash merge feature/library-reorganization
This commit is contained in:
81
backend/app/routes/teamsnap.py
Normal file
81
backend/app/routes/teamsnap.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
import json
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
|
||||
|
||||
from ..auth import require_session
|
||||
from ..models import UserSession
|
||||
|
||||
router = APIRouter(prefix="/teamsnap", tags=["teamsnap"])
|
||||
|
||||
|
||||
def build_proxy_api_root(request: Request) -> str:
|
||||
scheme = request.headers.get("x-forwarded-proto", request.url.scheme)
|
||||
host = request.headers.get("x-forwarded-host", request.headers.get("host", request.url.netloc))
|
||||
return f"{scheme}://{host}/api/teamsnap"
|
||||
|
||||
|
||||
def rewrite_teamsnap_urls(value: object, upstream_root: str, proxy_root: str) -> object:
|
||||
if isinstance(value, str):
|
||||
return value.replace(upstream_root, proxy_root)
|
||||
if isinstance(value, Mapping):
|
||||
return {key: rewrite_teamsnap_urls(item, upstream_root, proxy_root) for key, item in value.items()}
|
||||
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
|
||||
return [rewrite_teamsnap_urls(item, upstream_root, proxy_root) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
def build_upstream_url(request: Request, proxy_path: str) -> str:
|
||||
base = request.app.state.settings.teamsnap_api_root.rstrip("/")
|
||||
if not proxy_path:
|
||||
return base
|
||||
return f"{base}/{proxy_path.lstrip('/')}"
|
||||
|
||||
|
||||
@router.api_route("/{proxy_path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
|
||||
@router.api_route("", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
|
||||
async def teamsnap_proxy(
|
||||
request: Request,
|
||||
session: UserSession = Depends(require_session),
|
||||
proxy_path: str = "",
|
||||
) -> Response:
|
||||
if session.provider != "teamsnap":
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Session is not TeamSnap-backed")
|
||||
if not session.access_token:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing TeamSnap access token")
|
||||
|
||||
upstream_url = build_upstream_url(request, proxy_path)
|
||||
outgoing_headers = {
|
||||
"Accept": request.headers.get("accept", "application/vnd.collection+json"),
|
||||
"Authorization": f"Bearer {session.access_token}",
|
||||
}
|
||||
if request.headers.get("content-type"):
|
||||
outgoing_headers["Content-Type"] = request.headers["content-type"]
|
||||
|
||||
body = await request.body()
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
upstream = await client.request(
|
||||
request.method,
|
||||
upstream_url,
|
||||
params=request.query_params,
|
||||
content=body or None,
|
||||
headers=outgoing_headers,
|
||||
)
|
||||
|
||||
content_type = upstream.headers.get("content-type", "")
|
||||
if "json" not in content_type.lower():
|
||||
response = Response(content=upstream.content, status_code=upstream.status_code)
|
||||
if content_type:
|
||||
response.headers["Content-Type"] = content_type
|
||||
return response
|
||||
|
||||
proxy_root = build_proxy_api_root(request)
|
||||
rewritten = rewrite_teamsnap_urls(upstream.json(), request.app.state.settings.teamsnap_api_root, proxy_root)
|
||||
return Response(
|
||||
content=json.dumps(rewritten),
|
||||
status_code=upstream.status_code,
|
||||
media_type=content_type or "application/json",
|
||||
)
|
||||
Reference in New Issue
Block a user