Files
Aurora-Core/migrations/versions/0001_initial_schema.py
T
NixReaperandClaude Sonnet 5 5b38226818
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
Fix duplicate enum type creation in initial migration
Postgres ENUM columns auto-create their type as a side effect of the
first CREATE TABLE that uses them; since the migration also explicitly
pre-creates all 7 enum types up front (to group and control that DDL
alongside their explicit drop in downgrade()), the two collided with
"type already exists" on the first table past the first enum's owner.
create_type=False on each ENUM tells SQLAlchemy the migration manages
that DDL itself.

Found by running the migration against a real PostgreSQL for the first
time (docker compose up on a devbox) — this repo had no Docker/Postgres
available to test against when the migration was originally written.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-04 18:48:44 -05:00

826 lines
30 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,
),
]
# create_type=False on every enum below: their CREATE TYPE / DROP TYPE DDL
# is issued explicitly in upgrade()/downgrade() instead. Without this,
# SQLAlchemy also tries to auto-create each type as a side effect of the
# first `op.create_table()` that uses a column of that type, colliding with
# the explicit `enum_type.create(...)` call and raising DuplicateObject.
MOVIE_STATUS = postgresql.ENUM(
"rumored",
"planned",
"in_production",
"post_production",
"released",
"canceled",
name="moviestatus_enum",
create_type=False,
)
SERIES_STATUS = postgresql.ENUM(
"planned",
"pilot",
"in_production",
"returning_series",
"ended",
"canceled",
name="seriesstatus_enum",
create_type=False,
)
ARTWORK_TYPE = postgresql.ENUM(
"poster", "backdrop", "logo", "profile", "still", "banner", "thumbnail",
name="artworktype_enum",
create_type=False,
)
VIDEO_TYPE = postgresql.ENUM(
"trailer", "teaser", "clip", "featurette", "behind_the_scenes", "interview", "other",
name="videotype_enum",
create_type=False,
)
COMPANY_TYPE = postgresql.ENUM(
"production_company", "network", "studio", "distributor", name="companytype_enum",
create_type=False,
)
INGESTION_JOB_STATUS = postgresql.ENUM(
"pending", "running", "succeeded", "failed", "retrying", name="ingestionjobstatus_enum",
create_type=False,
)
MATCH_CONFIDENCE = postgresql.ENUM(
"exact", "high", "medium", "low", "ambiguous", "new", name="matchconfidence_enum",
create_type=False,
)
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")