"""Audit log model for centralized audit trail.""" import uuid from datetime import datetime from typing import Any from sqlalchemy import DateTime, ForeignKey, String, Text, func from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column from app.database import Base class AuditLog(Base): """Records all auditable actions in the system (config changes, CRUD, auth events).""" __tablename__ = "audit_logs" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=func.gen_random_uuid(), ) tenant_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True, ) user_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, ) action: Mapped[str] = mapped_column(String(100), nullable=False) resource_type: Mapped[str | None] = mapped_column(String(50), nullable=True) resource_id: Mapped[str | None] = mapped_column(String(255), nullable=True) device_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("devices.id", ondelete="SET NULL"), nullable=True, ) details: Mapped[dict[str, Any]] = mapped_column( JSONB, nullable=False, server_default="{}", ) # Transit-encrypted details JSON (vault:v1:...) — set when details are encrypted encrypted_details: Mapped[str | None] = mapped_column(Text, nullable=True) ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False, ) def __repr__(self) -> str: return f""