bmc_hub/app/modules/website_content/backend/service.py

100 lines
4.3 KiB
Python
Raw Normal View History

2026-08-28 20:49:55 +02:00
from __future__ import annotations
from datetime import datetime
from typing import Any
import httpx
from app.core.config import settings
class NotFoundError(RuntimeError):
pass
class WebsiteContentAPIError(RuntimeError):
pass
class WebsiteContentService:
"""Authenticated HTTPS client for the website administration API."""
def __init__(self, api_url=None, api_token=None, client: httpx.Client | None = None):
self.api_url = (api_url or settings.WEBSITE_CONTENT_API_URL).rstrip("/")
explicit_token = api_token is not None
self.api_token = api_token if explicit_token else settings.WEBSITE_CONTENT_API_TOKEN
if not explicit_token and not self.api_token and settings.WEBSITE_CONTENT_MYSQL_PASSWORD:
self.api_token = settings.WEBSITE_CONTENT_MYSQL_PASSWORD
self.client = client or httpx.Client(timeout=settings.WEBSITE_CONTENT_API_TIMEOUT_SECONDS)
def _request(self, method: str, resource: str, *, item_id=None, action=None,
data=None, files=None, expect_bytes=False, extra_params=None):
if not self.api_url or not self.api_token:
raise WebsiteContentAPIError("Website admin-API er ikke konfigureret")
params = {"resource": resource, **(extra_params or {})}
if item_id is not None:
params["id"] = item_id
if action:
params["action"] = action
headers = {"X-Website-Admin-Token": self.api_token, "Accept": "application/json"}
if settings.WEBSITE_CONTENT_API_HOST_HEADER:
headers["Host"] = settings.WEBSITE_CONTENT_API_HOST_HEADER
try:
response = self.client.request(
method, self.api_url, params=params, headers=headers,
json=self._json_values(data) if data is not None and files is None else None,
files=files,
)
except httpx.HTTPError as exc:
raise WebsiteContentAPIError("Kunne ikke kontakte website admin-API") from exc
if response.status_code == 404:
raise NotFoundError()
if not response.is_success:
try:
payload = response.json()
detail = payload.get("message") or payload.get("error")
except (ValueError, AttributeError):
detail = None
raise WebsiteContentAPIError(detail or f"Website admin-API svarede {response.status_code}")
if expect_bytes:
return response.content, response.headers.get("content-type", "application/octet-stream")
return response.json()
def list(self, kind: str, include_hidden: bool = True) -> list[dict[str, Any]]:
result = self._request("GET", kind, extra_params={"include_hidden": int(include_hidden)})
return result.get("items", result) if isinstance(result, dict) else result
def get(self, kind: str, item_id: int) -> dict[str, Any]:
return self._request("GET", kind, item_id=item_id)
def create(self, kind: str, data: dict[str, Any]) -> dict[str, Any]:
return self._request("POST", kind, data=data)
def update(self, kind: str, item_id: int, data: dict[str, Any]) -> dict[str, Any]:
return self._request("PATCH", kind, item_id=item_id, data=data)
def upload_logo(self, item_id: int, content: bytes, mime_type: str) -> dict[str, Any]:
return self._request("POST", "customers", item_id=item_id, action="logo",
files={"logo": ("logo", content, mime_type)})
def logo(self, item_id: int) -> tuple[bytes, str]:
return self._request("GET", "customers", item_id=item_id, action="logo", expect_bytes=True)
def complete_operation(self, item_id: int, ends_at: datetime | None, is_public: bool):
return self._request("POST", "operations", item_id=item_id, action="complete",
data={"ends_at": ends_at, "is_public": is_public})
@classmethod
def _json_values(cls, data):
if data is None:
return None
return {key: value.isoformat() if isinstance(value, datetime)
else str(value) if cls._is_url(value) else value for key, value in data.items()}
@staticmethod
def _is_url(value: Any) -> bool:
return value is not None and value.__class__.__module__.startswith("pydantic.networks")
website_content_service = WebsiteContentService()