"""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""