fix(migrations): add idempotency checks to additional migrations

Add existence checks to older migrations (002, 004, 013, 021, 031, 034, 044,
046, 071, 086, 087) to prevent errors when objects already exist.

Also fixes syntax errors in migrations 046 and 086 where index creation
code was incorrectly placed outside try/except blocks.

All affected migrations now:
- Check for table/column/index existence before creating
- Handle existing objects gracefully with informative messages
- Have safe downgrade functions
- Use proper try/except error handling
This commit is contained in:
Dries Peeters
2026-01-04 04:34:56 +01:00
parent fc6e1bb733
commit a24d515247
11 changed files with 813 additions and 237 deletions

View File

@@ -16,10 +16,57 @@ depends_on = None
def upgrade():
op.add_column('users', sa.Column('full_name', sa.String(length=200), nullable=True))
"""Add full_name column to users table"""
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
if 'users' not in existing_tables:
return
users_columns = {c['name'] for c in inspector.get_columns('users')}
if 'full_name' in users_columns:
print("✓ Column full_name already exists in users table")
return
try:
op.add_column('users', sa.Column('full_name', sa.String(length=200), nullable=True))
print("✓ Added full_name column to users table")
except Exception as e:
error_msg = str(e)
if 'already exists' in error_msg.lower() or 'duplicate' in error_msg.lower():
print("✓ Column full_name already exists in users table (detected via error)")
else:
print(f"✗ Error adding full_name column: {e}")
raise
def downgrade():
op.drop_column('users', 'full_name')
"""Remove full_name column from users table"""
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
if 'users' not in existing_tables:
return
users_columns = {c['name'] for c in inspector.get_columns('users')}
if 'full_name' not in users_columns:
print("⊘ Column full_name does not exist in users table, skipping")
return
try:
op.drop_column('users', 'full_name')
print("✓ Dropped full_name column from users table")
except Exception as e:
error_msg = str(e)
if 'does not exist' in error_msg.lower() or 'no such column' in error_msg.lower():
print("⊘ Column full_name does not exist in users table (detected via error)")
else:
print(f"⚠ Warning: Could not drop full_name column: {e}")

View File

@@ -17,28 +17,88 @@ depends_on = None
def upgrade() -> None:
op.create_table(
'task_activities',
sa.Column('id', sa.Integer(), primary_key=True),
sa.Column('task_id', sa.Integer(), sa.ForeignKey('tasks.id', ondelete='CASCADE'), nullable=False, index=True),
sa.Column('user_id', sa.Integer(), sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True),
sa.Column('event', sa.String(length=50), nullable=False, index=True),
sa.Column('details', sa.Text(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
)
"""Create task_activities table"""
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
if 'task_activities' in existing_tables:
print("✓ Table task_activities already exists")
# Ensure indexes exist
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('task_activities')]
indexes_to_create = [
('idx_task_activities_task_id', ['task_id']),
('idx_task_activities_user_id', ['user_id']),
('idx_task_activities_event', ['event']),
('idx_task_activities_created_at', ['created_at']),
]
for idx_name, cols in indexes_to_create:
if idx_name not in existing_indexes:
try:
op.create_index(idx_name, 'task_activities', cols)
except Exception:
pass
except Exception:
pass
return
try:
op.create_table(
'task_activities',
sa.Column('id', sa.Integer(), primary_key=True),
sa.Column('task_id', sa.Integer(), sa.ForeignKey('tasks.id', ondelete='CASCADE'), nullable=False, index=True),
sa.Column('user_id', sa.Integer(), sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True),
sa.Column('event', sa.String(length=50), nullable=False, index=True),
sa.Column('details', sa.Text(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
)
# Explicit indexes (in addition to inline index=True for portability)
op.create_index('idx_task_activities_task_id', 'task_activities', ['task_id'])
op.create_index('idx_task_activities_user_id', 'task_activities', ['user_id'])
op.create_index('idx_task_activities_event', 'task_activities', ['event'])
op.create_index('idx_task_activities_created_at', 'task_activities', ['created_at'])
# Explicit indexes (in addition to inline index=True for portability)
op.create_index('idx_task_activities_task_id', 'task_activities', ['task_id'])
op.create_index('idx_task_activities_user_id', 'task_activities', ['user_id'])
op.create_index('idx_task_activities_event', 'task_activities', ['event'])
op.create_index('idx_task_activities_created_at', 'task_activities', ['created_at'])
print("✓ Created task_activities table")
except Exception as e:
error_msg = str(e)
if 'already exists' in error_msg.lower() or 'duplicate' in error_msg.lower():
print("✓ Table task_activities already exists (detected via error)")
else:
print(f"✗ Error creating task_activities table: {e}")
raise
def downgrade() -> None:
op.drop_index('idx_task_activities_created_at', table_name='task_activities')
op.drop_index('idx_task_activities_event', table_name='task_activities')
op.drop_index('idx_task_activities_user_id', table_name='task_activities')
op.drop_index('idx_task_activities_task_id', table_name='task_activities')
op.drop_table('task_activities')
"""Drop task_activities table"""
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
if 'task_activities' not in existing_tables:
print("⊘ Table task_activities does not exist, skipping")
return
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('task_activities')]
for idx_name in ['idx_task_activities_created_at', 'idx_task_activities_event',
'idx_task_activities_user_id', 'idx_task_activities_task_id']:
if idx_name in existing_indexes:
try:
op.drop_index(idx_name, table_name='task_activities')
except Exception:
pass
op.drop_table('task_activities')
print("✓ Dropped task_activities table")
except Exception as e:
error_msg = str(e)
if 'does not exist' in error_msg.lower() or 'no such table' in error_msg.lower():
print("⊘ Table task_activities does not exist (detected via error)")
else:
print(f"⚠ Warning: Could not drop task_activities table: {e}")

View File

@@ -38,12 +38,32 @@ def upgrade() -> None:
sa.PrimaryKeyConstraint('id')
)
# Create indexes for better performance
op.create_index('ix_comments_project_id', 'comments', ['project_id'], unique=False)
op.create_index('ix_comments_task_id', 'comments', ['task_id'], unique=False)
op.create_index('ix_comments_user_id', 'comments', ['user_id'], unique=False)
op.create_index('ix_comments_parent_id', 'comments', ['parent_id'], unique=False)
op.create_index('ix_comments_created_at', 'comments', ['created_at'], unique=False)
# Create indexes for better performance (idempotent)
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('comments')]
indexes_to_create = [
('ix_comments_project_id', ['project_id']),
('ix_comments_task_id', ['task_id']),
('ix_comments_user_id', ['user_id']),
('ix_comments_parent_id', ['parent_id']),
('ix_comments_created_at', ['created_at']),
]
for idx_name, cols in indexes_to_create:
if idx_name not in existing_indexes:
try:
op.create_index(idx_name, 'comments', cols, unique=False)
except Exception:
pass # Index might already exist
except Exception:
# If we can't check indexes, try to create them anyway (best effort)
try:
op.create_index('ix_comments_project_id', 'comments', ['project_id'], unique=False)
op.create_index('ix_comments_task_id', 'comments', ['task_id'], unique=False)
op.create_index('ix_comments_user_id', 'comments', ['user_id'], unique=False)
op.create_index('ix_comments_parent_id', 'comments', ['parent_id'], unique=False)
op.create_index('ix_comments_created_at', 'comments', ['created_at'], unique=False)
except Exception:
pass # Indexes might already exist
def downgrade() -> None:

