"""activity sources catalog Revision ID: 0003_activity_sources Revises: 0002_active_workout_flow Create Date: 2026-05-29 14:50:00.000000 """ from collections.abc import Sequence import sqlalchemy as sa from alembic import op from sqlalchemy.dialects import postgresql revision: str = "0003_activity_sources" down_revision: str | None = "0002_active_workout_flow" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.create_table( "logic_activity_sources", sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), sa.Column("owner_user_id", postgresql.UUID(as_uuid=True), nullable=True), sa.Column("slug", sa.String(length=180), nullable=False), sa.Column("kind", sa.String(length=20), nullable=False), sa.Column("title", sa.String(length=160), nullable=False), sa.Column("description", sa.Text(), nullable=True), sa.Column("category", sa.String(length=32), nullable=False), sa.Column("equipment", sa.String(length=32), nullable=False), sa.Column("measurement_type", sa.String(length=32), nullable=False), sa.Column("difficulty", sa.String(length=20), nullable=False), sa.Column("image_s3_url", sa.Text(), nullable=True), sa.Column("image_s3_key", sa.Text(), nullable=True), sa.Column("is_builtin", sa.Boolean(), nullable=False), sa.Column("default_calories_per_minute", sa.Numeric(8, 2), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), sa.CheckConstraint("kind IN ('exercise', 'machine')", name="ck_activity_source_kind"), sa.CheckConstraint( "category IN ('chest', 'back', 'legs', 'shoulders', 'biceps', 'triceps', " "'core', 'cardio', 'full_body', 'other')", name="ck_activity_source_category", ), sa.CheckConstraint( "equipment IN ('barbell', 'dumbbell', 'machine', 'cable', 'bodyweight', " "'kettlebell', 'cardio_machine', 'other')", name="ck_activity_source_equipment", ), sa.CheckConstraint( "measurement_type IN ('weight_reps', 'reps_only', 'duration', " "'distance_duration', 'duration_calories')", name="ck_activity_source_measurement_type", ), sa.CheckConstraint( "difficulty IN ('beginner', 'intermediate', 'advanced')", name="ck_activity_source_difficulty", ), ) op.create_index("ix_logic_activity_sources_category", "logic_activity_sources", ["category"]) op.create_index("ix_logic_activity_sources_equipment", "logic_activity_sources", ["equipment"]) op.create_index( "ix_logic_activity_sources_is_builtin", "logic_activity_sources", ["is_builtin"] ) op.create_index("ix_logic_activity_sources_kind", "logic_activity_sources", ["kind"]) op.create_index( "ix_logic_activity_sources_owner_user_id", "logic_activity_sources", ["owner_user_id"], ) op.create_index( "ix_logic_activity_sources_slug", "logic_activity_sources", ["slug"], unique=True ) op.create_index("ix_logic_activity_sources_title", "logic_activity_sources", ["title"]) op.add_column( "logic_workout_items", sa.Column("activity_source_id", postgresql.UUID(as_uuid=True), nullable=True), ) op.create_foreign_key( "fk_logic_workout_items_activity_source_id", "logic_workout_items", "logic_activity_sources", ["activity_source_id"], ["id"], ) op.add_column( "logic_workout_items", sa.Column( "measurement_type_snapshot", sa.String(length=32), nullable=False, server_default="weight_reps", ), ) op.add_column( "logic_workout_items", sa.Column( "category_snapshot", sa.String(length=32), nullable=False, server_default="other" ), ) op.add_column( "logic_workout_items", sa.Column( "equipment_snapshot", sa.String(length=32), nullable=False, server_default="other" ), ) op.drop_constraint("ck_workout_item_exactly_one_entity", "logic_workout_items", type_="check") op.create_check_constraint( "ck_workout_item_exactly_one_entity", "logic_workout_items", "(CASE WHEN activity_source_id IS NOT NULL THEN 1 ELSE 0 END + " "CASE WHEN exercise_id IS NOT NULL THEN 1 ELSE 0 END + " "CASE WHEN equipment_id IS NOT NULL THEN 1 ELSE 0 END) = 1", ) op.drop_constraint("ck_workout_item_source_kind", "logic_workout_items", type_="check") op.create_check_constraint( "ck_workout_item_source_kind", "logic_workout_items", "source_kind IN ('exercise', 'machine', 'equipment')", ) op.add_column("logic_workout_sets", sa.Column("distance_km", sa.Numeric(8, 3), nullable=True)) def downgrade() -> None: op.drop_column("logic_workout_sets", "distance_km") op.drop_constraint("ck_workout_item_source_kind", "logic_workout_items", type_="check") op.create_check_constraint( "ck_workout_item_source_kind", "logic_workout_items", "source_kind IN ('exercise', 'equipment')", ) op.drop_constraint("ck_workout_item_exactly_one_entity", "logic_workout_items", type_="check") op.create_check_constraint( "ck_workout_item_exactly_one_entity", "logic_workout_items", "(exercise_id IS NOT NULL AND equipment_id IS NULL) OR " "(exercise_id IS NULL AND equipment_id IS NOT NULL)", ) op.drop_column("logic_workout_items", "equipment_snapshot") op.drop_column("logic_workout_items", "category_snapshot") op.drop_column("logic_workout_items", "measurement_type_snapshot") op.drop_constraint( "fk_logic_workout_items_activity_source_id", "logic_workout_items", type_="foreignkey" ) op.drop_column("logic_workout_items", "activity_source_id") op.drop_index("ix_logic_activity_sources_title", table_name="logic_activity_sources") op.drop_index("ix_logic_activity_sources_slug", table_name="logic_activity_sources") op.drop_index("ix_logic_activity_sources_owner_user_id", table_name="logic_activity_sources") op.drop_index("ix_logic_activity_sources_kind", table_name="logic_activity_sources") op.drop_index("ix_logic_activity_sources_is_builtin", table_name="logic_activity_sources") op.drop_index("ix_logic_activity_sources_equipment", table_name="logic_activity_sources") op.drop_index("ix_logic_activity_sources_category", table_name="logic_activity_sources") op.drop_table("logic_activity_sources")