"""Author RAG Chatbot SaaS — Link and Audit Log Models."""
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import Base, TimestampMixin, generate_uuid
class Link(Base, TimestampMixin):
"""Purchase and external links for a specific book."""
__tablename__ = "links"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
author_id: Mapped[str] = mapped_column(
String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
book_id: Mapped[str] = mapped_column(
String(36), ForeignKey("books.id", ondelete="CASCADE"), nullable=False, index=True
)
purchase_url: Mapped[str | None] = mapped_column(String(2000), nullable=True)
preview_url: Mapped[str | None] = mapped_column(String(2000), nullable=True)
sample_chapter_url: Mapped[str | None] = mapped_column(String(2000), nullable=True)
author_bio_url: Mapped[str | None] = mapped_column(String(2000), nullable=True)
newsletter_url: Mapped[str | None] = mapped_column(String(2000), nullable=True)
discount_code: Mapped[str | None] = mapped_column(String(50), nullable=True)
# URL health tracking
purchase_url_ok: Mapped[bool | None] = mapped_column(nullable=True)
preview_url_ok: Mapped[bool | None] = mapped_column(nullable=True)
last_health_check: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
# Relationships
book: Mapped["Book"] = relationship("Book", back_populates="links")
def __repr__(self) -> str:
return f""
class AuditLog(Base):
"""Immutable audit log — append-only, no updates or deletes ever."""
__tablename__ = "audit_logs"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
timestamp: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, index=True
)
actor_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
actor_email: Mapped[str] = mapped_column(String(255), nullable=False)
action: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
target_type: Mapped[str | None] = mapped_column(String(50), nullable=True)
target_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
details: Mapped[str | None] = mapped_column(Text, nullable=True) # JSON string
ip_address_hash: Mapped[str | None] = mapped_column(String(64), nullable=True) # Hashed
def __repr__(self) -> str:
return f""