View File

@@ -107,16 +107,33 @@ def upgrade() -> None:
print(f"[Migration 021] ✗ Error creating table: {e}")
raise
# Create indexes
# Create indexes (idempotent)
print("[Migration 021] Creating indexes...")
try:
op.create_index('ix_extra_goods_project_id', 'extra_goods', ['project_id'])
op.create_index('ix_extra_goods_invoice_id', 'extra_goods', ['invoice_id'])
op.create_index('ix_extra_goods_created_by', 'extra_goods', ['created_by'])
print("[Migration 021] ✓ Indexes created")
existing_indexes = [idx['name'] for idx in inspector.get_indexes('extra_goods')]
indexes_to_create = [
('ix_extra_goods_project_id', ['project_id']),
('ix_extra_goods_invoice_id', ['invoice_id']),
('ix_extra_goods_created_by', ['created_by']),
]
for idx_name, cols in indexes_to_create:
if idx_name not in existing_indexes:
try:
op.create_index(idx_name, 'extra_goods', cols)
except Exception as e:
error_msg = str(e)
if 'already exists' not in error_msg.lower() and 'duplicate' not in error_msg.lower():
print(f"[Migration 021] ⚠ Warning creating index {idx_name}: {e}")
print("[Migration 021] ✓ Indexes created/verified")
except Exception as e:
print(f"[Migration 021] ✗ Error creating indexes: {e}")
raise
print(f"[Migration 021] ⚠ Warning checking/creating indexes: {e}")
# Try to create indexes anyway (best effort)
try:
op.create_index('ix_extra_goods_project_id', 'extra_goods', ['project_id'])
op.create_index('ix_extra_goods_invoice_id', 'extra_goods', ['invoice_id'])
op.create_index('ix_extra_goods_created_by', 'extra_goods', ['created_by'])
except Exception:
pass # Indexes might already exist
print("[Migration 021] ✓ Migration completed successfully")
else:

View File

@@ -17,12 +17,57 @@ depends_on = None
def upgrade():
"""Add standard_hours_per_day column to users table"""
op.add_column('users',
sa.Column('standard_hours_per_day', sa.Float(), nullable=False, server_default='8.0')
)
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
if 'users' not in existing_tables:
return
users_columns = {c['name'] for c in inspector.get_columns('users')}
if 'standard_hours_per_day' in users_columns:
print("✓ Column standard_hours_per_day already exists in users table")
return
try:
op.add_column('users',
sa.Column('standard_hours_per_day', sa.Float(), nullable=False, server_default='8.0')
)
print("✓ Added standard_hours_per_day column to users table")
except Exception as e:
error_msg = str(e)
if 'already exists' in error_msg.lower() or 'duplicate' in error_msg.lower():
print("✓ Column standard_hours_per_day already exists in users table (detected via error)")
else:
print(f"✗ Error adding standard_hours_per_day column: {e}")
raise
def downgrade():
"""Remove standard_hours_per_day column from users table"""
op.drop_column('users', 'standard_hours_per_day')
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
if 'users' not in existing_tables:
return
users_columns = {c['name'] for c in inspector.get_columns('users')}
if 'standard_hours_per_day' not in users_columns:
print("⊘ Column standard_hours_per_day does not exist in users table, skipping")
return
try:
op.drop_column('users', 'standard_hours_per_day')
print("✓ Dropped standard_hours_per_day column from users table")
except Exception as e:
error_msg = str(e)
if 'does not exist' in error_msg.lower() or 'no such column' in error_msg.lower():
print("⊘ Column standard_hours_per_day does not exist in users table (detected via error)")
else:
print(f"⚠ Warning: Could not drop standard_hours_per_day column: {e}")

View File

