"""Add platform modules table Revision ID: zc2m3n4o5p6q7 Revises: zb1l2m3n4o5p6 Create Date: 2026-01-26 Adds platform_modules junction table for tracking module enablement per platform: - Auditability: Track when modules were enabled/disabled and by whom - Configuration: Per-module settings specific to each platform - State tracking: Explicit enabled/disabled states with timestamps This replaces the simpler Platform.settings["enabled_modules"] JSON approach for better auditability and query capabilities. """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = "zc2m3n4o5p6q7" down_revision = "zb1l2m3n4o5p6" branch_labels = None depends_on = None def upgrade() -> None: # Create platform_modules table op.create_table( "platform_modules", sa.Column("id", sa.Integer(), nullable=False), sa.Column( "platform_id", sa.Integer(), nullable=False, comment="Platform this module configuration belongs to", ), sa.Column( "module_code", sa.String(50), nullable=False, comment="Module code (e.g., 'billing', 'inventory', 'orders')", ), sa.Column( "is_enabled", sa.Boolean(), nullable=False, server_default="true", comment="Whether this module is currently enabled for the platform", ), sa.Column( "enabled_at", sa.DateTime(timezone=True), nullable=True, comment="When the module was last enabled", ), sa.Column( "enabled_by_user_id", sa.Integer(), nullable=True, comment="User who enabled the module", ), sa.Column( "disabled_at", sa.DateTime(timezone=True), nullable=True, comment="When the module was last disabled", ), sa.Column( "disabled_by_user_id", sa.Integer(), nullable=True, comment="User who disabled the module", ), sa.Column( "config", sa.JSON(), nullable=False, server_default="{}", comment="Module-specific configuration for this platform", ), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.Column( "updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now(), ), # Primary key sa.PrimaryKeyConstraint("id"), # Foreign keys sa.ForeignKeyConstraint( ["platform_id"], ["platforms.id"], ondelete="CASCADE", ), sa.ForeignKeyConstraint( ["enabled_by_user_id"], ["users.id"], ondelete="SET NULL", ), sa.ForeignKeyConstraint( ["disabled_by_user_id"], ["users.id"], ondelete="SET NULL", ), # Unique constraint - one config per platform/module pair sa.UniqueConstraint("platform_id", "module_code", name="uq_platform_module"), ) # Create indexes for performance op.create_index( "idx_platform_module_platform_id", "platform_modules", ["platform_id"], ) op.create_index( "idx_platform_module_code", "platform_modules", ["module_code"], ) op.create_index( "idx_platform_module_enabled", "platform_modules", ["platform_id", "is_enabled"], ) def downgrade() -> None: # Drop indexes op.drop_index("idx_platform_module_enabled", table_name="platform_modules") op.drop_index("idx_platform_module_code", table_name="platform_modules") op.drop_index("idx_platform_module_platform_id", table_name="platform_modules") # Drop table op.drop_table("platform_modules")