138 lines
5.1 KiB
Python
138 lines
5.1 KiB
Python
import httpx
|
|
import logging
|
|
import uuid
|
|
from fastapi import APIRouter, HTTPException, Response, status
|
|
from pydantic import BaseModel
|
|
|
|
from app.services import engine
|
|
from app.services.store import document_store
|
|
|
|
router = APIRouter(tags=["documents"])
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ExportRemoteRequest(BaseModel):
|
|
fresh_token: str | None = None
|
|
|
|
@router.get("/{document_id}/export")
|
|
def export_document(document_id: str):
|
|
if not engine.is_available():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
|
detail="Engine bridge (bindings/python) not yet available.",
|
|
)
|
|
|
|
d = document_store.get_document(document_id)
|
|
if not d:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found")
|
|
|
|
perms = d.get("permissions") or {}
|
|
if perms.get("canCopy", True) is False:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Exporting is not permitted by this document's restrictions (canCopy).",
|
|
)
|
|
|
|
try:
|
|
doc = d["doc_instance"]
|
|
bytes_data = doc.save_full_for_export()
|
|
filename = d["filename"]
|
|
if not filename.endswith(".pdf"):
|
|
filename += ".pdf"
|
|
|
|
return Response(
|
|
content=bytes_data,
|
|
media_type="application/pdf",
|
|
headers={
|
|
"Content-Disposition": f'attachment; filename="{filename}"',
|
|
"Content-Length": str(len(bytes_data)),
|
|
},
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
|
|
|
|
|
@router.post("/{document_id}/export-remote")
|
|
async def export_remote_document(document_id: str, body: ExportRemoteRequest | None = None):
|
|
if not engine.is_available():
|
|
raise HTTPException(status_code=501, detail="Engine bridge not available.")
|
|
|
|
d = document_store.get_document(document_id)
|
|
if not d:
|
|
raise HTTPException(status_code=404, detail="Document not found")
|
|
|
|
perms = d.get("permissions") or {}
|
|
if perms.get("canCopy", True) is False:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Exporting is not permitted by this document's restrictions (canCopy).",
|
|
)
|
|
|
|
ctx = d.get("remote_context")
|
|
if not ctx or not ctx.get("upload_url"):
|
|
print(f"[export-remote] FAILING with 400: ctx={ctx}", flush=True)
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="Document does not have a registered remote upload context.",
|
|
)
|
|
|
|
try:
|
|
# ALWAYS use the stored bytes_data, which accurately reflects the current state (including encryption).
|
|
# doc.save_full_for_export() drops encryption or corrupts the output when exporting a protected PDF.
|
|
bytes_data = d.get("bytes_data")
|
|
if not bytes_data:
|
|
doc = d["doc_instance"]
|
|
bytes_data = doc.save_full_for_export()
|
|
|
|
filename = d["filename"]
|
|
if not filename.endswith(".pdf"):
|
|
filename += ".pdf"
|
|
|
|
headers = {}
|
|
# Prefer fresh_token sent at save-time over the potentially-stale stored token
|
|
token_val = (body.fresh_token if body and body.fresh_token else None) or ctx.get("auth_token")
|
|
if token_val:
|
|
headers["Authorization"] = f"Bearer {token_val}"
|
|
csrf_token = str(uuid.uuid4())
|
|
headers["Cookie"] = f"csrf_token={csrf_token}"
|
|
headers["X-CSRF-Token"] = csrf_token
|
|
|
|
# Resolve folder_id from remote context or default to 0
|
|
data = {"folder_id": str(ctx.get("folder_id", "0"))}
|
|
if ctx.get("resource_id"):
|
|
data["file_id"] = str(ctx["resource_id"])
|
|
|
|
upload_url = ctx["upload_url"]
|
|
print(f"[export-remote] POST {upload_url} | file_id={data.get('file_id')} | filename={filename} | size={len(bytes_data)}", flush=True)
|
|
|
|
files = {"upload": (filename, bytes_data, "application/pdf")}
|
|
|
|
async with httpx.AsyncClient(timeout=120.0, follow_redirects=True) as client:
|
|
res = await client.post(upload_url, headers=headers, data=data, files=files)
|
|
|
|
print(f"[export-remote] Response status={res.status_code} body={res.text[:500]}", flush=True)
|
|
|
|
if res.status_code not in (200, 201):
|
|
logger.error("[export-remote] upstream %s: %s", res.status_code, res.text[:500])
|
|
raise HTTPException(
|
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
|
detail=f"Remote upload failed (upstream status {res.status_code}).",
|
|
)
|
|
|
|
try:
|
|
remote_response = res.json() if res.content else {}
|
|
except ValueError:
|
|
remote_response = {}
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Successfully exported updated document to remote host!",
|
|
"remoteResponse": remote_response,
|
|
}
|
|
except Exception as e:
|
|
if isinstance(e, HTTPException):
|
|
raise e
|
|
logger.error(f"[export-remote] Unexpected error: {e}", exc_info=True)
|
|
raise HTTPException(status_code=500, detail="Remote export failed.")
|
|
|