@@ -18,50 +18,119 @@ depends_on = None
def upgrade():
"""Create calendar_events table"""
op.create_table(
'calendar_events',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=200), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('start_time', sa.DateTime(), nullable=False),
sa.Column('end_time', sa.DateTime(), nullable=False),
sa.Column('all_day', sa.Boolean(), nullable=False, server_default='0'),
sa.Column('location', sa.String(length=200), nullable=True),
sa.Column('event_type', sa.String(length=50), nullable=False, server_default='event'),
sa.Column('project_id', sa.Integer(), nullable=True),
sa.Column('task_id', sa.Integer(), nullable=True),
sa.Column('client_id', sa.Integer(), nullable=True),
sa.Column('is_recurring', sa.Boolean(), nullable=False, server_default='0'),
sa.Column('recurrence_rule', sa.String(length=200), nullable=True),
sa.Column('recurrence_end_date', sa.DateTime(), nullable=True),
sa.Column('parent_event_id', sa.Integer(), nullable=True),
sa.Column('reminder_minutes', sa.Integer(), nullable=True),
sa.Column('color', sa.String(length=7), nullable=True),
sa.Column('is_private', sa.Boolean(), nullable=False, server_default='0'),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='fk_calendar_events_user_id'),
sa.ForeignKeyConstraint(['project_id'], ['projects.id'], name='fk_calendar_events_project_id'),
sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], name='fk_calendar_events_task_id'),
sa.ForeignKeyConstraint(['client_id'], ['clients.id'], name='fk_calendar_events_client_id'),
sa.ForeignKeyConstraint(['parent_event_id'], ['calendar_events.id'], name='fk_calendar_events_parent_event_id'),
sa.PrimaryKeyConstraint('id')
)
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
# Create indexes for better query performance
with op.batch_alter_table('calendar_events', schema=None) as batch_op:
batch_op.create_index('ix_calendar_events_user_id', ['user_id'])
batch_op.create_index('ix_calendar_events_start_time', ['start_time'])
batch_op.create_index('ix_calendar_events_end_time', ['end_time'])
batch_op.create_index('ix_calendar_events_event_type', ['event_type'])
batch_op.create_index('ix_calendar_events_project_id', ['project_id'])
batch_op.create_index('ix_calendar_events_task_id', ['task_id'])
batch_op.create_index('ix_calendar_events_client_id', ['client_id'])
batch_op.create_index('ix_calendar_events_parent_event_id', ['parent_event_id'])
existing_tables = inspector.get_table_names()
if 'calendar_events' in existing_tables:
print("✓ Table calendar_events already exists")
# Ensure indexes exist
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('calendar_events')]
indexes_to_create = [
('ix_calendar_events_user_id', ['user_id']),
('ix_calendar_events_start_time', ['start_time']),
('ix_calendar_events_end_time', ['end_time']),
('ix_calendar_events_event_type', ['event_type']),
('ix_calendar_events_project_id', ['project_id']),
('ix_calendar_events_task_id', ['task_id']),
('ix_calendar_events_client_id', ['client_id']),
('ix_calendar_events_parent_event_id', ['parent_event_id']),
]
for idx_name, cols in indexes_to_create:
if idx_name not in existing_indexes:
try:
op.create_index(idx_name, 'calendar_events', cols, unique=False)
except Exception:
pass
except Exception:
pass
return
try:
op.create_table(
'calendar_events',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=200), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('start_time', sa.DateTime(), nullable=False),
sa.Column('end_time', sa.DateTime(), nullable=False),
sa.Column('all_day', sa.Boolean(), nullable=False, server_default='0'),
sa.Column('location', sa.String(length=200), nullable=True),
sa.Column('event_type', sa.String(length=50), nullable=False, server_default='event'),
sa.Column('project_id', sa.Integer(), nullable=True),
sa.Column('task_id', sa.Integer(), nullable=True),
sa.Column('client_id', sa.Integer(), nullable=True),
sa.Column('is_recurring', sa.Boolean(), nullable=False, server_default='0'),
sa.Column('recurrence_rule', sa.String(length=200), nullable=True),
sa.Column('recurrence_end_date', sa.DateTime(), nullable=True),
sa.Column('parent_event_id', sa.Integer(), nullable=True),
sa.Column('reminder_minutes', sa.Integer(), nullable=True),
sa.Column('color', sa.String(length=7), nullable=True),
sa.Column('is_private', sa.Boolean(), nullable=False, server_default='0'),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='fk_calendar_events_user_id'),
sa.ForeignKeyConstraint(['project_id'], ['projects.id'], name='fk_calendar_events_project_id'),
sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], name='fk_calendar_events_task_id'),
sa.ForeignKeyConstraint(['client_id'], ['clients.id'], name='fk_calendar_events_client_id'),
sa.ForeignKeyConstraint(['parent_event_id'], ['calendar_events.id'], name='fk_calendar_events_parent_event_id'),
sa.PrimaryKeyConstraint('id')
)
# Create indexes for better query performance
with op.batch_alter_table('calendar_events', schema=None) as batch_op:
batch_op.create_index('ix_calendar_events_user_id', ['user_id'])
batch_op.create_index('ix_calendar_events_start_time', ['start_time'])
batch_op.create_index('ix_calendar_events_end_time', ['end_time'])
batch_op.create_index('ix_calendar_events_event_type', ['event_type'])
batch_op.create_index('ix_calendar_events_project_id', ['project_id'])
batch_op.create_index('ix_calendar_events_task_id', ['task_id'])
batch_op.create_index('ix_calendar_events_client_id', ['client_id'])
batch_op.create_index('ix_calendar_events_parent_event_id', ['parent_event_id'])
print("✓ Created calendar_events table")
except Exception as e:
error_msg = str(e)
if 'already exists' in error_msg.lower() or 'duplicate' in error_msg.lower():
print("✓ Table calendar_events already exists (detected via error)")
else:
print(f"✗ Error creating calendar_events table: {e}")
raise
def downgrade():
"""Drop calendar_events table"""
op.drop_table('calendar_events')
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
if 'calendar_events' not in existing_tables:
print("⊘ Table calendar_events does not exist, skipping")
return
try:
# Drop indexes first
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('calendar_events')]
for idx_name in existing_indexes:
try:
op.drop_index(idx_name, table_name='calendar_events')
except Exception:
pass
except Exception:
pass
op.drop_table('calendar_events')
print("✓ Dropped calendar_events table")
except Exception as e:
error_msg = str(e)
if 'does not exist' in error_msg.lower() or 'no such table' in error_msg.lower():
print("⊘ Table calendar_events does not exist (detected via error)")
else:
print(f"⚠ Warning: Could not drop calendar_events table: {e}")

