announcement.py 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. """Announcements from the Bambuddy maintainers, and who has read them.
  2. Fetched from a signed feed on GitHub (services/announcements.py). Only the ones
  3. that target this install are stored, and the feed is authoritative: a message
  4. withdrawn upstream is deleted here on the next fetch, together with its read
  5. markers.
  6. ``AnnouncementRead.user_id`` is nullable: with authentication off there are no
  7. users, and the install's read state is a single NULL-keyed row per message.
  8. """
  9. from __future__ import annotations
  10. from datetime import datetime
  11. from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func
  12. from sqlalchemy.orm import Mapped, mapped_column
  13. from backend.app.core.database import Base
  14. class Announcement(Base):
  15. __tablename__ = "announcements"
  16. id: Mapped[int] = mapped_column(primary_key=True)
  17. # The feed's id for the message. Stable across edits upstream, so a corrected
  18. # typo does not come back as unread.
  19. public_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
  20. level: Mapped[str] = mapped_column(String(16)) # info | important | critical
  21. # JSON {"en": {"title", "body", "link_label"?}, ...}. Text for SQLite/Postgres
  22. # uniformity; the service serialises with json.dumps.
  23. texts: Mapped[str] = mapped_column(Text)
  24. link_url: Mapped[str | None] = mapped_column(String(500), nullable=True)
  25. # Naive UTC, like every other timestamp in Bambuddy.
  26. published_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  27. expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  28. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  29. class AnnouncementRead(Base):
  30. __tablename__ = "announcement_reads"
  31. __table_args__ = (UniqueConstraint("announcement_id", "user_id", name="uq_announcement_reads_user"),)
  32. id: Mapped[int] = mapped_column(primary_key=True)
  33. announcement_id: Mapped[int] = mapped_column(
  34. Integer, ForeignKey("announcements.id", ondelete="CASCADE"), index=True
  35. )
  36. user_id: Mapped[int | None] = mapped_column(
  37. Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True, index=True
  38. )
  39. read_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())