File size: 3,896 Bytes
b4f2238 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | """收件箱 API 端点。"""
from __future__ import annotations
import logging
from typing import Any
from django.http import FileResponse, HttpRequest
from ninja import Query, Router
from apps.core.exceptions import NotFoundError
from apps.message_hub.models import InboxMessage
from apps.message_hub.schemas import InboxMessageDetailOut, InboxMessageOut
logger = logging.getLogger("apps.message_hub")
router = Router()
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _get_base_queryset() -> Any:
return InboxMessage.objects.select_related("source", "source__credential").order_by("-received_at")
def _get_message_or_404(pk: int) -> InboxMessage:
try:
return _get_base_queryset().get(pk=pk)
except InboxMessage.DoesNotExist:
raise NotFoundError(f"消息 {pk} 不存在")
# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------
@router.get("/messages", response=list[InboxMessageOut])
def list_messages(
request: HttpRequest,
source_id: int | None = None,
has_attachments: bool | None = None,
search: str | None = None,
) -> Any:
"""收件箱消息列表。"""
qs = _get_base_queryset()
if source_id is not None:
qs = qs.filter(source_id=source_id)
if has_attachments is not None:
qs = qs.filter(has_attachments=has_attachments)
if search:
from django.db.models import Q
qs = qs.filter(Q(subject__icontains=search) | Q(sender__icontains=search) | Q(body_text__icontains=search))
return qs
@router.get("/messages/{message_id}", response=InboxMessageDetailOut)
def get_message(request: HttpRequest, message_id: int) -> Any:
"""收件箱消息详情。"""
return _get_message_or_404(message_id)
@router.get("/messages/{message_id}/attachments/{part_index}/download")
def download_attachment(
request: HttpRequest,
message_id: int,
part_index: int,
) -> FileResponse:
"""下载附件。"""
msg = _get_message_or_404(message_id)
return _serve_attachment(msg, part_index, inline=False)
@router.get("/messages/{message_id}/attachments/{part_index}/preview")
def preview_attachment(
request: HttpRequest,
message_id: int,
part_index: int,
) -> FileResponse:
"""预览附件(inline)。"""
msg = _get_message_or_404(message_id)
return _serve_attachment(msg, part_index, inline=True)
def _resolve_download_filename(msg: InboxMessage, part_index: int, fallback: str) -> str:
for att in msg.attachments_meta or []:
if int(att.get("part_index", -1)) != part_index:
continue
custom_name = str(att.get("custom_filename", "")).strip()
if custom_name:
return custom_name
original_name = str(att.get("original_filename") or att.get("filename") or "").strip()
if original_name:
return original_name
return fallback
def _serve_attachment(msg: InboxMessage, part_index: int, *, inline: bool) -> FileResponse:
"""通过 fetcher 按需下载并返回附件。"""
from apps.message_hub.services import get_fetcher
fetcher = get_fetcher(msg.source.source_type)
content, filename, content_type = fetcher.download_attachment(
msg.source,
msg.message_id,
part_index,
)
download_filename = _resolve_download_filename(msg, part_index, filename)
disposition = "inline" if inline else "attachment"
response = FileResponse(
iter([content]),
content_type=content_type,
as_attachment=not inline,
filename=download_filename,
)
response["Content-Disposition"] = f'{disposition}; filename="{download_filename}"'
return response
|