267:async def preview_attachment(att_id: int, user: User = Depends(get_current_user), 268- db: AsyncSession = Depends(get_db)): 269- """Preview: images and PDFs return base64 data URI; text/html return content.""" 270- att = await _get_own(db, user, att_id) 271- 272- if att.is_html_attachment: 273- body = att.html_attachment_body or "" 274: return {"previewable": True, "kind": "html", 275: "content": sanitize_preview(body)[:50000]} 276- 277- content = await asyncio.get_event_loop().run_in_executor( 278- None, lambda: att_svc.read_attachment_bytes(att) or b"") 279- if not content: 280: return {"previewable": False, "reason": "no_content"} 281- if len(content) > 2 * 1024 * 1024: 282: return {"previewable": False, "reason": "too_large", "size": len(content)} 283- if att.kind in ("image", "pdf"): 284- mime = att.mime_type or ("application/pdf" if att.kind == "pdf" else "image/png") 285: return {"previewable": True, "kind": att.kind, 286- "data_uri": f"data:{mime};base64,{base64.b64encode(content).decode()}"} 287- if att.kind in ("text", "html", "file"): 288- try: 289- text = content.decode("utf-8") 290- except UnicodeDecodeError: 291: return {"previewable": False, "reason": "binary"} 292- if att.mime_type == "text/html":