from typing import Any, Protocol import httpx from fastapi import HTTPException, status from app.core.config import Settings from app.core.constants import BEARER_TOKEN_TEMPLATE, HttpHeader from app.modules.writebacks.constants import ( WRITEBACK_DISABLED_MESSAGE, WritebackErrorDetail, WritebackPayloadKey, WritebackStatus, ) from app.modules.writebacks.models import OfficialWritebackRun class WritebackAdapter(Protocol): def submit(self, run: OfficialWritebackRun) -> dict[str, Any]: """Submit one writeback run to the configured official integration.""" class DisabledWritebackAdapter: def submit(self, run: OfficialWritebackRun) -> dict[str, Any]: return { WritebackPayloadKey.STATUS: WritebackStatus.DISABLED, WritebackPayloadKey.ERROR_MESSAGE: WRITEBACK_DISABLED_MESSAGE, WritebackPayloadKey.CODE: run.code, } class HttpWritebackAdapter: def __init__(self, settings: Settings): self.settings = settings def submit(self, run: OfficialWritebackRun) -> dict[str, Any]: if not self.settings.official_api_base_url or not self.settings.official_api_token: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=WritebackErrorDetail.OFFICIAL_API_NOT_CONFIGURED, ) url = f"{self.settings.official_api_base_url.rstrip('/')}/writebacks/{run.domain}" headers = { HttpHeader.AUTHORIZATION: BEARER_TOKEN_TEMPLATE.format( token=self.settings.official_api_token ) } payload = { WritebackPayloadKey.CODE: run.code, WritebackPayloadKey.DOMAIN: run.domain, WritebackPayloadKey.RECORD_ID: run.record_id, WritebackPayloadKey.ACTION: run.action, WritebackPayloadKey.PAYLOAD: run.request_payload or {}, } with httpx.Client(timeout=self.settings.official_api_timeout_seconds) as client: response = client.post(url, json=payload, headers=headers) if response.status_code >= status.HTTP_400_BAD_REQUEST: raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail=response.text, ) try: data = response.json() except ValueError: data = {"text": response.text} return { WritebackPayloadKey.STATUS: WritebackStatus.SENT, WritebackPayloadKey.PROVIDER_RESPONSE: data, } def get_writeback_adapter(settings: Settings) -> WritebackAdapter: if settings.official_writeback_enabled: return HttpWritebackAdapter(settings) return DisabledWritebackAdapter()