Files
NixReaperandClaude Sonnet 5 6c95e18aad
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
Add watch providers (where to stream/rent/buy)
New TMDb integration end to end: append_to_response "watch/providers",
normalizer parsing grouped by country/monetization type, a WatchProvider
catalog entry (external-id keyed, same get_or_create pattern as Company/
Collection/Person) + polymorphic WatchProviderOffering table (new
migration 0002), ingestion write (including provider logos via the
existing Artwork table), and API exposure -- embedded on movie/series
detail plus a dedicated /watch-providers sub-route, shaped to mirror
TMDb's own response for easy porting.

Note: this data comes from JustWatch via TMDb and carries its own
attribution requirement separate from TMDb's general one -- flagged in
docs/api.md, must be satisfied before showing this in the frontend.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-06 04:59:02 -05:00

62 lines
2.0 KiB
Python

"""watch providers
Revision ID: 0002_watch_providers
Revises: 0001_initial_schema
Create Date: 2026-09-06
Adds the where-to-watch tables (section 17, additive): a WatchProvider
catalog entry (Netflix, Apple TV, etc. -- TMDb's own provider_id links in
via the existing external_ids table, same pattern as Company/Collection/
Person) and WatchProviderOffering, a polymorphic (entity_type, entity_id)
table like release_dates, one row per (movie/series, country,
monetization type, provider).
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "0002_watch_providers"
down_revision: str | None = "0001_initial_schema"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
"watch_providers",
sa.Column("id", sa.Uuid(), primary_key=True),
sa.Column("name", sa.String(200), nullable=False),
)
op.create_table(
"watch_provider_offerings",
sa.Column("id", sa.Uuid(), primary_key=True),
sa.Column("entity_type", sa.String(30), nullable=False),
sa.Column("entity_id", sa.Uuid(), nullable=False),
sa.Column("country_code", sa.String(2), sa.ForeignKey("countries.code"), nullable=False),
sa.Column("monetization_type", sa.String(20), nullable=False),
sa.Column(
"watch_provider_id",
sa.Uuid(),
sa.ForeignKey("watch_providers.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("display_priority", sa.Integer(), nullable=True),
sa.Column("link", sa.Text(), nullable=True),
)
op.create_index(
"ix_watch_provider_offerings_entity",
"watch_provider_offerings",
["entity_type", "entity_id"],
)
def downgrade() -> None:
op.drop_index("ix_watch_provider_offerings_entity", table_name="watch_provider_offerings")
op.drop_table("watch_provider_offerings")
op.drop_table("watch_providers")