82 lines
3.1 KiB
Python
82 lines
3.1 KiB
Python
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",
|
|
)
|