"""One Watch later list per user, held by the database Revision ID: 0112_one_watch_later Revises: 0111_audit_request_id Create Date: 2026-10-20 Watch later was made on a user's first save by a read then an insert, so two first saves at once each made a list, and the reader then picked between them at random. A partial unique index holds "one per user" now. An instance that already has a second list keeps the oldest: the others' videos move into it (after its own, in their order; one it already holds is not added twice), then they go. """ import sqlalchemy as sa from alembic import op revision: str = "0112_one_watch_later" down_revision: str | None = "0111_audit_request_id" branch_labels: str | None = None depends_on: str | None = None _EXTRA = """ SELECT p.id, k.keep_id FROM playlists p JOIN ( SELECT user_id, min(id) AS keep_id FROM playlists WHERE kind = 'watch_later' GROUP BY user_id ) k ON k.user_id = p.user_id WHERE p.kind = 'watch_later' AND p.id <> k.keep_id """ def upgrade() -> None: op.execute( f""" INSERT INTO playlist_items (playlist_id, video_id, position, added_at) SELECT e.keep_id, i.video_id, (SELECT coalesce(max(position), -1) FROM playlist_items WHERE playlist_id = e.keep_id) + row_number() OVER (PARTITION BY e.keep_id ORDER BY i.playlist_id, i.position, i.id), i.added_at FROM playlist_items i JOIN ({_EXTRA}) e ON e.id = i.playlist_id ON CONFLICT ON CONSTRAINT uq_playlist_video DO NOTHING """ ) op.execute(f"DELETE FROM playlists WHERE id IN (SELECT id FROM ({_EXTRA}) e)") op.create_index( "uq_playlist_watch_later", "playlists", ["user_id"], unique=True, postgresql_where=sa.text("kind = 'watch_later'"), ) def downgrade() -> None: op.drop_index("uq_playlist_watch_later", table_name="playlists")