from datetime import datetime, timezone from sqlalchemy import BigInteger, Column, DateTime, Index, String, Text from sqlalchemy.dialects.postgresql import JSONB from database.postgres import Base def _now(): return datetime.now(timezone.utc) class MigrationRun(Base): """Tracks every migration script execution — what ran, when, row counts, success/failure.""" __tablename__ = "_migration_runs" id = Column(BigInteger, primary_key=True, autoincrement=True) script_name = Column(String(256), nullable=False) ran_at = Column(DateTime(timezone=True), nullable=False, default=_now) source_rows = Column(BigInteger, nullable=False, default=0) dest_rows = Column(BigInteger, nullable=False, default=0) success = Column(String(8), nullable=False, default="ok") # 'ok' | 'error' notes = Column(Text) class AuditLog(Base): """Staff action audit trail — all create/update/delete/command events.""" __tablename__ = "audit_log" __table_args__ = ( Index("idx_audit_actor", "actor_id", "occurred_at"), Index("idx_audit_entity", "entity_type", "entity_id", "occurred_at"), Index("idx_audit_action", "action", "occurred_at"), Index("idx_audit_occurred", "occurred_at"), ) id = Column(BigInteger, primary_key=True, autoincrement=True) occurred_at = Column(DateTime(timezone=True), nullable=False, default=_now) actor_id = Column(String(128), nullable=False) actor_name = Column(String(255), nullable=False) action = Column(String(64), nullable=False) # CREATE | UPDATE | DELETE | COMMAND | PUBLISH | UNPUBLISH | # LOGIN | LOGOUT | PERMISSION_CHANGE | STATUS_CHANGE entity_type = Column(String(64), nullable=False) # customer | order | device | melody | product | staff | ticket | note | quotation | ... entity_id = Column(String(128), nullable=False) entity_label = Column(String(500)) # denormalised human name changes = Column(JSONB) # {"field": {"old": x, "new": y}} — null for CREATE/DELETE meta = Column(JSONB) # extra context: ip_address, command_name, etc.