mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 20:17:13 +08:00
123 lines
4.2 KiB
Python
123 lines
4.2 KiB
Python
"""按媒体身份批量删除订阅的应用用例。"""
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Callable, Protocol
|
|
|
|
from app.application.outbox import AsyncOutboxTransaction, OutboxIntent
|
|
from app.application.subscription.delete import (
|
|
AsyncUnitOfWork,
|
|
SubscribeDeletedPublisher,
|
|
SubscribeDeletionActor,
|
|
SubscribeDeletionCandidate,
|
|
build_subscribe_deleted_payload,
|
|
)
|
|
from app.schemas.types import MediaSource
|
|
|
|
|
|
class SubscribeIdentityDeletionRepository(Protocol):
|
|
"""按媒体身份删除订阅所需的数据访问端口。"""
|
|
|
|
async def list_candidates_by_identity(
|
|
self,
|
|
media_source: MediaSource,
|
|
media_id: str,
|
|
season: int | None,
|
|
music_type: str | None,
|
|
) -> list[SubscribeDeletionCandidate]:
|
|
"""读取匹配媒体身份的去重订阅快照。"""
|
|
...
|
|
|
|
async def stage_delete(self, subscribe_id: int) -> None:
|
|
"""把指定订阅登记为待删除,但不自行提交事务。"""
|
|
...
|
|
|
|
|
|
SubscribeDeletionEventErrorHandler = Callable[[int, Exception], None]
|
|
|
|
|
|
class DeleteSubscriptionsByIdentityCommand:
|
|
"""按媒体身份删除当前用户可访问的全部订阅。"""
|
|
|
|
def __init__(
|
|
self,
|
|
repository: SubscribeIdentityDeletionRepository,
|
|
unit_of_work: AsyncUnitOfWork,
|
|
publish_deleted: SubscribeDeletedPublisher,
|
|
handle_event_error: SubscribeDeletionEventErrorHandler,
|
|
outbox: AsyncOutboxTransaction | None = None,
|
|
) -> None:
|
|
"""注入数据访问、事务、事件和事件错误处理端口。"""
|
|
self._repository = repository
|
|
self._unit_of_work = unit_of_work
|
|
self._publish_deleted = publish_deleted
|
|
self._handle_event_error = handle_event_error
|
|
self._outbox = outbox
|
|
|
|
async def execute(
|
|
self,
|
|
media_source: MediaSource,
|
|
media_id: str,
|
|
season: int | None,
|
|
music_type: str | None,
|
|
actor: SubscribeDeletionActor,
|
|
) -> int:
|
|
"""删除匹配订阅,并在提交后逐条发送兼容事件。"""
|
|
candidates = await self._repository.list_candidates_by_identity(
|
|
media_source,
|
|
media_id,
|
|
season,
|
|
music_type,
|
|
)
|
|
deletions = [
|
|
candidate
|
|
for candidate in candidates
|
|
if self._can_delete(candidate, actor)
|
|
]
|
|
events: list[tuple[SubscribeDeletionCandidate, dict[str, Any]]] = []
|
|
for candidate in deletions:
|
|
await self._repository.stage_delete(candidate.subscribe_id)
|
|
event_payload = build_subscribe_deleted_payload(
|
|
candidate.subscribe_id,
|
|
candidate.event_payload,
|
|
)
|
|
events.append((candidate, event_payload))
|
|
|
|
try:
|
|
if self._outbox:
|
|
now = datetime.now(timezone.utc)
|
|
for _, event_payload in events:
|
|
await self._outbox.stage(
|
|
OutboxIntent(
|
|
event_key=event_payload["idempotency_key"],
|
|
topic="subscribe.deleted",
|
|
payload=event_payload,
|
|
),
|
|
now,
|
|
)
|
|
await self._unit_of_work.commit()
|
|
except Exception:
|
|
await self._unit_of_work.rollback()
|
|
raise
|
|
|
|
for candidate, event_payload in events:
|
|
try:
|
|
await self._publish_deleted(event_payload)
|
|
if self._outbox:
|
|
await self._outbox.complete_by_event_key(
|
|
event_payload["idempotency_key"],
|
|
datetime.now(timezone.utc),
|
|
)
|
|
except Exception as error:
|
|
self._handle_event_error(candidate.subscribe_id, error)
|
|
return len(deletions)
|
|
|
|
@staticmethod
|
|
def _can_delete(
|
|
candidate: SubscribeDeletionCandidate,
|
|
actor: SubscribeDeletionActor,
|
|
) -> bool:
|
|
"""判断用户是否拥有候选订阅的删除权限。"""
|
|
if actor.is_superuser:
|
|
return True
|
|
return bool(candidate.username) and candidate.username == actor.username
|