Files
delphi-database/app/models/audit.py
2025-08-13 18:53:35 -05:00

102 lines
4.6 KiB
Python

"""
Audit logging models
"""
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, JSON
from sqlalchemy.orm import relationship
from datetime import datetime
from app.models.base import BaseModel
class AuditLog(BaseModel):
"""
Audit log for tracking user actions and system events
"""
__tablename__ = "audit_logs"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True) # Nullable for system events
username = Column(String(100), nullable=True) # Store username for deleted users
action = Column(String(100), nullable=False) # Action performed (CREATE, UPDATE, DELETE, LOGIN, etc.)
resource_type = Column(String(50), nullable=False) # Type of resource (USER, CUSTOMER, FILE, etc.)
resource_id = Column(String(100), nullable=True) # ID of the affected resource
details = Column(JSON, nullable=True) # Additional details as JSON
ip_address = Column(String(45), nullable=True) # IPv4/IPv6 address
user_agent = Column(Text, nullable=True) # Browser/client information
timestamp = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
# Relationships
user = relationship("User", back_populates="audit_logs")
def __repr__(self):
return f"<AuditLog(id={self.id}, user='{self.username}', action='{self.action}', resource='{self.resource_type}')>"
class LoginAttempt(BaseModel):
"""
Track login attempts for security monitoring
"""
__tablename__ = "login_attempts"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
username = Column(String(100), nullable=False, index=True)
ip_address = Column(String(45), nullable=False)
user_agent = Column(Text, nullable=True)
success = Column(Integer, default=0) # 1 for success, 0 for failure
timestamp = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
failure_reason = Column(String(200), nullable=True) # Reason for failure
def __repr__(self):
return f"<LoginAttempt(username='{self.username}', success={bool(self.success)}, timestamp='{self.timestamp}')>"
class ImportAudit(BaseModel):
"""
Records each batch CSV upload run with metrics and outcome.
"""
__tablename__ = "import_audit"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
started_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
finished_at = Column(DateTime, nullable=True, index=True)
status = Column(String(30), nullable=False, default="running", index=True) # running|success|completed_with_errors|failed
total_files = Column(Integer, nullable=False, default=0)
successful_files = Column(Integer, nullable=False, default=0)
failed_files = Column(Integer, nullable=False, default=0)
total_imported = Column(Integer, nullable=False, default=0)
total_errors = Column(Integer, nullable=False, default=0)
initiated_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
initiated_by_username = Column(String(100), nullable=True)
message = Column(String(255), nullable=True)
details = Column(JSON, nullable=True) # optional, compact summary payload
user = relationship("User")
files = relationship("ImportAuditFile", back_populates="audit", cascade="all, delete-orphan")
def __repr__(self):
return (
f"<ImportAudit(id={self.id}, status='{self.status}', files={self.successful_files}/{self.total_files}, "
f"imported={self.total_imported}, errors={self.total_errors})>"
)
class ImportAuditFile(BaseModel):
"""Per-file result for a given batch import run."""
__tablename__ = "import_audit_files"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
audit_id = Column(Integer, ForeignKey("import_audit.id", ondelete="CASCADE"), nullable=False, index=True)
file_type = Column(String(64), nullable=False, index=True)
status = Column(String(30), nullable=False, index=True)
imported_count = Column(Integer, nullable=False, default=0)
errors = Column(Integer, nullable=False, default=0)
message = Column(String(255), nullable=True)
details = Column(JSON, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
audit = relationship("ImportAudit", back_populates="files")
def __repr__(self):
return f"<ImportAuditFile(audit_id={self.audit_id}, file='{self.file_type}', status='{self.status}', imported={self.imported_count}, errors={self.errors})>"