fc / apps /message_hub /api /inbox_api.py
jun2024's picture
Upload 2241 files
b4f2238 verified
Raw History Blame Contribute Delete
3.9 kB
"""收件箱 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