"""Klient HTTP do usługi render (PRE-24). Jedyny punkt styku prezentacji z usługą składającą PDF. Idzie po szyfrowanym łączu, tak jak reszta ruchu wewnątrz klastra — z WŁASNYM, trzecim kluczem (`LINK_KEY_PRESENTATION_RENDER`). Osobny klucz, bo przez to łącze leci CAŁY raport: dane urodzeniowe i opisy z baz. Przejęcie go nie może otwierać łącza do logiki ani do danych. """ from __future__ import annotations import os import httpx from app import link_crypto from app.config import settings def _auth_headers() -> dict[str, str]: """Token międzywarstwowy (LOG-32) — pusty, gdy ochrona wyłączona.""" token = os.getenv("INTERNAL_TOKEN", "") return {"X-Astrololo-Token": token} if token else {} def _link() -> link_crypto.Link | None: """Klucz łącza prezentacja↔render. Czytany przy każdym wywołaniu, bo konfiguracja może się zmienić bez restartu procesu.""" key = link_crypto.key_from_env(link_crypto.ENV_PRESENTATION_RENDER) return link_crypto.Link(key) if key else None class RenderClient: def __init__(self, base_url: str | None = None) -> None: self.base_url = (base_url or settings.render_url).rstrip("/") def pdf(self, report: dict) -> bytes: """Raport → bajty PDF. Timeout długi: XeLaTeX na obszernym tekście potrzebuje chwili, a raport bywa na kilkadziesiąt stron.""" with httpx.Client(timeout=max(settings.http_timeout, 180.0)) as client: return link_crypto.call(client, "POST", f"{self.base_url}/pdf", payload=report, headers=_auth_headers(), link=_link()) def health(self) -> dict: """/health celowo poza szyfrowaniem — pukają tu sondy k8s, które klucza nie mają, a nie przechodzi tędy nic z raportu.""" import json with httpx.Client(timeout=settings.http_timeout) as client: r = client.get(f"{self.base_url}/health", headers=_auth_headers()) r.raise_for_status() return json.loads(r.content)