Spaces:
Running
Running
| """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"<Link book={self.book_id} purchase={self.purchase_url}>" | |
| 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"<AuditLog actor={self.actor_email} action={self.action} at={self.timestamp}>" | |