Files
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

127 lines
3.8 KiB
Python

"""
手工订阅搜索应用用例。
"""
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Optional
from app.application.subscription.contract import (
SubscribeDeletionCandidate,
SubscriptionStagingPort,
)
@dataclass(frozen=True, slots=True)
class SubscribeSearchActor:
"""
执行手工订阅搜索的用户身份。
:param username: 发起操作的已认证本地用户名
:param is_superuser: 操作人是否具有超级管理员权限
"""
username: str
is_superuser: bool
@dataclass(frozen=True, slots=True)
class SubscriptionSearchSubmission:
"""
一次手工搜索请求的入队结果。
:param batch_ids: 本次手工搜索涉及的活动批次标识
:param target_count: 本次目标订阅总数
:param queued_count: 本次新进入队列的任务数
:param ongoing_count: 已在活动队列中而未重复提交的任务数
:param single: 是否仅针对一个订阅发起搜索
"""
batch_ids: tuple[str, ...]
target_count: int
queued_count: int
ongoing_count: int
single: bool
@property
def batch_id(self) -> Optional[str]:
"""
返回最适合前端立即跟踪的批次编号。
"""
return self.batch_ids[0] if self.batch_ids else None
SubscribeSearchSubmitter = Callable[
[tuple[int, ...], bool],
Awaitable[SubscriptionSearchSubmission],
]
class SearchSubscriptionsCommand:
"""
按用户权限读取目标,并把手工搜索轻量提交到持久队列。
"""
def __init__(
self,
repository: SubscriptionStagingPort,
submit_search: SubscribeSearchSubmitter,
) -> None:
"""
注入订阅读取端口和持久队列提交端口。
"""
self._repository = repository
self._submit_search = submit_search
async def execute(
self,
actor: SubscribeSearchActor,
subscribe_id: int | None = None,
mtype: Optional[str] = None,
) -> Optional[SubscriptionSearchSubmission]:
"""
提交单条或指定媒体类型的可搜索订阅,目标不可访问时返回空。
"""
if subscribe_id is not None:
candidate = await self._repository.get_candidate(subscribe_id)
if not self._can_access(candidate, actor) or (
mtype is not None
and (candidate is None or candidate.event_payload.get("type") != mtype)
):
return None
return await self._submit_search((subscribe_id,), True)
search_username = None if actor.is_superuser else actor.username
if mtype is None:
# 保留旧调用形态,兼容仍只实现全量查询参数的外部端口测试替身。
subscribe_ids = await self._repository.list_search_ids(search_username, "R")
else:
subscribe_ids = await self._repository.list_search_ids(
search_username,
"R",
mtype=mtype,
)
if not subscribe_ids:
return SubscriptionSearchSubmission(
batch_ids=(),
target_count=0,
queued_count=0,
ongoing_count=0,
single=False,
)
return await self._submit_search(tuple(subscribe_ids), False)
@staticmethod
def _can_access(
candidate: SubscribeDeletionCandidate | None,
actor: SubscribeSearchActor,
) -> bool:
"""
沿用订阅读取接口的超级用户和归属用户权限语义。
"""
if candidate is None:
return False
if actor.is_superuser:
return True
return bool(candidate.username) and candidate.username == actor.username