"""Consumer inbox model for outbox idempotency.""" from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import DateTime, ForeignKey, String, Text, UniqueConstraint, func from sqlalchemy.dialects.postgresql import UUID as PGUUID from sqlalchemy.orm import Mapped, mapped_column from app.core.db import Base class ConsumerInbox(Base): """Tracks which consumers have processed which outbox events. Prevents duplicate processing when a worker crashes between delivering an event and marking it as published. """ __tablename__ = "consumer_inbox" __table_args__ = ( UniqueConstraint("event_id", "consumer_name", name="uq_consumer_inbox_event_consumer"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) event_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), ForeignKey("event_outbox.id", ondelete="CASCADE"), nullable=False, index=True, ) consumer_name: Mapped[str] = mapped_column(String(100), nullable=False, index=True) status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending") processed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() )