Spaces:
Running
Running
Commit ·
eaeeb44
1
Parent(s): 5b492d5
api: accept PDF uploads + serve PDFs locally
Browse files- backend/api.py +39 -21
backend/api.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import json
|
|
|
|
| 4 |
import os
|
| 5 |
import socket
|
| 6 |
import paramiko
|
|
@@ -13,8 +14,6 @@ from dotenv import load_dotenv
|
|
| 13 |
from fastapi import FastAPI, HTTPException, Response
|
| 14 |
from fastapi.middleware.cors import CORSMiddleware
|
| 15 |
from fastapi.responses import FileResponse
|
| 16 |
-
from backend.sftp_store import download_bytes
|
| 17 |
-
from backend.sftp_store import download_bytes
|
| 18 |
|
| 19 |
|
| 20 |
# ------------------------------------------------------------------
|
|
@@ -106,27 +105,46 @@ def root() -> Dict[str, str]:
|
|
| 106 |
# API endpoints
|
| 107 |
# ------------------------------------------------------------------
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
@app.get("/api/pdf/{pdf_id}")
|
| 110 |
def get_pdf(pdf_id: str):
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
if not
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
remote_path = f"pdfs/{name}"
|
| 117 |
-
|
| 118 |
-
# Hard timeout so requests never hang
|
| 119 |
-
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
|
| 120 |
-
fut = ex.submit(download_bytes, remote_path)
|
| 121 |
-
try:
|
| 122 |
-
data = fut.result(timeout=10)
|
| 123 |
-
except concurrent.futures.TimeoutError:
|
| 124 |
-
from fastapi import HTTPException
|
| 125 |
-
raise HTTPException(status_code=504, detail="SFTP timeout reading PDF")
|
| 126 |
-
|
| 127 |
-
# Return raw bytes
|
| 128 |
-
from fastapi.responses import Response
|
| 129 |
-
return Response(content=data, media_type="application/pdf")
|
| 130 |
return Response(content=data, media_type="application/pdf")
|
| 131 |
|
| 132 |
@app.post("/api/send-config")
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import json
|
| 4 |
+
import re
|
| 5 |
import os
|
| 6 |
import socket
|
| 7 |
import paramiko
|
|
|
|
| 14 |
from fastapi import FastAPI, HTTPException, Response
|
| 15 |
from fastapi.middleware.cors import CORSMiddleware
|
| 16 |
from fastapi.responses import FileResponse
|
|
|
|
|
|
|
| 17 |
|
| 18 |
|
| 19 |
# ------------------------------------------------------------------
|
|
|
|
| 105 |
# API endpoints
|
| 106 |
# ------------------------------------------------------------------
|
| 107 |
|
| 108 |
+
# ------------------------------------------------------------------
|
| 109 |
+
# PDF storage (local to API container)
|
| 110 |
+
# Worker uploads PDFs here. UI fetches from here.
|
| 111 |
+
# ------------------------------------------------------------------
|
| 112 |
+
|
| 113 |
+
PDF_STORE_DIR = Path(os.getenv("PDF_STORE_DIR", "/app/data/pdfs")).resolve()
|
| 114 |
+
PDF_STORE_DIR.mkdir(parents=True, exist_ok=True)
|
| 115 |
+
|
| 116 |
+
def _require_worker_token(x_worker_token: Optional[str]):
|
| 117 |
+
# reuse global WORKER_TOKEN if you already have it later in the file
|
| 118 |
+
token = os.getenv("WORKER_TOKEN", "").strip()
|
| 119 |
+
if not token:
|
| 120 |
+
raise HTTPException(status_code=500, detail="Server missing WORKER_TOKEN")
|
| 121 |
+
if not x_worker_token or x_worker_token != token:
|
| 122 |
+
raise HTTPException(status_code=401, detail="Unauthorized worker")
|
| 123 |
+
|
| 124 |
+
def _safe_id(s: str) -> str:
|
| 125 |
+
# keep it simple: allow only safe filename chars
|
| 126 |
+
return re.sub(r"[^a-zA-Z0-9_.-]+", "_", s)
|
| 127 |
+
|
| 128 |
+
@app.post("/api/pdf/{pdf_id}")
|
| 129 |
+
async def put_pdf(pdf_id: str, request: Request, x_worker_token: Optional[str] = Header(default=None)):
|
| 130 |
+
_require_worker_token(x_worker_token)
|
| 131 |
+
pid = _safe_id(pdf_id)
|
| 132 |
+
data = await request.body()
|
| 133 |
+
if not data:
|
| 134 |
+
raise HTTPException(status_code=400, detail="Empty body")
|
| 135 |
+
if len(data) > 50 * 1024 * 1024:
|
| 136 |
+
raise HTTPException(status_code=413, detail="PDF too large")
|
| 137 |
+
out = PDF_STORE_DIR / (pid if pid.lower().endswith(".pdf") else pid + ".pdf")
|
| 138 |
+
out.write_bytes(data)
|
| 139 |
+
return {"ok": True, "pdf_id": pid, "bytes": len(data)}
|
| 140 |
+
|
| 141 |
@app.get("/api/pdf/{pdf_id}")
|
| 142 |
def get_pdf(pdf_id: str):
|
| 143 |
+
pid = _safe_id(pdf_id)
|
| 144 |
+
f = PDF_STORE_DIR / (pid if pid.lower().endswith(".pdf") else pid + ".pdf")
|
| 145 |
+
if not f.exists():
|
| 146 |
+
raise HTTPException(status_code=404, detail="PDF not found")
|
| 147 |
+
data = f.read_bytes()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
return Response(content=data, media_type="application/pdf")
|
| 149 |
|
| 150 |
@app.post("/api/send-config")
|