103 lines
3.7 KiB
Python
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)
|