from datetime import UTC, datetime from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.model.audit import InteractionAudit from app.model.platform import DomainEventOutbox class OutboxReplayError(ValueError): pass class OutboxAdminService: def __init__(self, session: AsyncSession) -> None: self.session = session async def replay_dead(self, event_id: str, actor_id: int) -> DomainEventOutbox: event = await self.session.scalar( select(DomainEventOutbox) .where(DomainEventOutbox.event_id == event_id) .with_for_update() ) if event is None: raise OutboxReplayError("event not found") if event.status != "dead": raise OutboxReplayError("only dead event can be replayed") now = datetime.now(UTC).replace(tzinfo=None) event.status = "pending" event.retry_count = 0 event.next_retry_at = None event.last_error = None event.updated_at = now self.session.add( InteractionAudit( actor_type="user", actor_id=actor_id, target_customer_id=None, session_id=None, portal="admin", action_type="outbox.dead_replay", detail={"event_id": event.event_id, "event_type": event.event_type}, created_at=now, ) ) await self.session.flush() return event