From 9776311c2017ddbc2b58dd83c2e2fcfd9d12e4ae Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Tue, 26 May 2026 20:56:59 -0400 Subject: [PATCH] Add public share PDF download endpoint --- backend/main.py | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/backend/main.py b/backend/main.py index fb29520..ae94726 100644 --- a/backend/main.py +++ b/backend/main.py @@ -2790,6 +2790,50 @@ async def api_share_revoke(share_id: str, current_user=Depends(require_auth)): return {"status": "revoked"} +@app.get("/s/{token}/pdf") +async def public_share_pdf(token: str): + """Download shared document as PDF — no authentication required.""" + share = get_share_by_token(token) + if not share: + raise HTTPException(404, "Share not found or expired") + vault_data = get_vault_data(share["vault"]) + if not vault_data: + raise HTTPException(404, "Vault not found") + vault_root = Path(vault_data["path"]) + file_path = _resolve_safe_path(vault_root, share["path"]) + if not file_path.exists(): + raise HTTPException(404, "File not found") + try: + raw = file_path.read_text(encoding="utf-8", errors="replace") + except Exception: + raise HTTPException(500, "Cannot read file") + record_access(token) + raw = redact_file_content(raw, str(file_path)) + post = parse_markdown_file(raw) + html = _render_markdown(post.content, share["vault"], file_path) + title = post.metadata.get("title", file_path.stem) + + # Build a full HTML page for PDF rendering + full_html = f"""{title} +

{title}

{html}""" + + filename = f"{title}.pdf" + return HTMLResponse( + content=full_html, + media_type="text/html", + headers={ + "Content-Disposition": f'attachment; filename="{filename}"', + "X-PDF-Download": "true", + }, + ) + + @app.get("/s/{token}") async def public_share_view(token: str): """Public share view — no authentication required.""" @@ -2868,7 +2912,7 @@ body{{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color: function toggleTheme(){{var t=document.documentElement;var isDark=t.dataset.theme==="dark";t.dataset.theme=isDark?"light":"dark";document.getElementById("theme-icon-dark").style.display=isDark?"none":"";document.getElementById("theme-icon-light").style.display=isDark?"":"none";localStorage.setItem("obsigate-share-theme",t.dataset.theme)}} (function(){{var s=localStorage.getItem("obsigate-share-theme");if(!s)s="dark";document.documentElement.dataset.theme=s;var isDark=s==="dark";document.getElementById("theme-icon-dark").style.display=isDark?"":"none";document.getElementById("theme-icon-light").style.display=isDark?"none":""}})(); function exportMD(){{var t=document.getElementById("content").innerText;var b=new Blob([t],{{type:"text/markdown"}});var a=document.createElement("a");a.href=URL.createObjectURL(b);a.download="{title}.md";a.click()}} -function exportPDF(){{document.title="{title}";window.print()}} +function exportPDF(){{var w=window.open("","_blank","width=800,height=600");w.document.write("{title}

{title}

"+document.getElementById("content").innerHTML+"");w.document.close();w.focus();setTimeout(function(){{w.print();w.close()}},500)}} """)