feat(saved-views): scope saved views by module source (backend)

Saved "smart views" were feed-only; the Plex module is getting them too. Add a
`source` discriminator to saved_views ("feed" | "plex"), default "feed" so every
existing row and any source-less client stays feed. Widen the per-user unique
name constraint to (user_id, source, name) so a feed view and a Plex view can
share a name, and scope create's position numbering + the is_default "clear the
others" to (user, source).

Migration 0073 (ADD source NOT NULL server_default 'feed' + swap the unique
constraint; reversible). Endpoints: GET/POST take `source` (default feed).
Tests: source isolation, per-source default, per-source name uniqueness.
This commit is contained in:
2026-08-22 18:56:52 +02:00
parent f983a491c0
commit fa7a09a420
4 changed files with 149 additions and 14 deletions
@@ -0,0 +1,34 @@
"""saved_views.source (ask 4: per-module saved views)
Saved "smart views" were feed-only; the Plex module now has them too. Add a `source` discriminator
(default "feed" so every existing row stays a feed view) and widen the per-user unique name constraint
to (user_id, source, name) so a feed view and a Plex view can share a name. `source` is NOT NULL with a
server_default, so the ADD is a metadata-only rewrite-free change; the constraint swap is quick on a
small per-user table.
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
revision: str = "0073_saved_view_source"
down_revision: Union[str, None] = "0072_plex_person_bio"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column(
"saved_views",
sa.Column("source", sa.String(length=16), nullable=False, server_default="feed"),
)
op.drop_constraint("uq_user_saved_view", "saved_views", type_="unique")
op.create_unique_constraint(
"uq_user_source_saved_view", "saved_views", ["user_id", "source", "name"]
)
def downgrade() -> None:
op.drop_constraint("uq_user_source_saved_view", "saved_views", type_="unique")
op.create_unique_constraint("uq_user_saved_view", "saved_views", ["user_id", "name"])
op.drop_column("saved_views", "source")