View File

@@ -19,47 +19,112 @@ depends_on = None
def upgrade():
"""Create audit_logs table for comprehensive change tracking"""
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
op.create_table('audit_logs',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('entity_type', sa.String(length=50), nullable=False),
sa.Column('entity_id', sa.Integer(), nullable=False),
sa.Column('entity_name', sa.String(length=500), nullable=True),
sa.Column('action', sa.String(length=20), nullable=False),
sa.Column('field_name', sa.String(length=100), nullable=True),
sa.Column('old_value', sa.Text(), nullable=True),
sa.Column('new_value', sa.Text(), nullable=True),
sa.Column('change_description', sa.Text(), nullable=True),
sa.Column('ip_address', sa.String(length=45), nullable=True),
sa.Column('user_agent', sa.Text(), nullable=True),
sa.Column('request_path', sa.String(length=500), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='SET NULL'),
sa.PrimaryKeyConstraint('id')
)
existing_tables = inspector.get_table_names()
# Create indexes for common queries
op.create_index('ix_audit_logs_entity', 'audit_logs', ['entity_type', 'entity_id'])
op.create_index('ix_audit_logs_user_created', 'audit_logs', ['user_id', 'created_at'])
op.create_index('ix_audit_logs_created_at', 'audit_logs', ['created_at'])
op.create_index('ix_audit_logs_action', 'audit_logs', ['action'])
op.create_index('ix_audit_logs_entity_type', 'audit_logs', ['entity_type'])
op.create_index('ix_audit_logs_entity_id', 'audit_logs', ['entity_id'])
op.create_index('ix_audit_logs_user_id', 'audit_logs', ['user_id'])
op.create_index('ix_audit_logs_field_name', 'audit_logs', ['field_name'])
if 'audit_logs' in existing_tables:
print("✓ Table audit_logs already exists")
# Ensure indexes exist
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('audit_logs')]
indexes_to_create = [
('ix_audit_logs_entity', ['entity_type', 'entity_id']),
('ix_audit_logs_user_created', ['user_id', 'created_at']),
('ix_audit_logs_created_at', ['created_at']),
('ix_audit_logs_action', ['action']),
('ix_audit_logs_entity_type', ['entity_type']),
('ix_audit_logs_entity_id', ['entity_id']),
('ix_audit_logs_user_id', ['user_id']),
('ix_audit_logs_field_name', ['field_name']),
]
for idx_name, cols in indexes_to_create:
if idx_name not in existing_indexes:
try:
op.create_index(idx_name, 'audit_logs', cols, unique=False)
except Exception:
pass
except Exception:
pass
return
try:
op.create_table('audit_logs',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('entity_type', sa.String(length=50), nullable=False),
sa.Column('entity_id', sa.Integer(), nullable=False),
sa.Column('entity_name', sa.String(length=500), nullable=True),
sa.Column('action', sa.String(length=20), nullable=False),
sa.Column('field_name', sa.String(length=100), nullable=True),
sa.Column('old_value', sa.Text(), nullable=True),
sa.Column('new_value', sa.Text(), nullable=True),
sa.Column('change_description', sa.Text(), nullable=True),
sa.Column('ip_address', sa.String(length=45), nullable=True),
sa.Column('user_agent', sa.Text(), nullable=True),
sa.Column('request_path', sa.String(length=500), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='SET NULL'),
sa.PrimaryKeyConstraint('id')
)
# Create indexes for common queries
op.create_index('ix_audit_logs_entity', 'audit_logs', ['entity_type', 'entity_id'])
op.create_index('ix_audit_logs_user_created', 'audit_logs', ['user_id', 'created_at'])
op.create_index('ix_audit_logs_created_at', 'audit_logs', ['created_at'])
op.create_index('ix_audit_logs_action', 'audit_logs', ['action'])
op.create_index('ix_audit_logs_entity_type', 'audit_logs', ['entity_type'])
op.create_index('ix_audit_logs_entity_id', 'audit_logs', ['entity_id'])
op.create_index('ix_audit_logs_user_id', 'audit_logs', ['user_id'])
op.create_index('ix_audit_logs_field_name', 'audit_logs', ['field_name'])
print("✓ Created audit_logs table")
except Exception as e:
error_msg = str(e)
if 'already exists' in error_msg.lower() or 'duplicate' in error_msg.lower():
print("✓ Table audit_logs already exists (detected via error)")
else:
print(f"✗ Error creating audit_logs table: {e}")
raise
def downgrade():
"""Remove audit_logs table"""
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
op.drop_index('ix_audit_logs_field_name', table_name='audit_logs')
op.drop_index('ix_audit_logs_user_id', table_name='audit_logs')
op.drop_index('ix_audit_logs_entity_id', table_name='audit_logs')
op.drop_index('ix_audit_logs_entity_type', table_name='audit_logs')
op.drop_index('ix_audit_logs_action', table_name='audit_logs')
op.drop_index('ix_audit_logs_created_at', table_name='audit_logs')
op.drop_index('ix_audit_logs_user_created', table_name='audit_logs')
op.drop_index('ix_audit_logs_entity', table_name='audit_logs')
op.drop_table('audit_logs')
existing_tables = inspector.get_table_names()
if 'audit_logs' not in existing_tables:
print("⊘ Table audit_logs does not exist, skipping")
return
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('audit_logs')]
indexes_to_drop = [
'ix_audit_logs_field_name',
'ix_audit_logs_user_id',
'ix_audit_logs_entity_id',
'ix_audit_logs_entity_type',
'ix_audit_logs_action',
'ix_audit_logs_created_at',
'ix_audit_logs_user_created',
'ix_audit_logs_entity',
]
for idx_name in indexes_to_drop:
if idx_name in existing_indexes:
try:
op.drop_index(idx_name, table_name='audit_logs')
except Exception:
pass
op.drop_table('audit_logs')
print("✓ Dropped audit_logs table")
except Exception as e:
error_msg = str(e)
if 'does not exist' in error_msg.lower() or 'no such table' in error_msg.lower():
print("⊘ Table audit_logs does not exist (detected via error)")
else:
print(f"⚠ Warning: Could not drop audit_logs table: {e}")

