diff --git a/migrations/versions/075_add_client_custom_fields_and_link_templates.py b/migrations/versions/075_add_client_custom_fields_and_link_templates.py index f16248e..32e9c14 100644 --- a/migrations/versions/075_add_client_custom_fields_and_link_templates.py +++ b/migrations/versions/075_add_client_custom_fields_and_link_templates.py @@ -27,6 +27,22 @@ def _has_column(inspector, table_name: str, column_name: str) -> bool: return False +def _has_table(inspector, table_name: str) -> bool: + """Check if a table exists""" + try: + return table_name in inspector.get_table_names() + except Exception: + return False + + +def _has_index(inspector, table_name: str, index_name: str) -> bool: + """Check if an index exists on a table""" + try: + return any((idx.get("name") or "") == index_name for idx in inspector.get_indexes(table_name)) + except Exception: + return False + + def upgrade(): """Add custom_fields to clients and create link_templates table""" bind = op.get_bind() @@ -35,30 +51,44 @@ def upgrade(): bool_true_default = '1' if dialect_name == 'sqlite' else ('true' if dialect_name == 'postgresql' else '1') # Add custom_fields column to clients table if it doesn't exist - if 'clients' in inspector.get_table_names(): + if _has_table(inspector, 'clients'): if not _has_column(inspector, 'clients', 'custom_fields'): # Use portable JSON type for cross-db compatibility (SQLite + PostgreSQL). op.add_column('clients', sa.Column('custom_fields', sa.JSON(), nullable=True)) - # Create link_templates table - op.create_table( - 'link_templates', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=200), nullable=False), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('url_template', sa.String(length=1000), nullable=False), - sa.Column('icon', sa.String(length=50), nullable=True), - sa.Column('field_key', sa.String(length=100), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False, server_default=sa.text(bool_true_default)), - sa.Column('order', sa.Integer(), nullable=False, server_default='0'), - sa.Column('created_by', sa.Integer(), nullable=False), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - sa.ForeignKeyConstraint(['created_by'], ['users.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_index('idx_link_templates_is_active', 'link_templates', ['is_active']) - op.create_index('idx_link_templates_field_key', 'link_templates', ['field_key']) + # Create link_templates table (idempotent; some installs may already have it) + if not _has_table(inspector, 'link_templates'): + op.create_table( + 'link_templates', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=200), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('url_template', sa.String(length=1000), nullable=False), + sa.Column('icon', sa.String(length=50), nullable=True), + sa.Column('field_key', sa.String(length=100), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False, server_default=sa.text(bool_true_default)), + sa.Column('order', sa.Integer(), nullable=False, server_default='0'), + sa.Column('created_by', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['created_by'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + else: + print("[Migration 075] ℹ Table link_templates already exists, skipping creation") + + # Ensure indexes exist (best-effort / idempotent) + if _has_table(inspector, 'link_templates'): + if not _has_index(inspector, 'link_templates', 'idx_link_templates_is_active'): + try: + op.create_index('idx_link_templates_is_active', 'link_templates', ['is_active']) + except Exception: + pass + if not _has_index(inspector, 'link_templates', 'idx_link_templates_field_key'): + try: + op.create_index('idx_link_templates_field_key', 'link_templates', ['field_key']) + except Exception: + pass def downgrade(): @@ -67,13 +97,13 @@ def downgrade(): inspector = sa.inspect(bind) # Drop link_templates table - if 'link_templates' in inspector.get_table_names(): + if _has_table(inspector, 'link_templates'): op.drop_index('idx_link_templates_field_key', table_name='link_templates') op.drop_index('idx_link_templates_is_active', table_name='link_templates') op.drop_table('link_templates') # Remove custom_fields column from clients table - if 'clients' in inspector.get_table_names(): + if _has_table(inspector, 'clients'): if _has_column(inspector, 'clients', 'custom_fields'): op.drop_column('clients', 'custom_fields')