Total URL rebuilds
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Ekaropolus 2025-09-19 00:12:10 -06:00
parent d57548f273
commit 276647e079

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import json import json
import re import re
from typing import Any, Dict, Tuple from typing import Any, Dict, Tuple
from urllib.parse import urlparse
import requests import requests
from django.conf import settings from django.conf import settings
@ -11,6 +12,9 @@ from django.http import JsonResponse, HttpRequest
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods, require_POST from django.views.decorators.http import require_http_methods, require_POST
# build absolute public URLs from a request + path
from core.urlbuild import public_url
# ----- contracts version (best-effort) ----- # ----- contracts version (best-effort) -----
try: try:
from pxy_contracts.version import SPEC_VERSION from pxy_contracts.version import SPEC_VERSION
@ -24,7 +28,7 @@ AGENTS_INTERNAL_BASE = getattr(settings, "AGENTS_INTERNAL_BASE", "")
# For the formatter endpoints we *force* an internal base and never guess from Host. # For the formatter endpoints we *force* an internal base and never guess from Host.
# Set in .env: AGENTS_INTERNAL_BASE=http://127.0.0.1:8002 # Set in .env: AGENTS_INTERNAL_BASE=http://127.0.0.1:8002
# Fallback keeps you safe even if env is missing/misread. # Fallback keeps you safe even if env is missing/misread.
FORMAT_INTERNAL_BASE = AGENTS_INTERNAL_BASE or "http://127.0.0.1:8002" FORMAT_INTERNAL_BASE = (AGENTS_INTERNAL_BASE or "http://127.0.0.1:8002").rstrip("/")
# ===== helpers ===== # ===== helpers =====
def _load_body(request: HttpRequest) -> Dict[str, Any]: def _load_body(request: HttpRequest) -> Dict[str, Any]:
@ -59,7 +63,7 @@ def _post_underlying(agent: str, payload: Dict[str, Any], timeout: float = 60.0)
sites -> /api/sites/search sites -> /api/sites/search
""" """
path = "/api/sami/run" if agent == "sami" else "/api/sites/search" path = "/api/sami/run" if agent == "sami" else "/api/sites/search"
url = f"{FORMAT_INTERNAL_BASE.rstrip('/')}{path}" url = f"{FORMAT_INTERNAL_BASE}{path}"
try: try:
r = requests.post(url, json=payload, timeout=timeout) r = requests.post(url, json=payload, timeout=timeout)
try: try:
@ -72,6 +76,37 @@ def _post_underlying(agent: str, payload: Dict[str, Any], timeout: float = 60.0)
except Exception as e: except Exception as e:
return 500, {"code": "EXEC_ERROR", "message": str(e), "_debug_url": url} return 500, {"code": "EXEC_ERROR", "message": str(e), "_debug_url": url}
def _normalize_urls_to_public(data: dict, request: HttpRequest) -> None:
"""
Convert any absolute URLs that may point to 127.0.0.1:8002 into public URLs
using the current request host while preserving the path.
"""
if not isinstance(data, dict):
return
url_keys = {
# common keys from sami + sites
"share_url", "map_url", "demand_map_url", "competition_map_url",
"main_download_url", "demand_download_url", "competition_download_url",
"main_preview_url", "demand_preview_url", "competition_preview_url",
"isochrones_geojson_url", "candidates_geojson_url",
"pois_competition_geojson_url", "popgrid_geojson_url",
"chart_url",
}
for k in list(url_keys):
v = data.get(k)
if not isinstance(v, str) or not v:
continue
try:
p = urlparse(v)
# only rewrite absolute http(s) URLs; keep relative ones
if p.scheme in ("http", "https") and p.path:
data[k] = public_url(request, p.path)
except Exception:
# never fail formatting due to a bad URL
pass
# Tiny text builders for bot replies # Tiny text builders for bot replies
def _text_sami(data: Dict[str, Any]) -> str: def _text_sami(data: Dict[str, Any]) -> str:
if "beta" in data and "r2" in data: if "beta" in data and "r2" in data:
@ -174,6 +209,7 @@ def format_sami(request: HttpRequest):
payload, src = _extract_payload(body) payload, src = _extract_payload(body)
status, data = _post_underlying("sami", payload, timeout=60.0) status, data = _post_underlying("sami", payload, timeout=60.0)
data = data if isinstance(data, dict) else {"result": data} data = data if isinstance(data, dict) else {"result": data}
_normalize_urls_to_public(data, request) # ensure public links
data.setdefault("_echo", {"src": src, "payload_keys": list(payload.keys())}) data.setdefault("_echo", {"src": src, "payload_keys": list(payload.keys())})
try: try:
data["text"] = _text_sami(data) data["text"] = _text_sami(data)
@ -188,6 +224,7 @@ def format_sites(request: HttpRequest):
payload, src = _extract_payload(body) payload, src = _extract_payload(body)
status, data = _post_underlying("sites", payload, timeout=60.0) status, data = _post_underlying("sites", payload, timeout=60.0)
data = data if isinstance(data, dict) else {"result": data} data = data if isinstance(data, dict) else {"result": data}
_normalize_urls_to_public(data, request) # ensure public links
data.setdefault("_echo", {"src": src, "payload_keys": list(payload.keys())}) data.setdefault("_echo", {"src": src, "payload_keys": list(payload.keys())})
try: try:
data["text"] = _text_sites(data) data["text"] = _text_sites(data)