Files
Business-Management/backend/app/routers/documents_proxy.py
T
curo1305 b8238e03ea Fix prod startup: add start.sh for backend, fix documents proxy base route
- backend/Dockerfile: run migrations via start.sh before uvicorn instead
  of launching uvicorn directly (prod was skipping Alembic)
- backend/scripts/start.sh: alembic upgrade head + uvicorn exec
- documents_proxy.py: add explicit "" route so GET /api/documents (no
  trailing slash) returns 200 instead of 307 redirect
- README.md: update Containers table, volumes section, and Current State
  to reflect the new 4-container architecture with doc-service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 05:32:43 +02:00

86 lines
2.3 KiB
Python

"""
Proxy all /api/documents/* requests to doc-service:8001/documents/*.
Uses a module-level AsyncClient for connection pooling.
Strips hop-by-hop headers that must not be forwarded.
File downloads (/file endpoint) are streamed.
"""
import os
import httpx
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import StreamingResponse
from app.deps import get_current_user
from app.models.user import User
DOC_SERVICE_URL = os.environ.get("DOC_SERVICE_URL", "http://doc-service:8001")
# Module-level client — reused across requests for connection pooling
_client = httpx.AsyncClient(base_url=DOC_SERVICE_URL, timeout=120.0)
router = APIRouter()
_HOP_BY_HOP = frozenset(
[
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailers",
"transfer-encoding",
"upgrade",
"host",
]
)
def _forward_headers(request: Request, user_id: str) -> dict:
headers = {
k: v
for k, v in request.headers.items()
if k.lower() not in _HOP_BY_HOP
}
headers["x-user-id"] = user_id
return headers
@router.api_route("", methods=["GET", "POST"])
@router.api_route("/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def proxy_documents(
request: Request,
current_user: User = Depends(get_current_user),
path: str = "",
) -> StreamingResponse:
url = f"/documents/{path}" if path else "/documents"
headers = _forward_headers(request, str(current_user.id))
# For multipart uploads, stream the body directly
body = await request.body()
try:
response = await _client.request(
method=request.method,
url=url,
headers=headers,
content=body,
params=dict(request.query_params),
)
except httpx.RequestError as exc:
raise HTTPException(status_code=502, detail=f"doc-service unreachable: {exc}")
# Strip hop-by-hop from response headers
resp_headers = {
k: v
for k, v in response.headers.items()
if k.lower() not in _HOP_BY_HOP
}
return StreamingResponse(
content=iter([response.content]),
status_code=response.status_code,
headers=resp_headers,
media_type=response.headers.get("content-type"),
)