70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from sqlalchemy import DateTime, ForeignKey, Index, String, Text, JSON, func
|
|
from sqlalchemy import Uuid
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.models.base import Base, UUIDMixin
|
|
|
|
|
|
class AuditLog(Base, UUIDMixin):
|
|
"""Immutable record of member activity and application errors."""
|
|
|
|
__tablename__ = "audit_logs"
|
|
__table_args__ = (
|
|
Index("ix_audit_logs_timestamp", "timestamp"),
|
|
Index("ix_audit_logs_member_id", "member_id"),
|
|
Index("ix_audit_logs_action_type", "action_type"),
|
|
Index("ix_audit_logs_status", "status"),
|
|
)
|
|
|
|
timestamp: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), nullable=False
|
|
)
|
|
# Nullable FK — SET NULL if member is deleted so the log is preserved.
|
|
member_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
|
Uuid(as_uuid=True),
|
|
ForeignKey("members.id", ondelete="SET NULL"),
|
|
nullable=True,
|
|
)
|
|
# Denormalised for readability after member deletion.
|
|
member_email: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
|
|
|
# One of: login, logout, page_visit, booking_created, booking_cancelled,
|
|
# profile_updated, onboarding_updated, contract_signed,
|
|
# account_claimed, message_read, error
|
|
action_type: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
|
|
# Identifies the page / feature area, e.g. "members/dashboard"
|
|
area: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
# Human-readable one-liner
|
|
description: Mapped[str] = mapped_column(String(500), nullable=False)
|
|
|
|
# success | warning | error
|
|
status: Mapped[str] = mapped_column(String(16), nullable=False, default="success")
|
|
|
|
# Optional related booking — SET NULL if booking is deleted.
|
|
booking_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
|
Uuid(as_uuid=True),
|
|
ForeignKey("bookings.id", ondelete="SET NULL"),
|
|
nullable=True,
|
|
)
|
|
|
|
# Error detail — populated for action_type='error' records.
|
|
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
|
error_detail: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
|
|
|
# Request metadata
|
|
ip_address: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
|
user_agent: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
|
|
|
|
# Catch-all JSON for any extra context (e.g. booking service_type)
|
|
extra: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
|
|
|
member: Mapped[Optional["Member"]] = relationship( # type: ignore[name-defined]
|
|
"Member", foreign_keys=[member_id]
|
|
)
|