Files
microdao-daarion/services/oneok-docs-adapter/app.py

103 lines
3.7 KiB
Python

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"<tr><td style='padding:6px;border:1px solid #ddd;vertical-align:top'><b>{html.escape(str(k))}</b></td>"
f"<td style='padding:6px;border:1px solid #ddd'>{html.escape(str(v))}</td></tr>"
)
table = "\n".join(rows) if rows else "<tr><td style='padding:6px;border:1px solid #ddd'>No data</td></tr>"
return f"""<!doctype html>
<html lang="uk">
<head>
<meta charset="utf-8" />
<style>
body {{ font-family: Arial, sans-serif; color: #111; margin: 24px; }}
h1 {{ margin: 0 0 8px; font-size: 24px; }}
.meta {{ color: #666; margin: 0 0 18px; font-size: 12px; }}
table {{ border-collapse: collapse; width: 100%; }}
td {{ font-size: 13px; }}
</style>
</head>
<body>
<h1>{html.escape(title)}</h1>
<div class="meta">Generated: {html.escape(ts)}</div>
<table>{table}</table>
</body>
</html>"""
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)