mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-18 01:59:43 -06:00
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:
@@ -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}")
|
||||
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user