Files
triviathang/migrations/versions/9a119272b516_add_question_ownership_and_sharing.py
Ryan Chen f1a05a68a8 Make migrations idempotent for production DB with pre-existing columns
Check for existing columns/tables before adding to avoid duplicate
column errors when production DB state is ahead of Alembic history.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 10:36:21 -04:00

85 lines
3.4 KiB
Python

"""Add question ownership and sharing
Revision ID: 9a119272b516
Revises: d2113a61fa42
Create Date: 2026-04-03 09:32:53.890510
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import sqlite
# revision identifiers, used by Alembic.
revision = '9a119272b516'
down_revision = 'd2113a61fa42'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
conn = op.get_bind()
# Create question_shares table if it doesn't exist
tables = [row[0] for row in conn.execute(sa.text("SELECT name FROM sqlite_master WHERE type='table'"))]
if 'question_shares' not in tables:
op.create_table('question_shares',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('question_id', sa.Integer(), nullable=False),
sa.Column('shared_with_user_id', sa.Integer(), nullable=False),
sa.Column('shared_by_user_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['question_id'], ['questions.id'], ),
sa.ForeignKeyConstraint(['shared_by_user_id'], ['users.id'], ),
sa.ForeignKeyConstraint(['shared_with_user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('question_id', 'shared_with_user_id', name='unique_question_share')
)
# Check existing columns to make migration idempotent
game_columns = [row[1] for row in conn.execute(sa.text("PRAGMA table_info('games')"))]
question_columns = [row[1] for row in conn.execute(sa.text("PRAGMA table_info('questions')"))]
# Add new game columns if missing
game_cols_to_add = []
if 'completed_at' not in game_columns:
game_cols_to_add.append(sa.Column('completed_at', sa.DateTime(), nullable=True))
if 'winners' not in game_columns:
game_cols_to_add.append(sa.Column('winners', sa.JSON(), nullable=True))
if game_cols_to_add:
with op.batch_alter_table('games', schema=None) as batch_op:
for col in game_cols_to_add:
batch_op.add_column(col)
# Drop old game columns if they still exist
game_cols_to_drop = [c for c in ['turn_order', 'is_steal_mode'] if c in game_columns]
if game_cols_to_drop:
with op.batch_alter_table('games', schema=None) as batch_op:
for col_name in game_cols_to_drop:
batch_op.drop_column(col_name)
# Add created_by to questions if missing
if 'created_by' not in question_columns:
with op.batch_alter_table('questions', schema=None) as batch_op:
batch_op.add_column(sa.Column('created_by', sa.Integer(), nullable=True))
batch_op.create_foreign_key('fk_questions_created_by', 'users', ['created_by'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('questions', schema=None) as batch_op:
batch_op.drop_constraint('fk_questions_created_by', type_='foreignkey')
batch_op.drop_column('created_by')
with op.batch_alter_table('games', schema=None) as batch_op:
batch_op.add_column(sa.Column('is_steal_mode', sa.BOOLEAN(), nullable=True))
batch_op.add_column(sa.Column('turn_order', sqlite.JSON(), nullable=True))
batch_op.drop_column('winners')
batch_op.drop_column('completed_at')
op.drop_table('question_shares')
# ### end Alembic commands ###