"""Add subscriptions table and link sources Revision ID: 002 Revises: 001 Create Date: 2025-01-25 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql revision: str = '002' down_revision: Union[str, None] = '001' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # Create subscriptions table op.create_table( 'subscriptions', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(255), nullable=False), sa.Column('enabled', sa.Boolean(), default=True), sa.Column('priority', sa.Integer(), default=0), sa.Column('description', sa.Text(), nullable=True), sa.Column('metadata', postgresql.JSONB(), default={}), sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()), sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now(), onupdate=sa.func.now()), sa.PrimaryKeyConstraint('id') ) op.create_index('ix_subscriptions_name', 'subscriptions', ['name'], unique=True) # Migrate existing sources to subscriptions # Each unique source name becomes a subscription conn = op.get_bind() # Get existing sources existing_sources = conn.execute( sa.text("SELECT DISTINCT name FROM sources ORDER BY name") ).fetchall() # Create subscriptions for each unique source name for (name,) in existing_sources: conn.execute( sa.text("INSERT INTO subscriptions (name, enabled, priority) VALUES (:name, true, 0)"), {"name": name} ) # Add subscription_id column to sources op.add_column('sources', sa.Column('subscription_id', sa.Integer(), nullable=True)) op.create_index('ix_sources_subscription_id', 'sources', ['subscription_id']) # Update sources with their subscription_id conn.execute(sa.text(""" UPDATE sources s SET subscription_id = sub.id FROM subscriptions sub WHERE s.name = sub.name """)) # Make subscription_id NOT NULL after data migration op.alter_column('sources', 'subscription_id', nullable=False) # Add foreign key constraint op.create_foreign_key( 'fk_sources_subscription_id', 'sources', 'subscriptions', ['subscription_id'], ['id'], ondelete='CASCADE' ) # Remove name and priority columns from sources (now on subscription) op.drop_column('sources', 'name') op.drop_column('sources', 'priority') def downgrade() -> None: # Add back name and priority columns to sources op.add_column('sources', sa.Column('name', sa.String(255), nullable=True)) op.add_column('sources', sa.Column('priority', sa.Integer(), default=0)) # Populate name from subscription conn = op.get_bind() conn.execute(sa.text(""" UPDATE sources s SET name = sub.name, priority = sub.priority FROM subscriptions sub WHERE s.subscription_id = sub.id """)) op.alter_column('sources', 'name', nullable=False) # Remove foreign key and subscription_id column op.drop_constraint('fk_sources_subscription_id', 'sources', type_='foreignkey') op.drop_index('ix_sources_subscription_id', 'sources') op.drop_column('sources', 'subscription_id') # Drop subscriptions table op.drop_index('ix_subscriptions_name', 'subscriptions') op.drop_table('subscriptions')