feat(views): saved-views backend — table, model, CRUD + reorder API

Per-user named snapshots of the feed's FeedFilters. New saved_views table
(migration 0035, unique per user+name, position, is_default) + SavedView
model, and a /api/saved-views router (list/create/patch/delete/reorder)
gated behind require_human so the shared demo account can't pollute it.
Setting is_default clears the flag on the user's other views (one default).
This commit is contained in:
npeter83 2026-07-01 03:17:36 +02:00
parent 669994cc85
commit 3056734231
4 changed files with 235 additions and 0 deletions

View file

@ -593,6 +593,28 @@ class PlaylistItem(Base):
playlist: Mapped["Playlist"] = relationship(back_populates="items")
class SavedView(Base, TimestampMixin, UpdatedAtMixin):
"""A per-user named snapshot of the feed's filter/sort state (a "smart view").
`filters` is the serialized FeedFilters JSON the frontend applies with one click.
`position` orders them in the sidebar (drag-reorder); `is_default` marks the one view
applied automatically on load at most one per user, enforced by the API on set."""
__tablename__ = "saved_views"
__table_args__ = (UniqueConstraint("user_id", "name", name="uq_user_saved_view"),)
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), index=True
)
name: Mapped[str] = mapped_column(String(80))
filters: Mapped[dict] = mapped_column(JSON)
position: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
is_default: Mapped[bool] = mapped_column(
Boolean, default=False, server_default="false"
)
class Notification(Base):
"""A durable, server-backed per-user notification (the inbox center, phase 1).