Adds complete super_admin panel with 9 pages and account owner categories page. Backend includes 5 new DB tables, ~25 API endpoints, settings manager with in-memory cache, and 29 integration tests. Frontend includes reusable admin components (DataTable, Pagination, ActionMenu, etc.) with code-split lazy loading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
103 lines
5.1 KiB
Python
103 lines
5.1 KiB
Python
"""add admin panel tables
|
|
|
|
Revision ID: 026
|
|
Revises: 025
|
|
Create Date: 2026-02-08
|
|
|
|
Creates tables for admin panel:
|
|
- account_limit_overrides: Per-account plan limit overrides
|
|
- feature_flags: Feature flag definitions
|
|
- plan_feature_defaults: Which features each plan gets
|
|
- account_feature_overrides: Per-account feature exceptions
|
|
- platform_settings: Runtime configuration storage
|
|
"""
|
|
|
|
revision = "026"
|
|
down_revision = "025"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.dialects import postgresql
|
|
|
|
|
|
def upgrade() -> None:
|
|
# Account limit overrides
|
|
op.create_table(
|
|
"account_limit_overrides",
|
|
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
|
|
sa.Column("account_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("accounts.id", ondelete="CASCADE"), unique=True, nullable=False),
|
|
sa.Column("override_max_trees", sa.Integer(), nullable=True),
|
|
sa.Column("override_max_sessions_per_month", sa.Integer(), nullable=True),
|
|
sa.Column("override_max_users", sa.Integer(), nullable=True),
|
|
sa.Column("note", sa.Text(), nullable=True),
|
|
sa.Column("created_by_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id"), nullable=False),
|
|
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
|
)
|
|
op.create_index("ix_account_limit_overrides_account_id", "account_limit_overrides", ["account_id"])
|
|
|
|
# Feature flags
|
|
op.create_table(
|
|
"feature_flags",
|
|
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
|
|
sa.Column("flag_key", sa.String(100), unique=True, nullable=False),
|
|
sa.Column("display_name", sa.String(255), nullable=False),
|
|
sa.Column("description", sa.Text(), nullable=True),
|
|
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
|
)
|
|
|
|
# Plan feature defaults
|
|
op.create_table(
|
|
"plan_feature_defaults",
|
|
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
|
|
sa.Column("plan", sa.String(50), sa.ForeignKey("plan_limits.plan"), nullable=False),
|
|
sa.Column("flag_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("feature_flags.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("enabled", sa.Boolean(), server_default=sa.text("false"), nullable=False),
|
|
sa.UniqueConstraint("plan", "flag_id", name="uq_plan_feature_defaults_plan_flag"),
|
|
)
|
|
op.create_index("ix_plan_feature_defaults_plan", "plan_feature_defaults", ["plan"])
|
|
|
|
# Account feature overrides
|
|
op.create_table(
|
|
"account_feature_overrides",
|
|
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
|
|
sa.Column("account_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("flag_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("feature_flags.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("enabled", sa.Boolean(), nullable=False),
|
|
sa.Column("note", sa.Text(), nullable=True),
|
|
sa.Column("created_by_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id"), nullable=True),
|
|
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
|
sa.UniqueConstraint("account_id", "flag_id", name="uq_account_feature_overrides_account_flag"),
|
|
)
|
|
op.create_index("ix_account_feature_overrides_account_id", "account_feature_overrides", ["account_id"])
|
|
|
|
# Platform settings
|
|
op.create_table(
|
|
"platform_settings",
|
|
sa.Column("setting_key", sa.String(100), primary_key=True),
|
|
sa.Column("setting_value", sa.Text(), nullable=True),
|
|
sa.Column("data_type", sa.String(20), nullable=False, server_default="string"),
|
|
sa.Column("is_sensitive", sa.Boolean(), server_default=sa.text("false"), nullable=False),
|
|
sa.Column("updated_by_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id"), nullable=True),
|
|
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
|
)
|
|
|
|
# Seed default platform settings
|
|
op.execute(
|
|
"INSERT INTO platform_settings (setting_key, setting_value, data_type) VALUES "
|
|
"('maintenance_mode', 'false', 'boolean'), "
|
|
"('maintenance_message', 'We''re performing scheduled maintenance. We''ll be back soon!', 'string')"
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_table("platform_settings")
|
|
op.drop_table("account_feature_overrides")
|
|
op.drop_table("plan_feature_defaults")
|
|
op.drop_table("feature_flags")
|
|
op.drop_table("account_limit_overrides")
|