audit_log.py 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. """
  2. AuditLog model - tracks all user actions.
  3. """
  4. from sqlalchemy import BigInteger, ForeignKey, Integer, String
  5. from sqlalchemy.dialects.postgresql import INET, JSONB
  6. from sqlalchemy.orm import Mapped, mapped_column, relationship
  7. from app.models.base import Base
  8. class AuditLog(Base):
  9. """
  10. Audit log model for tracking user actions.
  11. Logs every action: login, view, create, update, delete, export, etc.
  12. """
  13. __tablename__ = "audit_logs"
  14. id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
  15. # Who
  16. user_id: Mapped[int | None] = mapped_column(
  17. ForeignKey("users.id", ondelete="SET NULL")
  18. )
  19. user_email: Mapped[str | None] = mapped_column(
  20. String(255)
  21. ) # Cached for deleted users
  22. organization_id: Mapped[int | None] = mapped_column(
  23. ForeignKey("organizations.id", ondelete="SET NULL")
  24. )
  25. # What
  26. action: Mapped[str] = mapped_column(
  27. String(50), nullable=False
  28. ) # login, logout, failed_login, view, create, update, delete, export, etc.
  29. resource_type: Mapped[str | None] = mapped_column(
  30. String(50)
  31. ) # device, user, location, beacon, etc.
  32. resource_id: Mapped[int | None] = mapped_column(Integer)
  33. # Details
  34. description: Mapped[str | None] = mapped_column(String)
  35. changes: Mapped[dict | None] = mapped_column(JSONB) # Before/after values
  36. # When & Where
  37. ip_address: Mapped[str | None] = mapped_column(INET)
  38. user_agent: Mapped[str | None] = mapped_column(String)
  39. # Relationships
  40. user: Mapped["User | None"] = relationship("User", back_populates="audit_logs")
  41. organization: Mapped["Organization | None"] = relationship("Organization")
  42. def __repr__(self) -> str:
  43. return f"<AuditLog {self.id}: {self.action} by {self.user_email}>"