import base64 import html import os from datetime import datetime, timezone from typing import Any, Dict, Optional import httpx from fastapi import Depends, FastAPI, Header, HTTPException GOTENBERG_URL = os.getenv("ONEOK_GOTENBERG_URL", "http://oneok-gotenberg:3000").rstrip("/") API_KEY = os.getenv("ONEOK_ADAPTER_API_KEY", "").strip() app = FastAPI(title="1OK Docs Adapter", version="1.0.0") def _auth(authorization: Optional[str] = Header(default=None)) -> None: if not API_KEY: return expected = f"Bearer {API_KEY}" if authorization != expected: raise HTTPException(status_code=401, detail="Unauthorized") def _render_html(title: str, data: Dict[str, Any]) -> str: ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") rows = [] for k, v in data.items(): rows.append( f"{html.escape(str(k))}" f"{html.escape(str(v))}" ) table = "\n".join(rows) if rows else "No data" return f"""

{html.escape(title)}

Generated: {html.escape(ts)}
{table}
""" async def _render_pdf(file_name: str, html_text: str) -> Dict[str, Any]: endpoints = [ f"{GOTENBERG_URL}/forms/chromium/convert/html", f"{GOTENBERG_URL}/forms/libreoffice/convert", ] async with httpx.AsyncClient(timeout=60.0) as client: files = {file_name: (file_name, html_text.encode("utf-8"), "text/html")} last_error = None pdf = b"" for url in endpoints: resp = await client.post(url, files=files) if resp.status_code < 400: pdf = resp.content break last_error = f"{url} -> {resp.status_code} {resp.text[:200]}" if not pdf: raise HTTPException(status_code=502, detail=f"Gotenberg error: {last_error}") return { "file_name": file_name.replace(".html", ".pdf"), "mime": "application/pdf", "size_bytes": len(pdf), "pdf_base64": base64.b64encode(pdf).decode("utf-8"), } @app.get("/health") def health() -> Dict[str, Any]: return {"status": "ok", "service": "oneok-docs-adapter"} @app.post("/docs/render_quote_pdf") async def render_quote_pdf(body: Dict[str, Any], _: None = Depends(_auth)) -> Dict[str, Any]: quote_id = body.get("quote_id") payload = body.get("quote_payload") or {} if not quote_id and not payload: raise HTTPException(status_code=400, detail="quote_id or quote_payload required") if quote_id and "quote_id" not in payload: payload = dict(payload) payload["quote_id"] = quote_id html_doc = _render_html("Комерційна пропозиція (1OK)", payload) return await _render_pdf("quote.html", html_doc) @app.post("/docs/render_invoice_pdf") async def render_invoice_pdf(body: Dict[str, Any], _: None = Depends(_auth)) -> Dict[str, Any]: payload = body.get("invoice_payload") if not isinstance(payload, dict): raise HTTPException(status_code=400, detail="invoice_payload required") html_doc = _render_html("Рахунок (1OK)", payload) return await _render_pdf("invoice.html", html_doc)