"""Add enhanced alert features for Phase 5 Revision ID: 004 Revises: 003 Create Date: 2025-11-18 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic revision = '004' down_revision = '003' branch_labels = None depends_on = None def upgrade(): """ Add enhancements for Phase 5 Alert Rule Engine: - Enhanced alert_rules fields - Enhanced alerts fields - New webhooks table - New webhook_delivery_log table """ # Enhance alert_rules table with op.batch_alter_table('alert_rules') as batch_op: batch_op.add_column(sa.Column('name', sa.String(255), nullable=True, comment='User-friendly rule name')) batch_op.add_column(sa.Column('webhook_enabled', sa.Boolean(), nullable=False, server_default='0', comment='Whether to send webhooks for this rule')) batch_op.add_column(sa.Column('severity', sa.String(20), nullable=True, comment='Alert severity level (critical, warning, info)')) batch_op.add_column(sa.Column('filter_conditions', sa.Text(), nullable=True, comment='JSON filter conditions for the rule')) batch_op.add_column(sa.Column('config_file', sa.String(255), nullable=True, comment='Optional: specific config file this rule applies to')) batch_op.add_column(sa.Column('updated_at', sa.DateTime(), nullable=True, comment='Last update timestamp')) # Enhance alerts table with op.batch_alter_table('alerts') as batch_op: batch_op.add_column(sa.Column('rule_id', sa.Integer(), nullable=True, comment='Associated alert rule')) batch_op.add_column(sa.Column('webhook_sent', sa.Boolean(), nullable=False, server_default='0', comment='Whether webhook was sent')) batch_op.add_column(sa.Column('webhook_sent_at', sa.DateTime(), nullable=True, comment='When webhook was sent')) batch_op.add_column(sa.Column('acknowledged', sa.Boolean(), nullable=False, server_default='0', comment='Whether alert was acknowledged')) batch_op.add_column(sa.Column('acknowledged_at', sa.DateTime(), nullable=True, comment='When alert was acknowledged')) batch_op.add_column(sa.Column('acknowledged_by', sa.String(255), nullable=True, comment='User who acknowledged the alert')) batch_op.create_foreign_key('fk_alerts_rule_id', 'alert_rules', ['rule_id'], ['id']) batch_op.create_index('idx_alerts_rule_id', ['rule_id']) batch_op.create_index('idx_alerts_acknowledged', ['acknowledged']) # Create webhooks table op.create_table('webhooks', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(255), nullable=False, comment='Webhook name'), sa.Column('url', sa.Text(), nullable=False, comment='Webhook URL'), sa.Column('enabled', sa.Boolean(), nullable=False, server_default='1', comment='Whether webhook is enabled'), sa.Column('auth_type', sa.String(20), nullable=True, comment='Authentication type: none, bearer, basic, custom'), sa.Column('auth_token', sa.Text(), nullable=True, comment='Encrypted authentication token'), sa.Column('custom_headers', sa.Text(), nullable=True, comment='JSON custom headers'), sa.Column('alert_types', sa.Text(), nullable=True, comment='JSON array of alert types to trigger on'), sa.Column('severity_filter', sa.Text(), nullable=True, comment='JSON array of severities to trigger on'), sa.Column('timeout', sa.Integer(), nullable=True, server_default='10', comment='Request timeout in seconds'), sa.Column('retry_count', sa.Integer(), nullable=True, server_default='3', comment='Number of retry attempts'), sa.Column('created_at', sa.DateTime(), nullable=False), sa.Column('updated_at', sa.DateTime(), nullable=False), sa.PrimaryKeyConstraint('id') ) # Create webhook_delivery_log table op.create_table('webhook_delivery_log', sa.Column('id', sa.Integer(), nullable=False), sa.Column('webhook_id', sa.Integer(), nullable=False, comment='Associated webhook'), sa.Column('alert_id', sa.Integer(), nullable=False, comment='Associated alert'), sa.Column('status', sa.String(20), nullable=True, comment='Delivery status: success, failed, retrying'), sa.Column('response_code', sa.Integer(), nullable=True, comment='HTTP response code'), sa.Column('response_body', sa.Text(), nullable=True, comment='Response body from webhook'), sa.Column('error_message', sa.Text(), nullable=True, comment='Error message if failed'), sa.Column('attempt_number', sa.Integer(), nullable=True, comment='Which attempt this was'), sa.Column('delivered_at', sa.DateTime(), nullable=False, comment='Delivery timestamp'), sa.ForeignKeyConstraint(['webhook_id'], ['webhooks.id'], ), sa.ForeignKeyConstraint(['alert_id'], ['alerts.id'], ), sa.PrimaryKeyConstraint('id') ) # Create indexes for webhook_delivery_log op.create_index('idx_webhook_delivery_alert_id', 'webhook_delivery_log', ['alert_id']) op.create_index('idx_webhook_delivery_webhook_id', 'webhook_delivery_log', ['webhook_id']) op.create_index('idx_webhook_delivery_status', 'webhook_delivery_log', ['status']) def downgrade(): """Remove Phase 5 alert enhancements.""" # Drop webhook_delivery_log table and its indexes op.drop_index('idx_webhook_delivery_status', table_name='webhook_delivery_log') op.drop_index('idx_webhook_delivery_webhook_id', table_name='webhook_delivery_log') op.drop_index('idx_webhook_delivery_alert_id', table_name='webhook_delivery_log') op.drop_table('webhook_delivery_log') # Drop webhooks table op.drop_table('webhooks') # Remove enhancements from alerts table with op.batch_alter_table('alerts') as batch_op: batch_op.drop_index('idx_alerts_acknowledged') batch_op.drop_index('idx_alerts_rule_id') batch_op.drop_constraint('fk_alerts_rule_id', type_='foreignkey') batch_op.drop_column('acknowledged_by') batch_op.drop_column('acknowledged_at') batch_op.drop_column('acknowledged') batch_op.drop_column('webhook_sent_at') batch_op.drop_column('webhook_sent') batch_op.drop_column('rule_id') # Remove enhancements from alert_rules table with op.batch_alter_table('alert_rules') as batch_op: batch_op.drop_column('updated_at') batch_op.drop_column('config_file') batch_op.drop_column('filter_conditions') batch_op.drop_column('severity') batch_op.drop_column('webhook_enabled') batch_op.drop_column('name')