View File

@@ -19,86 +19,178 @@ depends_on = None
def upgrade():
"""Create webhooks and webhook_deliveries tables"""
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
# Create webhooks table
op.create_table('webhooks',
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', sa.String(length=500), nullable=False),
sa.Column('secret', sa.String(length=128), nullable=True),
sa.Column('events', sa.JSON(), nullable=False, server_default='[]'),
sa.Column('http_method', sa.String(length=10), nullable=False, server_default='POST'),
sa.Column('content_type', sa.String(length=50), nullable=False, server_default='application/json'),
sa.Column('headers', sa.JSON(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False, server_default='1'),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('max_retries', sa.Integer(), nullable=False, server_default='3'),
sa.Column('retry_delay_seconds', sa.Integer(), nullable=False, server_default='60'),
sa.Column('timeout_seconds', sa.Integer(), nullable=False, server_default='30'),
sa.Column('total_deliveries', sa.Integer(), nullable=False, server_default='0'),
sa.Column('successful_deliveries', sa.Integer(), nullable=False, server_default='0'),
sa.Column('failed_deliveries', sa.Integer(), nullable=False, server_default='0'),
sa.Column('last_delivery_at', sa.DateTime(), nullable=True),
sa.Column('last_success_at', sa.DateTime(), nullable=True),
sa.Column('last_failure_at', sa.DateTime(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
# Create indexes for webhooks
op.create_index('ix_webhooks_user_id', 'webhooks', ['user_id'])
op.create_index('ix_webhooks_is_active', 'webhooks', ['is_active'])
op.create_index('ix_webhooks_created_at', 'webhooks', ['created_at'])
if 'webhooks' in existing_tables:
print("✓ Table webhooks already exists")
# Ensure indexes exist
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('webhooks')]
for idx_name, cols in [
('ix_webhooks_user_id', ['user_id']),
('ix_webhooks_is_active', ['is_active']),
('ix_webhooks_created_at', ['created_at']),
]:
if idx_name not in existing_indexes:
try:
op.create_index(idx_name, 'webhooks', cols, unique=False)
except Exception:
pass
except Exception:
pass
else:
try:
op.create_table('webhooks',
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', sa.String(length=500), nullable=False),
sa.Column('secret', sa.String(length=128), nullable=True),
sa.Column('events', sa.JSON(), nullable=False, server_default='[]'),
sa.Column('http_method', sa.String(length=10), nullable=False, server_default='POST'),
sa.Column('content_type', sa.String(length=50), nullable=False, server_default='application/json'),
sa.Column('headers', sa.JSON(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False, server_default='1'),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('max_retries', sa.Integer(), nullable=False, server_default='3'),
sa.Column('retry_delay_seconds', sa.Integer(), nullable=False, server_default='60'),
sa.Column('timeout_seconds', sa.Integer(), nullable=False, server_default='30'),
sa.Column('total_deliveries', sa.Integer(), nullable=False, server_default='0'),
sa.Column('successful_deliveries', sa.Integer(), nullable=False, server_default='0'),
sa.Column('failed_deliveries', sa.Integer(), nullable=False, server_default='0'),
sa.Column('last_delivery_at', sa.DateTime(), nullable=True),
sa.Column('last_success_at', sa.DateTime(), nullable=True),
sa.Column('last_failure_at', sa.DateTime(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
# Create indexes for webhooks
op.create_index('ix_webhooks_user_id', 'webhooks', ['user_id'])
op.create_index('ix_webhooks_is_active', 'webhooks', ['is_active'])
op.create_index('ix_webhooks_created_at', 'webhooks', ['created_at'])
print("✓ Created webhooks table")
except Exception as e:
error_msg = str(e)
if 'already exists' in error_msg.lower() or 'duplicate' in error_msg.lower():
print("✓ Table webhooks already exists (detected via error)")
else:
print(f"✗ Error creating webhooks table: {e}")
raise
# Create webhook_deliveries table
op.create_table('webhook_deliveries',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('webhook_id', sa.Integer(), nullable=False),
sa.Column('event_type', sa.String(length=100), nullable=False),
sa.Column('event_id', sa.String(length=100), nullable=True),
sa.Column('payload', sa.Text(), nullable=False),
sa.Column('payload_hash', sa.String(length=64), nullable=True),
sa.Column('status', sa.String(length=20), nullable=False, server_default='pending'),
sa.Column('attempt_number', sa.Integer(), nullable=False, server_default='1'),
sa.Column('response_status_code', sa.Integer(), nullable=True),
sa.Column('response_body', sa.Text(), nullable=True),
sa.Column('response_headers', sa.JSON(), nullable=True),
sa.Column('error_message', sa.Text(), nullable=True),
sa.Column('error_type', sa.String(length=100), nullable=True),
sa.Column('started_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.Column('completed_at', sa.DateTime(), nullable=True),
sa.Column('duration_ms', sa.Integer(), nullable=True),
sa.Column('next_retry_at', sa.DateTime(), nullable=True),
sa.Column('retry_count', sa.Integer(), nullable=False, server_default='0'),
sa.ForeignKeyConstraint(['webhook_id'], ['webhooks.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
# Create indexes for webhook_deliveries
op.create_index('ix_webhook_deliveries_webhook_id', 'webhook_deliveries', ['webhook_id'])
op.create_index('ix_webhook_deliveries_status', 'webhook_deliveries', ['status'])
op.create_index('ix_webhook_deliveries_event_type', 'webhook_deliveries', ['event_type'])
op.create_index('ix_webhook_deliveries_next_retry_at', 'webhook_deliveries', ['next_retry_at'])
op.create_index('ix_webhook_deliveries_started_at', 'webhook_deliveries', ['started_at'])
if 'webhook_deliveries' in existing_tables:
print("✓ Table webhook_deliveries already exists")
# Ensure indexes exist
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('webhook_deliveries')]
for idx_name, cols in [
('ix_webhook_deliveries_webhook_id', ['webhook_id']),
('ix_webhook_deliveries_status', ['status']),
('ix_webhook_deliveries_event_type', ['event_type']),
('ix_webhook_deliveries_next_retry_at', ['next_retry_at']),
('ix_webhook_deliveries_started_at', ['started_at']),
]:
if idx_name not in existing_indexes:
try:
op.create_index(idx_name, 'webhook_deliveries', cols, unique=False)
except Exception:
pass
except Exception:
pass
else:
try:
op.create_table('webhook_deliveries',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('webhook_id', sa.Integer(), nullable=False),
sa.Column('event_type', sa.String(length=100), nullable=False),
sa.Column('event_id', sa.String(length=100), nullable=True),
sa.Column('payload', sa.Text(), nullable=False),
sa.Column('payload_hash', sa.String(length=64), nullable=True),
sa.Column('status', sa.String(length=20), nullable=False, server_default='pending'),
sa.Column('attempt_number', sa.Integer(), nullable=False, server_default='1'),
sa.Column('response_status_code', sa.Integer(), nullable=True),
sa.Column('response_body', sa.Text(), nullable=True),
sa.Column('response_headers', sa.JSON(), nullable=True),
sa.Column('error_message', sa.Text(), nullable=True),
sa.Column('error_type', sa.String(length=100), nullable=True),
sa.Column('started_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.Column('completed_at', sa.DateTime(), nullable=True),
sa.Column('duration_ms', sa.Integer(), nullable=True),
sa.Column('next_retry_at', sa.DateTime(), nullable=True),
sa.Column('retry_count', sa.Integer(), nullable=False, server_default='0'),
sa.ForeignKeyConstraint(['webhook_id'], ['webhooks.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
# Create indexes for webhook_deliveries
op.create_index('ix_webhook_deliveries_webhook_id', 'webhook_deliveries', ['webhook_id'])
op.create_index('ix_webhook_deliveries_status', 'webhook_deliveries', ['status'])
op.create_index('ix_webhook_deliveries_event_type', 'webhook_deliveries', ['event_type'])
op.create_index('ix_webhook_deliveries_next_retry_at', 'webhook_deliveries', ['next_retry_at'])
op.create_index('ix_webhook_deliveries_started_at', 'webhook_deliveries', ['started_at'])
print("✓ Created webhook_deliveries table")
except Exception as e:
error_msg = str(e)
if 'already exists' in error_msg.lower() or 'duplicate' in error_msg.lower():
print("✓ Table webhook_deliveries already exists (detected via error)")
else:
print(f"✗ Error creating webhook_deliveries table: {e}")
raise
def downgrade():
"""Remove webhooks system tables"""
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
# Drop webhook_deliveries table
op.drop_index('ix_webhook_deliveries_started_at', table_name='webhook_deliveries')
op.drop_index('ix_webhook_deliveries_next_retry_at', table_name='webhook_deliveries')
op.drop_index('ix_webhook_deliveries_event_type', table_name='webhook_deliveries')
op.drop_index('ix_webhook_deliveries_status', table_name='webhook_deliveries')
op.drop_index('ix_webhook_deliveries_webhook_id', table_name='webhook_deliveries')
op.drop_table('webhook_deliveries')
if 'webhook_deliveries' in existing_tables:
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('webhook_deliveries')]
for idx_name in ['ix_webhook_deliveries_started_at', 'ix_webhook_deliveries_next_retry_at',
'ix_webhook_deliveries_event_type', 'ix_webhook_deliveries_status',
'ix_webhook_deliveries_webhook_id']:
if idx_name in existing_indexes:
try:
op.drop_index(idx_name, table_name='webhook_deliveries')
except Exception:
pass
op.drop_table('webhook_deliveries')
print("✓ Dropped webhook_deliveries table")
except Exception as e:
error_msg = str(e)
if 'does not exist' in error_msg.lower() or 'no such table' in error_msg.lower():
print("⊘ Table webhook_deliveries does not exist (detected via error)")
else:
print(f"⚠ Warning: Could not drop webhook_deliveries table: {e}")
# Drop webhooks table
op.drop_index('ix_webhooks_created_at', table_name='webhooks')
op.drop_index('ix_webhooks_is_active', table_name='webhooks')
op.drop_index('ix_webhooks_user_id', table_name='webhooks')
op.drop_table('webhooks')
if 'webhooks' in existing_tables:
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('webhooks')]
for idx_name in ['ix_webhooks_created_at', 'ix_webhooks_is_active', 'ix_webhooks_user_id']:
if idx_name in existing_indexes:
try:
op.drop_index(idx_name, table_name='webhooks')
except Exception:
pass
op.drop_table('webhooks')
print("✓ Dropped webhooks table")
except Exception as e:
error_msg = str(e)
if 'does not exist' in error_msg.lower() or 'no such table' in error_msg.lower():
print("⊘ Table webhooks does not exist (detected via error)")
else:
print(f"⚠ Warning: Could not drop webhooks table: {e}")

View File

@@ -47,8 +47,20 @@ def upgrade():
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_recurring_tasks_project_id'), 'recurring_tasks', ['project_id'], unique=False)
op.create_index(op.f('ix_recurring_tasks_assigned_to'), 'recurring_tasks', ['assigned_to'], unique=False)
# Create indexes (idempotent)
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('recurring_tasks')]
if op.f('ix_recurring_tasks_project_id') not in existing_indexes:
op.create_index(op.f('ix_recurring_tasks_project_id'), 'recurring_tasks', ['project_id'], unique=False)
if op.f('ix_recurring_tasks_assigned_to') not in existing_indexes:
op.create_index(op.f('ix_recurring_tasks_assigned_to'), 'recurring_tasks', ['assigned_to'], unique=False)
except Exception:
# If we can't check, try to create indexes anyway
try:
op.create_index(op.f('ix_recurring_tasks_project_id'), 'recurring_tasks', ['project_id'], unique=False)
op.create_index(op.f('ix_recurring_tasks_assigned_to'), 'recurring_tasks', ['assigned_to'], unique=False)
except Exception:
pass # Indexes might already exist
def downgrade():

View File

@@ -20,53 +20,146 @@ depends_on = None
def upgrade():
"""Create project_attachments and client_attachments tables"""
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
# Create project_attachments table
op.create_table('project_attachments',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('project_id', sa.Integer(), nullable=False),
sa.Column('filename', sa.String(length=255), nullable=False),
sa.Column('original_filename', sa.String(length=255), nullable=False),
sa.Column('file_path', sa.String(length=500), nullable=False),
sa.Column('file_size', sa.Integer(), nullable=False),
sa.Column('mime_type', sa.String(length=100), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('is_visible_to_client', sa.Boolean(), nullable=False, server_default='false'),
sa.Column('uploaded_by', sa.Integer(), nullable=False),
sa.Column('uploaded_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['project_id'], ['projects.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['uploaded_by'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index('ix_project_attachments_project_id', 'project_attachments', ['project_id'], unique=False)
op.create_index('ix_project_attachments_uploaded_by', 'project_attachments', ['uploaded_by'], unique=False)
if 'project_attachments' in existing_tables:
print("✓ Table project_attachments already exists")
# Ensure indexes exist
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('project_attachments')]
for idx_name, cols in [
('ix_project_attachments_project_id', ['project_id']),
('ix_project_attachments_uploaded_by', ['uploaded_by']),
]:
if idx_name not in existing_indexes:
try:
op.create_index(idx_name, 'project_attachments', cols, unique=False)
except Exception:
pass
except Exception:
pass
else:
try:
op.create_table('project_attachments',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('project_id', sa.Integer(), nullable=False),
sa.Column('filename', sa.String(length=255), nullable=False),
sa.Column('original_filename', sa.String(length=255), nullable=False),
sa.Column('file_path', sa.String(length=500), nullable=False),
sa.Column('file_size', sa.Integer(), nullable=False),
sa.Column('mime_type', sa.String(length=100), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('is_visible_to_client', sa.Boolean(), nullable=False, server_default='false'),
sa.Column('uploaded_by', sa.Integer(), nullable=False),
sa.Column('uploaded_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['project_id'], ['projects.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['uploaded_by'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index('ix_project_attachments_project_id', 'project_attachments', ['project_id'], unique=False)
op.create_index('ix_project_attachments_uploaded_by', 'project_attachments', ['uploaded_by'], unique=False)
print("✓ Created project_attachments table")
except Exception as e:
error_msg = str(e)
if 'already exists' in error_msg.lower() or 'duplicate' in error_msg.lower():
print("✓ Table project_attachments already exists (detected via error)")
else:
print(f"✗ Error creating project_attachments table: {e}")
raise
# Create client_attachments table
op.create_table('client_attachments',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('client_id', sa.Integer(), nullable=False),
sa.Column('filename', sa.String(length=255), nullable=False),
sa.Column('original_filename', sa.String(length=255), nullable=False),
sa.Column('file_path', sa.String(length=500), nullable=False),
sa.Column('file_size', sa.Integer(), nullable=False),
sa.Column('mime_type', sa.String(length=100), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('is_visible_to_client', sa.Boolean(), nullable=False, server_default='false'),
sa.Column('uploaded_by', sa.Integer(), nullable=False),
sa.Column('uploaded_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['client_id'], ['clients.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['uploaded_by'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index('ix_client_attachments_client_id', 'client_attachments', ['client_id'], unique=False)
op.create_index('ix_client_attachments_uploaded_by', 'client_attachments', ['uploaded_by'], unique=False)
if 'client_attachments' in existing_tables:
print("✓ Table client_attachments already exists")
# Ensure indexes exist
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('client_attachments')]
for idx_name, cols in [
('ix_client_attachments_client_id', ['client_id']),
('ix_client_attachments_uploaded_by', ['uploaded_by']),
]:
if idx_name not in existing_indexes:
try:
op.create_index(idx_name, 'client_attachments', cols, unique=False)
except Exception:
pass
except Exception:
pass
else:
try:
op.create_table('client_attachments',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('client_id', sa.Integer(), nullable=False),
sa.Column('filename', sa.String(length=255), nullable=False),
sa.Column('original_filename', sa.String(length=255), nullable=False),
sa.Column('file_path', sa.String(length=500), nullable=False),
sa.Column('file_size', sa.Integer(), nullable=False),
sa.Column('mime_type', sa.String(length=100), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('is_visible_to_client', sa.Boolean(), nullable=False, server_default='false'),
sa.Column('uploaded_by', sa.Integer(), nullable=False),
sa.Column('uploaded_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['client_id'], ['clients.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['uploaded_by'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index('ix_client_attachments_client_id', 'client_attachments', ['client_id'], unique=False)
op.create_index('ix_client_attachments_uploaded_by', 'client_attachments', ['uploaded_by'], unique=False)
print("✓ Created client_attachments table")
except Exception as e:
error_msg = str(e)
if 'already exists' in error_msg.lower() or 'duplicate' in error_msg.lower():
print("✓ Table client_attachments already exists (detected via error)")
else:
print(f"✗ Error creating client_attachments table: {e}")
raise
def downgrade():
"""Drop project_attachments and client_attachments tables"""
op.drop_index('ix_client_attachments_uploaded_by', table_name='client_attachments')
op.drop_index('ix_client_attachments_client_id', table_name='client_attachments')
op.drop_table('client_attachments')
op.drop_index('ix_project_attachments_uploaded_by', table_name='project_attachments')
op.drop_index('ix_project_attachments_project_id', table_name='project_attachments')
op.drop_table('project_attachments')
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
if 'client_attachments' in existing_tables:
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('client_attachments')]
for idx_name in ['ix_client_attachments_uploaded_by', 'ix_client_attachments_client_id']:
if idx_name in existing_indexes:
try:
op.drop_index(idx_name, table_name='client_attachments')
except Exception:
pass
op.drop_table('client_attachments')
print("✓ Dropped client_attachments table")
except Exception as e:
error_msg = str(e)
if 'does not exist' in error_msg.lower() or 'no such table' in error_msg.lower():
print("⊘ Table client_attachments does not exist (detected via error)")
else:
print(f"⚠ Warning: Could not drop client_attachments table: {e}")
if 'project_attachments' in existing_tables:
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('project_attachments')]
for idx_name in ['ix_project_attachments_uploaded_by', 'ix_project_attachments_project_id']:
if idx_name in existing_indexes:
try:
op.drop_index(idx_name, table_name='project_attachments')
except Exception:
pass
op.drop_table('project_attachments')
print("✓ Dropped project_attachments table")
except Exception as e:
error_msg = str(e)
if 'does not exist' in error_msg.lower() or 'no such table' in error_msg.lower():
print("⊘ Table project_attachments does not exist (detected via error)")
else:
print(f"⚠ Warning: Could not drop project_attachments table: {e}")

View File

@@ -19,7 +19,32 @@ depends_on = None
def upgrade():
"""Create salesman_email_mappings table"""
op.create_table('salesman_email_mappings',
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
if 'salesman_email_mappings' in existing_tables:
print("✓ Table salesman_email_mappings already exists")
# Ensure indexes exist
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('salesman_email_mappings')]
for idx_name, cols in [
('ix_salesman_email_mappings_initial', ['salesman_initial']),
('ix_salesman_email_mappings_active', ['is_active']),
]:
if idx_name not in existing_indexes:
try:
op.create_index(idx_name, 'salesman_email_mappings', cols, unique=False)
except Exception:
pass
except Exception:
pass
return
try:
op.create_table('salesman_email_mappings',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('salesman_initial', sa.String(length=20), nullable=False),
sa.Column('email_address', sa.String(length=255), nullable=True),
@@ -30,15 +55,46 @@ def upgrade():
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('salesman_initial', name='uq_salesman_email_mapping_initial')
)
op.create_index('ix_salesman_email_mappings_initial', 'salesman_email_mappings', ['salesman_initial'], unique=False)
op.create_index('ix_salesman_email_mappings_active', 'salesman_email_mappings', ['is_active'], unique=False)
sa.UniqueConstraint('salesman_initial', name='uq_salesman_email_mapping_initial')
)
op.create_index('ix_salesman_email_mappings_initial', 'salesman_email_mappings', ['salesman_initial'], unique=False)
op.create_index('ix_salesman_email_mappings_active', 'salesman_email_mappings', ['is_active'], unique=False)
print("✓ Created salesman_email_mappings table")
except Exception as e:
error_msg = str(e)
if 'already exists' in error_msg.lower() or 'duplicate' in error_msg.lower():
print("✓ Table salesman_email_mappings already exists (detected via error)")
else:
print(f"✗ Error creating salesman_email_mappings table: {e}")
raise
def downgrade():
"""Drop salesman_email_mappings table"""
op.drop_index('ix_salesman_email_mappings_active', table_name='salesman_email_mappings')
op.drop_index('ix_salesman_email_mappings_initial', table_name='salesman_email_mappings')
op.drop_table('salesman_email_mappings')
from sqlalchemy import inspect
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = inspector.get_table_names()
if 'salesman_email_mappings' not in existing_tables:
print("⊘ Table salesman_email_mappings does not exist, skipping")
return
try:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('salesman_email_mappings')]
for idx_name in ['ix_salesman_email_mappings_active', 'ix_salesman_email_mappings_initial']:
if idx_name in existing_indexes:
try:
op.drop_index(idx_name, table_name='salesman_email_mappings')
except Exception:
pass
op.drop_table('salesman_email_mappings')
print("✓ Dropped salesman_email_mappings table")
except Exception as e:
error_msg = str(e)
if 'does not exist' in error_msg.lower() or 'no such table' in error_msg.lower():
print("⊘ Table salesman_email_mappings does not exist (detected via error)")
else:
print(f"⚠ Warning: Could not drop salesman_email_mappings table: {e}")