from __future__ import annotations import uuid from typing import Any from datetime import datetime from sqlalchemy import DateTime, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from govoplan_core.db.base import Base, TimestampMixin def new_uuid() -> str: return str(uuid.uuid4()) class AuditLog(Base, TimestampMixin): __tablename__ = "audit_log" __table_args__ = ( Index("ix_audit_log_scope_created_at", "scope", "created_at"), Index("ix_audit_log_tenant_scope_created_at", "tenant_id", "scope", "created_at"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) scope: Mapped[str] = mapped_column(String(20), default="tenant", nullable=False, index=True) tenant_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True) user_id: Mapped[str | None] = mapped_column(ForeignKey("access_users.id", ondelete="SET NULL"), nullable=True, index=True) api_key_id: Mapped[str | None] = mapped_column(ForeignKey("access_api_keys.id", ondelete="SET NULL"), nullable=True, index=True) action: Mapped[str] = mapped_column(String(100), nullable=False, index=True) object_type: Mapped[str | None] = mapped_column(String(100), index=True) object_id: Mapped[str | None] = mapped_column(String(100), index=True) details: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) class AuditOutboxEvent(Base, TimestampMixin): __tablename__ = "audit_outbox_events" __table_args__ = ( UniqueConstraint("event_id", name="uq_audit_outbox_events_event_id"), Index("ix_audit_outbox_events_status_next_attempt_at", "status", "next_attempt_at"), Index("ix_audit_outbox_events_event_type", "event_type"), Index("ix_audit_outbox_events_correlation_id", "correlation_id"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) event_id: Mapped[str] = mapped_column(String(36), nullable=False) event_type: Mapped[str] = mapped_column(String(200), nullable=False) module_id: Mapped[str] = mapped_column(String(100), nullable=False) correlation_id: Mapped[str | None] = mapped_column(String(128), nullable=True) causation_id: Mapped[str | None] = mapped_column(String(128), nullable=True) classification: Mapped[str] = mapped_column(String(40), nullable=False, default="internal") payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending", index=True) attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=0) next_attempt_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) dispatched_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) last_error: Mapped[str | None] = mapped_column(Text, nullable=True) __all__ = ["AuditLog", "AuditOutboxEvent", "new_uuid"]