Files
Aurora-Core/migrations/versions/0001_initial_schema.py
T
NixReaperandClaude Sonnet 5 c20df1c503
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
Initial Aurora Core 0.1 implementation
Production-grade media metadata backend per docs/aurora-core-spec.md:
Aurora-owned UUIDv7 identity system, normalized movie/TV/people/credits
schema, provider abstraction with a TMDb adapter, an idempotent async
ingestion pipeline (normalize -> match -> upsert -> provenance), a
Postgres FTS/trigram search layer, a stateless FastAPI v1 API with
Redis caching/rate-limiting, arq workers, dev/prod Docker Compose
topologies, unit + integration + load tests, CI, and architecture/
database/API/ERD documentation.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-04 17:34:03 -05:00

814 lines
29 KiB
Python

"""initial schema
Revision ID: 0001_initial_schema
Revises:
Create Date: 2026-09-04
Creates every Aurora Core 0.1 table (section 26/33 of the build spec):
reference data (languages/countries/certifications), providers and
provenance, genres/keywords, companies, collections, people, movies and
their satellite tables, series/seasons/episodes and their satellite tables,
and the polymorphic credit/artwork/video/external-id tables. Also enables
`pg_trgm` and creates the functional GIN indexes PostgreSQLSearchProvider
relies on (section 16).
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
revision: str = "0001_initial_schema"
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _timestamps() -> list[sa.Column]:
return [
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
]
MOVIE_STATUS = postgresql.ENUM(
"rumored",
"planned",
"in_production",
"post_production",
"released",
"canceled",
name="moviestatus_enum",
)
SERIES_STATUS = postgresql.ENUM(
"planned",
"pilot",
"in_production",
"returning_series",
"ended",
"canceled",
name="seriesstatus_enum",
)
ARTWORK_TYPE = postgresql.ENUM(
"poster", "backdrop", "logo", "profile", "still", "banner", "thumbnail",
name="artworktype_enum",
)
VIDEO_TYPE = postgresql.ENUM(
"trailer", "teaser", "clip", "featurette", "behind_the_scenes", "interview", "other",
name="videotype_enum",
)
COMPANY_TYPE = postgresql.ENUM(
"production_company", "network", "studio", "distributor", name="companytype_enum"
)
INGESTION_JOB_STATUS = postgresql.ENUM(
"pending", "running", "succeeded", "failed", "retrying", name="ingestionjobstatus_enum"
)
MATCH_CONFIDENCE = postgresql.ENUM(
"exact", "high", "medium", "low", "ambiguous", "new", name="matchconfidence_enum"
)
def upgrade() -> None:
bind = op.get_bind()
op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm")
for enum_type in (
MOVIE_STATUS,
SERIES_STATUS,
ARTWORK_TYPE,
VIDEO_TYPE,
COMPANY_TYPE,
INGESTION_JOB_STATUS,
MATCH_CONFIDENCE,
):
enum_type.create(bind, checkfirst=True)
# --- Reference data -----------------------------------------------
op.create_table(
"languages",
sa.Column("code", sa.String(10), primary_key=True),
sa.Column("english_name", sa.String(200), nullable=False),
sa.Column("native_name", sa.String(200), nullable=True),
)
op.create_table(
"countries",
sa.Column("code", sa.String(2), primary_key=True),
sa.Column("name", sa.String(200), nullable=False),
)
op.create_table(
"certifications",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column("country_code", sa.String(2), sa.ForeignKey("countries.code"), nullable=False),
sa.Column("system", sa.String(50), nullable=False),
sa.Column("rating", sa.String(20), nullable=False),
sa.Column("meaning", sa.Text(), nullable=True),
sa.UniqueConstraint("country_code", "system", "rating", name="uq_certification_scope"),
)
# --- Providers & provenance -----------------------------------------
op.create_table(
"providers",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column("slug", sa.String(50), nullable=False, unique=True),
sa.Column("name", sa.String(200), nullable=False),
sa.Column("base_url", sa.String(500), nullable=True),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column(
"config", postgresql.JSONB(astext_type=sa.Text()), nullable=False, server_default="{}"
),
)
op.create_table(
"ingestion_jobs",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column(
"provider_id",
sa.Uuid(),
sa.ForeignKey("providers.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("job_type", sa.String(50), nullable=False),
sa.Column("entity_type", sa.String(30), nullable=True),
sa.Column("external_id", sa.String(200), nullable=True),
sa.Column("idempotency_key", sa.String(300), nullable=False),
sa.Column("status", INGESTION_JOB_STATUS, nullable=False, server_default="pending"),
sa.Column("attempts", sa.Integer(), nullable=False, server_default="0"),
sa.Column("max_attempts", sa.Integer(), nullable=False, server_default="5"),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("error", sa.Text(), nullable=True),
sa.Column("result_aurora_id", sa.Uuid(), nullable=True),
sa.UniqueConstraint("idempotency_key", name="uq_ingestion_job_idempotency"),
)
op.create_table(
"provider_records",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column(
"provider_id",
sa.Uuid(),
sa.ForeignKey("providers.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("entity_type", sa.String(30), nullable=False),
sa.Column("external_id", sa.String(200), nullable=False),
sa.Column("aurora_entity_id", sa.Uuid(), nullable=True),
sa.Column(
"raw_data", postgresql.JSONB(astext_type=sa.Text()), nullable=False, server_default="{}"
),
sa.Column("source_version", sa.String(100), nullable=True),
sa.Column("match_confidence", MATCH_CONFIDENCE, nullable=False, server_default="new"),
sa.Column("match_notes", sa.Text(), nullable=True),
sa.Column(
"ingestion_job_id",
sa.Uuid(),
sa.ForeignKey("ingestion_jobs.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column(
"last_synced_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.UniqueConstraint(
"provider_id", "entity_type", "external_id", name="uq_provider_record_source"
),
)
# --- Genres / keywords ----------------------------------------------
op.create_table(
"genres",
sa.Column("id", sa.Uuid(), primary_key=True),
sa.Column("name", sa.String(100), nullable=False, unique=True),
sa.Column("slug", sa.String(100), nullable=False, unique=True),
)
op.create_table(
"keywords",
sa.Column("id", sa.Uuid(), primary_key=True),
sa.Column("name", sa.String(150), nullable=False, unique=True),
sa.Column("slug", sa.String(150), nullable=False, unique=True),
)
# --- Companies --------------------------------------------------------
op.create_table(
"companies",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column("name", sa.String(300), nullable=False),
sa.Column(
"company_type", COMPANY_TYPE, nullable=False, server_default="production_company"
),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("country_code", sa.String(2), sa.ForeignKey("countries.code"), nullable=True),
sa.Column("headquarters", sa.String(300), nullable=True),
sa.Column("homepage", sa.String(500), nullable=True),
sa.Column(
"parent_company_id",
sa.Uuid(),
sa.ForeignKey("companies.id", ondelete="SET NULL"),
nullable=True,
),
)
op.create_index("ix_companies_name", "companies", ["name"])
# --- Collections --------------------------------------------------------
op.create_table(
"collections",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column("name", sa.String(300), nullable=False),
sa.Column("overview", sa.Text(), nullable=True),
sa.Column("slug", sa.String(350), nullable=False, unique=True),
)
op.create_index("ix_collections_name", "collections", ["name"])
# --- People -----------------------------------------------------------
op.create_table(
"people",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column("name", sa.String(300), nullable=False),
sa.Column(
"aliases", postgresql.JSONB(astext_type=sa.Text()), nullable=False, server_default="[]"
),
sa.Column("biography", sa.Text(), nullable=True),
sa.Column("birthday", sa.Date(), nullable=True),
sa.Column("deathday", sa.Date(), nullable=True),
sa.Column("birthplace", sa.String(300), nullable=True),
sa.Column("gender", sa.String(30), nullable=True),
sa.Column("known_for_department", sa.String(100), nullable=True),
sa.Column("popularity", sa.Float(), nullable=True),
)
op.create_index("ix_people_name", "people", ["name"])
# --- Movies -------------------------------------------------------------
op.create_table(
"movies",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column("title", sa.String(500), nullable=False),
sa.Column("original_title", sa.String(500), nullable=True),
sa.Column("sort_title", sa.String(500), nullable=False),
sa.Column("overview", sa.Text(), nullable=True),
sa.Column("tagline", sa.Text(), nullable=True),
sa.Column("status", MOVIE_STATUS, nullable=False, server_default="released"),
sa.Column(
"original_language_code", sa.String(10), sa.ForeignKey("languages.code"), nullable=True
),
sa.Column("primary_release_date", sa.Date(), nullable=True),
sa.Column("runtime_minutes", sa.Integer(), nullable=True),
sa.Column("budget", sa.BigInteger(), nullable=True),
sa.Column("revenue", sa.BigInteger(), nullable=True),
sa.Column("popularity", sa.Float(), nullable=True),
sa.Column("vote_average", sa.Float(), nullable=True),
sa.Column("vote_count", sa.Integer(), nullable=True),
sa.Column("adult", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column(
"collection_id",
sa.Uuid(),
sa.ForeignKey("collections.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column(
"source_provider_id",
sa.Uuid(),
sa.ForeignKey("providers.id", ondelete="SET NULL"),
nullable=True,
),
)
op.create_index("ix_movies_title", "movies", ["title"])
op.create_index("ix_movies_sort_title", "movies", ["sort_title"])
op.create_index("ix_movies_primary_release_date", "movies", ["primary_release_date"])
op.create_table(
"movie_alternate_titles",
sa.Column("id", sa.Uuid(), primary_key=True),
sa.Column(
"movie_id", sa.Uuid(), sa.ForeignKey("movies.id", ondelete="CASCADE"), nullable=False
),
sa.Column("title", sa.String(500), nullable=False),
sa.Column("country_code", sa.String(2), sa.ForeignKey("countries.code"), nullable=True),
sa.Column("type", sa.String(50), nullable=True),
)
op.create_index(
"ix_movie_alternate_titles_movie_id", "movie_alternate_titles", ["movie_id"]
)
op.create_table(
"movie_spoken_languages",
sa.Column(
"movie_id",
sa.Uuid(),
sa.ForeignKey("movies.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column(
"language_code",
sa.String(10),
sa.ForeignKey("languages.code"),
primary_key=True,
),
)
op.create_table(
"movie_production_countries",
sa.Column(
"movie_id",
sa.Uuid(),
sa.ForeignKey("movies.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column(
"country_code",
sa.String(2),
sa.ForeignKey("countries.code"),
primary_key=True,
),
)
op.create_table(
"release_dates",
sa.Column("id", sa.Uuid(), primary_key=True),
sa.Column("entity_type", sa.String(30), nullable=False, server_default="movie"),
sa.Column("entity_id", sa.Uuid(), nullable=False),
sa.Column("country_code", sa.String(2), sa.ForeignKey("countries.code"), nullable=False),
sa.Column("release_date", sa.Date(), nullable=False),
sa.Column("release_type", sa.String(50), nullable=True),
sa.Column(
"certification_id",
sa.Uuid(),
sa.ForeignKey("certifications.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column("note", sa.Text(), nullable=True),
)
op.create_index("ix_release_dates_entity_id", "release_dates", ["entity_id"])
op.create_table(
"movie_recommendations",
sa.Column(
"movie_id",
sa.Uuid(),
sa.ForeignKey("movies.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column(
"recommended_movie_id",
sa.Uuid(),
sa.ForeignKey("movies.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column("rank", sa.Integer(), nullable=True),
sa.Column("source", sa.String(50), nullable=True),
)
op.create_table(
"movie_genres",
sa.Column(
"movie_id", sa.Uuid(), sa.ForeignKey("movies.id", ondelete="CASCADE"), primary_key=True
),
sa.Column(
"genre_id", sa.Uuid(), sa.ForeignKey("genres.id", ondelete="CASCADE"), primary_key=True
),
)
op.create_table(
"movie_keywords",
sa.Column(
"movie_id", sa.Uuid(), sa.ForeignKey("movies.id", ondelete="CASCADE"), primary_key=True
),
sa.Column(
"keyword_id",
sa.Uuid(),
sa.ForeignKey("keywords.id", ondelete="CASCADE"),
primary_key=True,
),
)
op.create_table(
"movie_companies",
sa.Column(
"movie_id", sa.Uuid(), sa.ForeignKey("movies.id", ondelete="CASCADE"), primary_key=True
),
sa.Column(
"company_id",
sa.Uuid(),
sa.ForeignKey("companies.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column("role", sa.String(50), nullable=True),
)
# --- Series / Seasons / Episodes ---------------------------------------
op.create_table(
"series",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column("title", sa.String(500), nullable=False),
sa.Column("original_title", sa.String(500), nullable=True),
sa.Column("sort_title", sa.String(500), nullable=False),
sa.Column("overview", sa.Text(), nullable=True),
sa.Column("tagline", sa.Text(), nullable=True),
sa.Column("status", SERIES_STATUS, nullable=False, server_default="returning_series"),
sa.Column(
"original_language_code", sa.String(10), sa.ForeignKey("languages.code"), nullable=True
),
sa.Column("first_air_date", sa.Date(), nullable=True),
sa.Column("last_air_date", sa.Date(), nullable=True),
sa.Column("number_of_seasons", sa.Integer(), nullable=True),
sa.Column("number_of_episodes", sa.Integer(), nullable=True),
sa.Column("popularity", sa.Float(), nullable=True),
sa.Column("vote_average", sa.Float(), nullable=True),
sa.Column("vote_count", sa.Integer(), nullable=True),
sa.Column(
"source_provider_id",
sa.Uuid(),
sa.ForeignKey("providers.id", ondelete="SET NULL"),
nullable=True,
),
)
op.create_index("ix_series_title", "series", ["title"])
op.create_index("ix_series_sort_title", "series", ["sort_title"])
op.create_index("ix_series_first_air_date", "series", ["first_air_date"])
op.create_table(
"series_alternate_titles",
sa.Column("id", sa.Uuid(), primary_key=True),
sa.Column(
"series_id", sa.Uuid(), sa.ForeignKey("series.id", ondelete="CASCADE"), nullable=False
),
sa.Column("title", sa.String(500), nullable=False),
sa.Column("country_code", sa.String(2), sa.ForeignKey("countries.code"), nullable=True),
sa.Column("type", sa.String(50), nullable=True),
)
op.create_index(
"ix_series_alternate_titles_series_id", "series_alternate_titles", ["series_id"]
)
op.create_table(
"series_spoken_languages",
sa.Column(
"series_id",
sa.Uuid(),
sa.ForeignKey("series.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column(
"language_code",
sa.String(10),
sa.ForeignKey("languages.code"),
primary_key=True,
),
)
op.create_table(
"series_production_countries",
sa.Column(
"series_id",
sa.Uuid(),
sa.ForeignKey("series.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column(
"country_code",
sa.String(2),
sa.ForeignKey("countries.code"),
primary_key=True,
),
)
op.create_table(
"seasons",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column(
"series_id", sa.Uuid(), sa.ForeignKey("series.id", ondelete="CASCADE"), nullable=False
),
sa.Column("season_number", sa.Integer(), nullable=False),
sa.Column("title", sa.String(500), nullable=True),
sa.Column("overview", sa.Text(), nullable=True),
sa.Column("air_date", sa.Date(), nullable=True),
sa.Column("episode_count", sa.Integer(), nullable=True),
sa.UniqueConstraint("series_id", "season_number", name="uq_season_number"),
)
op.create_index("ix_seasons_series_id", "seasons", ["series_id"])
op.create_table(
"episodes",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column(
"season_id", sa.Uuid(), sa.ForeignKey("seasons.id", ondelete="CASCADE"), nullable=False
),
sa.Column(
"series_id", sa.Uuid(), sa.ForeignKey("series.id", ondelete="CASCADE"), nullable=False
),
sa.Column("season_number", sa.Integer(), nullable=False),
sa.Column("episode_number", sa.Integer(), nullable=False),
sa.Column("title", sa.String(500), nullable=True),
sa.Column("overview", sa.Text(), nullable=True),
sa.Column("air_date", sa.Date(), nullable=True),
sa.Column("runtime_minutes", sa.Integer(), nullable=True),
sa.UniqueConstraint("season_id", "episode_number", name="uq_episode_number"),
)
op.create_index("ix_episodes_season_id", "episodes", ["season_id"])
op.create_index("ix_episodes_series_id", "episodes", ["series_id"])
op.create_table(
"series_genres",
sa.Column(
"series_id", sa.Uuid(), sa.ForeignKey("series.id", ondelete="CASCADE"), primary_key=True
),
sa.Column(
"genre_id", sa.Uuid(), sa.ForeignKey("genres.id", ondelete="CASCADE"), primary_key=True
),
)
op.create_table(
"series_keywords",
sa.Column(
"series_id", sa.Uuid(), sa.ForeignKey("series.id", ondelete="CASCADE"), primary_key=True
),
sa.Column(
"keyword_id",
sa.Uuid(),
sa.ForeignKey("keywords.id", ondelete="CASCADE"),
primary_key=True,
),
)
op.create_table(
"series_companies",
sa.Column(
"series_id", sa.Uuid(), sa.ForeignKey("series.id", ondelete="CASCADE"), primary_key=True
),
sa.Column(
"company_id",
sa.Uuid(),
sa.ForeignKey("companies.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column("role", sa.String(50), nullable=True),
)
# --- Collection memberships ---------------------------------------------
op.create_table(
"collection_movies",
sa.Column(
"collection_id",
sa.Uuid(),
sa.ForeignKey("collections.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column(
"movie_id", sa.Uuid(), sa.ForeignKey("movies.id", ondelete="CASCADE"), primary_key=True
),
sa.Column("sort_order", sa.Integer(), nullable=True),
)
op.create_table(
"collection_series",
sa.Column(
"collection_id",
sa.Uuid(),
sa.ForeignKey("collections.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column(
"series_id", sa.Uuid(), sa.ForeignKey("series.id", ondelete="CASCADE"), primary_key=True
),
sa.Column("sort_order", sa.Integer(), nullable=True),
)
op.create_table(
"collection_relations",
sa.Column(
"collection_id",
sa.Uuid(),
sa.ForeignKey("collections.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column(
"related_collection_id",
sa.Uuid(),
sa.ForeignKey("collections.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column("relation_type", sa.String(50), nullable=False, server_default="related"),
)
# --- Polymorphic satellite tables ---------------------------------------
op.create_table(
"credits",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column(
"person_id", sa.Uuid(), sa.ForeignKey("people.id", ondelete="CASCADE"), nullable=False
),
sa.Column("entity_type", sa.String(30), nullable=False),
sa.Column("entity_id", sa.Uuid(), nullable=False),
sa.Column("department", sa.String(100), nullable=False),
sa.Column("job", sa.String(150), nullable=True),
sa.Column("character", sa.String(300), nullable=True),
sa.Column("billing_order", sa.Integer(), nullable=True),
sa.Column("is_guest", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("episode_count", sa.Integer(), nullable=True),
sa.Column(
"provider_id",
sa.Uuid(),
sa.ForeignKey("providers.id", ondelete="SET NULL"),
nullable=True,
),
)
op.create_index("ix_credits_entity", "credits", ["entity_type", "entity_id"])
op.create_index("ix_credits_person_id", "credits", ["person_id"])
op.create_table(
"artwork",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column("entity_type", sa.String(30), nullable=False),
sa.Column("entity_id", sa.Uuid(), nullable=False),
sa.Column("artwork_type", ARTWORK_TYPE, nullable=False),
sa.Column("language_code", sa.String(10), sa.ForeignKey("languages.code"), nullable=True),
sa.Column("width", sa.Integer(), nullable=True),
sa.Column("height", sa.Integer(), nullable=True),
sa.Column("aspect_ratio", sa.Float(), nullable=True),
sa.Column("vote_average", sa.Float(), nullable=True),
sa.Column("is_primary", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column(
"provider_id",
sa.Uuid(),
sa.ForeignKey("providers.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("provider_asset_id", sa.String(200), nullable=True),
sa.Column("original_url", sa.String(1000), nullable=False),
sa.Column("local_path", sa.String(1000), nullable=True),
sa.Column("checksum", sa.String(128), nullable=True),
sa.Column("attribution", sa.Text(), nullable=True),
sa.Column("license", sa.Text(), nullable=True),
)
op.create_index(
"ix_artwork_entity", "artwork", ["entity_type", "entity_id", "artwork_type"]
)
op.create_table(
"videos",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column("entity_type", sa.String(30), nullable=False),
sa.Column("entity_id", sa.Uuid(), nullable=False),
sa.Column("video_type", VIDEO_TYPE, nullable=False),
sa.Column(
"provider_id",
sa.Uuid(),
sa.ForeignKey("providers.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("provider_video_id", sa.String(200), nullable=False),
sa.Column("site", sa.String(100), nullable=True),
sa.Column("url", sa.String(1000), nullable=False),
sa.Column("language_code", sa.String(10), sa.ForeignKey("languages.code"), nullable=True),
sa.Column("country_code", sa.String(2), sa.ForeignKey("countries.code"), nullable=True),
sa.Column("is_official", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("published_at", sa.Date(), nullable=True),
sa.UniqueConstraint(
"provider_id", "provider_video_id", name="uq_video_provider_source"
),
)
op.create_index("ix_videos_entity", "videos", ["entity_type", "entity_id"])
op.create_table(
"external_ids",
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.Column("entity_type", sa.String(30), nullable=False),
sa.Column("entity_id", sa.Uuid(), nullable=False),
sa.Column(
"provider_id",
sa.Uuid(),
sa.ForeignKey("providers.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("external_id", sa.String(200), nullable=False),
sa.Column("url", sa.String(500), nullable=True),
sa.UniqueConstraint(
"provider_id", "entity_type", "external_id", name="uq_external_id_source"
),
sa.UniqueConstraint(
"entity_type",
"entity_id",
"provider_id",
name="uq_external_id_entity_provider",
),
)
# --- Search indexes (section 16) ----------------------------------------
op.execute(
"CREATE INDEX ix_movies_title_trgm ON movies USING gin (title gin_trgm_ops)"
)
op.execute(
"CREATE INDEX ix_movies_title_fts ON movies "
"USING gin (to_tsvector('simple', coalesce(title, '') || ' ' || coalesce(original_title, '')))"
)
op.execute(
"CREATE INDEX ix_movie_alt_titles_trgm ON movie_alternate_titles "
"USING gin (title gin_trgm_ops)"
)
op.execute(
"CREATE INDEX ix_series_title_trgm ON series USING gin (title gin_trgm_ops)"
)
op.execute(
"CREATE INDEX ix_series_title_fts ON series "
"USING gin (to_tsvector('simple', coalesce(title, '') || ' ' || coalesce(original_title, '')))"
)
op.execute(
"CREATE INDEX ix_series_alt_titles_trgm ON series_alternate_titles "
"USING gin (title gin_trgm_ops)"
)
op.execute("CREATE INDEX ix_people_name_trgm ON people USING gin (name gin_trgm_ops)")
op.execute(
"CREATE INDEX ix_people_name_fts ON people USING gin (to_tsvector('simple', name))"
)
op.execute(
"CREATE INDEX ix_collections_name_trgm ON collections USING gin (name gin_trgm_ops)"
)
op.execute(
"CREATE INDEX ix_companies_name_trgm ON companies USING gin (name gin_trgm_ops)"
)
def downgrade() -> None:
op.drop_table("external_ids")
op.drop_table("videos")
op.drop_table("artwork")
op.drop_table("credits")
op.drop_table("collection_relations")
op.drop_table("collection_series")
op.drop_table("collection_movies")
op.drop_table("series_companies")
op.drop_table("series_keywords")
op.drop_table("series_genres")
op.drop_table("episodes")
op.drop_table("seasons")
op.drop_table("series_production_countries")
op.drop_table("series_spoken_languages")
op.drop_table("series_alternate_titles")
op.drop_table("series")
op.drop_table("movie_companies")
op.drop_table("movie_keywords")
op.drop_table("movie_genres")
op.drop_table("movie_recommendations")
op.drop_table("release_dates")
op.drop_table("movie_production_countries")
op.drop_table("movie_spoken_languages")
op.drop_table("movie_alternate_titles")
op.drop_table("movies")
op.drop_table("people")
op.drop_table("collections")
op.drop_table("companies")
op.drop_table("keywords")
op.drop_table("genres")
op.drop_table("provider_records")
op.drop_table("ingestion_jobs")
op.drop_table("providers")
op.drop_table("certifications")
op.drop_table("countries")
op.drop_table("languages")
bind = op.get_bind()
for enum_type in (
MATCH_CONFIDENCE,
INGESTION_JOB_STATUS,
COMPANY_TYPE,
VIDEO_TYPE,
ARTWORK_TYPE,
SERIES_STATUS,
MOVIE_STATUS,
):
enum_type.drop(bind, checkfirst=True)
op.execute("DROP EXTENSION IF EXISTS pg_trgm")