Database stores times in UTC but serialized without timezone info, causing frontend to misinterpret as local time. Now all datetime fields include 'Z' suffix to indicate UTC, enabling proper timezone conversion in the browser. - Add UTCDatetimeBaseModel base class for Pydantic schemas - Update model to_dict() methods to append 'Z' suffix - Affects: tasks, users, sessions, audit logs, translations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
99 lines
3.1 KiB
Python
99 lines
3.1 KiB
Python
"""
|
|
Tool_OCR - Audit Log Model
|
|
Security audit logging for authentication and task operations
|
|
"""
|
|
|
|
from sqlalchemy import Column, Integer, String, DateTime, Text, ForeignKey
|
|
from sqlalchemy.orm import relationship
|
|
from datetime import datetime
|
|
|
|
from app.core.database import Base
|
|
|
|
|
|
class AuditLog(Base):
|
|
"""
|
|
Audit log model for security tracking
|
|
|
|
Records all important events including:
|
|
- Authentication events (login, logout, failures)
|
|
- Task operations (create, update, delete)
|
|
- Admin operations
|
|
"""
|
|
|
|
__tablename__ = "tool_ocr_audit_logs"
|
|
|
|
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
|
user_id = Column(
|
|
Integer,
|
|
ForeignKey("tool_ocr_users.id", ondelete="SET NULL"),
|
|
nullable=True,
|
|
index=True,
|
|
comment="User who performed the action (NULL for system events)"
|
|
)
|
|
event_type = Column(
|
|
String(50),
|
|
nullable=False,
|
|
index=True,
|
|
comment="Event type: auth_login, auth_logout, auth_failed, task_create, etc."
|
|
)
|
|
event_category = Column(
|
|
String(20),
|
|
nullable=False,
|
|
index=True,
|
|
comment="Category: authentication, task, admin, system"
|
|
)
|
|
description = Column(
|
|
Text,
|
|
nullable=False,
|
|
comment="Human-readable event description"
|
|
)
|
|
ip_address = Column(String(45), nullable=True, comment="Client IP address (IPv4/IPv6)")
|
|
user_agent = Column(String(500), nullable=True, comment="Client user agent")
|
|
resource_type = Column(
|
|
String(50),
|
|
nullable=True,
|
|
comment="Type of resource affected (task, user, session)"
|
|
)
|
|
resource_id = Column(
|
|
String(255),
|
|
nullable=True,
|
|
index=True,
|
|
comment="ID of affected resource"
|
|
)
|
|
success = Column(
|
|
Integer,
|
|
default=1,
|
|
nullable=False,
|
|
comment="1 for success, 0 for failure"
|
|
)
|
|
error_message = Column(Text, nullable=True, comment="Error details if failed")
|
|
extra_data = Column(Text, nullable=True, comment="Additional JSON metadata")
|
|
created_at = 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}, type='{self.event_type}', user_id={self.user_id})>"
|
|
|
|
def to_dict(self):
|
|
"""Convert audit log to dictionary.
|
|
|
|
All datetime fields are serialized with 'Z' suffix to indicate UTC timezone.
|
|
"""
|
|
return {
|
|
"id": self.id,
|
|
"user_id": self.user_id,
|
|
"event_type": self.event_type,
|
|
"event_category": self.event_category,
|
|
"description": self.description,
|
|
"ip_address": self.ip_address,
|
|
"user_agent": self.user_agent,
|
|
"resource_type": self.resource_type,
|
|
"resource_id": self.resource_id,
|
|
"success": bool(self.success),
|
|
"error_message": self.error_message,
|
|
"extra_data": self.extra_data,
|
|
"created_at": self.created_at.isoformat() + 'Z' if self.created_at else None
|
|
}
|