Add public share PDF download endpoint

This commit is contained in:
Bruno Charest 2026-05-26 20:56:59 -04:00
parent b0b5541bc5
commit 9776311c20

View File

@ -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"""<!DOCTYPE html><html><head><meta charset="utf-8"><title>{title}</title>
<style>
body{{font-family:Georgia,serif;max-width:700px;margin:40px auto;padding:0 20px;line-height:1.7;color:#1a1a2e;font-size:13px}}
h1{{font-size:22px;border-bottom:2px solid #333;padding-bottom:6px}}h2{{font-size:18px;margin-top:20px}}h3{{font-size:15px}}
pre{{background:#f5f5f5;padding:10px;border-radius:4px;font-size:11px;overflow-x:auto}}code{{font-size:12px;background:#f5f5f5;padding:1px 3px}}
img{{max-width:100%}}blockquote{{border-left:3px solid #ccc;padding-left:12px;color:#555}}
table{{border-collapse:collapse;width:100%}}th,td{{border:1px solid #ddd;padding:6px 10px;text-align:left}}th{{background:#f0f0f0}}
</style></head><body><h1>{title}</h1>{html}</body></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("<!DOCTYPE html><html><head><meta charset=utf-8><title>{title}</title><style>body{{font-family:Georgia,serif;max-width:700px;margin:40px auto;padding:0 20px;line-height:1.7;color:#1a1a2e;font-size:13px}}h1{{font-size:22px;border-bottom:2px solid #333;padding-bottom:6px}}h2{{font-size:18px;margin-top:20px}}h3{{font-size:15px}}pre{{background:#f5f5f5;padding:10px;border-radius:4px;font-size:11px}}code{{font-size:12px;background:#f5f5f5;padding:1px 3px}}img{{max-width:100%}}blockquote{{border-left:3px solid #ccc;padding-left:12px;color:#555}}table{{border-collapse:collapse;width:100%}}th,td{{border:1px solid #ddd;padding:6px 10px}}th{{background:#f0f0f0}}@media print{{body{{margin:20px}}}}</style></head><body><h1>{title}</h1>"+document.getElementById("content").innerHTML+"</body></html>");w.document.close();w.focus();setTimeout(function(){{w.print();w.close()}},500)}}
</script></body></html>""")