chore: consolidate platform split checks
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Module Matrix / module-matrix (push) Has been cancelled

This commit is contained in:
2026-07-10 12:51:19 +02:00
parent 150b720f12
commit 635d25c74c
216 changed files with 23336 additions and 4077 deletions

View File

@@ -7,6 +7,7 @@ from sqlalchemy import engine_from_config, pool
from govoplan_access.backend.db import models as access_models # noqa: F401 - populate access metadata
from govoplan_core.admin import models as core_admin_models # noqa: F401 - populate core admin metadata
from govoplan_core.core import change_sequence as core_change_sequence_models # noqa: F401 - populate core metadata
from govoplan_core.core.migrations import migration_metadata_plan
from govoplan_core.db.base import Base
from govoplan_core.server.default_config import get_server_config

View File

@@ -94,10 +94,10 @@ def _scrub_policy_column(table_name: str) -> None:
def upgrade() -> None:
inspector = sa.inspect(op.get_bind())
tables = set(inspector.get_table_names())
for table_name in ("system_settings", "tenants"):
for table_name in ("core_system_settings", "tenancy_tenants"):
if table_name in tables and "settings" in {column["name"] for column in inspector.get_columns(table_name)}:
_scrub_settings_table(table_name)
for table_name in ("users", "groups", "campaigns"):
for table_name in ("access_users", "access_groups", "campaigns"):
if table_name in tables and "mail_profile_policy" in {column["name"] for column in inspector.get_columns(table_name)}:
_scrub_policy_column(table_name)

View File

@@ -30,7 +30,7 @@ def upgrade() -> None:
batch_op.add_column(sa.Column("locked_by_user_id", sa.String(length=36), nullable=True))
batch_op.create_foreign_key(
op.f("fk_campaign_versions_locked_by_user_id_users"),
"users",
"access_users",
["locked_by_user_id"],
["id"],
ondelete="SET NULL",

View File

@@ -17,67 +17,67 @@ depends_on = None
def upgrade() -> None:
with op.batch_alter_table("users") as batch_op:
with op.batch_alter_table("access_users") as batch_op:
batch_op.add_column(sa.Column("auth_provider", sa.String(length=50), nullable=False, server_default="local"))
batch_op.add_column(sa.Column("password_hash", sa.String(length=500), nullable=True))
batch_op.add_column(sa.Column("last_login_at", sa.DateTime(timezone=True), nullable=True))
op.create_table(
"user_group_memberships",
"access_user_group_memberships",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("tenant_id", sa.String(length=36), nullable=False),
sa.Column("user_id", sa.String(length=36), nullable=False),
sa.Column("group_id", sa.String(length=36), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["group_id"], ["groups.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["user_id"], ["access_users.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["group_id"], ["access_groups.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("tenant_id", "user_id", "group_id", name="uq_user_group_memberships"),
)
op.create_index(op.f("ix_user_group_memberships_tenant_id"), "user_group_memberships", ["tenant_id"])
op.create_index(op.f("ix_user_group_memberships_user_id"), "user_group_memberships", ["user_id"])
op.create_index(op.f("ix_user_group_memberships_group_id"), "user_group_memberships", ["group_id"])
op.create_index(op.f("ix_access_user_group_memberships_tenant_id"), "access_user_group_memberships", ["tenant_id"])
op.create_index(op.f("ix_access_user_group_memberships_user_id"), "access_user_group_memberships", ["user_id"])
op.create_index(op.f("ix_access_user_group_memberships_group_id"), "access_user_group_memberships", ["group_id"])
op.create_table(
"user_role_assignments",
"access_user_role_assignments",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("tenant_id", sa.String(length=36), nullable=False),
sa.Column("user_id", sa.String(length=36), nullable=False),
sa.Column("role_id", sa.String(length=36), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["role_id"], ["roles.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["user_id"], ["access_users.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["role_id"], ["access_roles.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("tenant_id", "user_id", "role_id", name="uq_user_role_assignments"),
)
op.create_index(op.f("ix_user_role_assignments_tenant_id"), "user_role_assignments", ["tenant_id"])
op.create_index(op.f("ix_user_role_assignments_user_id"), "user_role_assignments", ["user_id"])
op.create_index(op.f("ix_user_role_assignments_role_id"), "user_role_assignments", ["role_id"])
op.create_index(op.f("ix_access_user_role_assignments_tenant_id"), "access_user_role_assignments", ["tenant_id"])
op.create_index(op.f("ix_access_user_role_assignments_user_id"), "access_user_role_assignments", ["user_id"])
op.create_index(op.f("ix_access_user_role_assignments_role_id"), "access_user_role_assignments", ["role_id"])
op.create_table(
"group_role_assignments",
"access_group_role_assignments",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("tenant_id", sa.String(length=36), nullable=False),
sa.Column("group_id", sa.String(length=36), nullable=False),
sa.Column("role_id", sa.String(length=36), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["group_id"], ["groups.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["role_id"], ["roles.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["group_id"], ["access_groups.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["role_id"], ["access_roles.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("tenant_id", "group_id", "role_id", name="uq_group_role_assignments"),
)
op.create_index(op.f("ix_group_role_assignments_tenant_id"), "group_role_assignments", ["tenant_id"])
op.create_index(op.f("ix_group_role_assignments_group_id"), "group_role_assignments", ["group_id"])
op.create_index(op.f("ix_group_role_assignments_role_id"), "group_role_assignments", ["role_id"])
op.create_index(op.f("ix_access_group_role_assignments_tenant_id"), "access_group_role_assignments", ["tenant_id"])
op.create_index(op.f("ix_access_group_role_assignments_group_id"), "access_group_role_assignments", ["group_id"])
op.create_index(op.f("ix_access_group_role_assignments_role_id"), "access_group_role_assignments", ["role_id"])
op.create_table(
"auth_sessions",
"access_auth_sessions",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("tenant_id", sa.String(length=36), nullable=False),
sa.Column("user_id", sa.String(length=36), nullable=False),
@@ -89,42 +89,42 @@ def upgrade() -> None:
sa.Column("ip_address", sa.String(length=100), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["user_id"], ["access_users.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("token_hash"),
)
op.create_index(op.f("ix_auth_sessions_tenant_id"), "auth_sessions", ["tenant_id"])
op.create_index(op.f("ix_auth_sessions_user_id"), "auth_sessions", ["user_id"])
op.create_index(op.f("ix_auth_sessions_token_hash"), "auth_sessions", ["token_hash"])
op.create_index(op.f("ix_auth_sessions_expires_at"), "auth_sessions", ["expires_at"])
op.create_index(op.f("ix_auth_sessions_revoked_at"), "auth_sessions", ["revoked_at"])
op.create_index(op.f("ix_access_auth_sessions_tenant_id"), "access_auth_sessions", ["tenant_id"])
op.create_index(op.f("ix_access_auth_sessions_user_id"), "access_auth_sessions", ["user_id"])
op.create_index(op.f("ix_access_auth_sessions_token_hash"), "access_auth_sessions", ["token_hash"])
op.create_index(op.f("ix_access_auth_sessions_expires_at"), "access_auth_sessions", ["expires_at"])
op.create_index(op.f("ix_access_auth_sessions_revoked_at"), "access_auth_sessions", ["revoked_at"])
def downgrade() -> None:
op.drop_index(op.f("ix_auth_sessions_revoked_at"), table_name="auth_sessions")
op.drop_index(op.f("ix_auth_sessions_expires_at"), table_name="auth_sessions")
op.drop_index(op.f("ix_auth_sessions_token_hash"), table_name="auth_sessions")
op.drop_index(op.f("ix_auth_sessions_user_id"), table_name="auth_sessions")
op.drop_index(op.f("ix_auth_sessions_tenant_id"), table_name="auth_sessions")
op.drop_table("auth_sessions")
op.drop_index(op.f("ix_access_auth_sessions_revoked_at"), table_name="access_auth_sessions")
op.drop_index(op.f("ix_access_auth_sessions_expires_at"), table_name="access_auth_sessions")
op.drop_index(op.f("ix_access_auth_sessions_token_hash"), table_name="access_auth_sessions")
op.drop_index(op.f("ix_access_auth_sessions_user_id"), table_name="access_auth_sessions")
op.drop_index(op.f("ix_access_auth_sessions_tenant_id"), table_name="access_auth_sessions")
op.drop_table("access_auth_sessions")
op.drop_index(op.f("ix_group_role_assignments_role_id"), table_name="group_role_assignments")
op.drop_index(op.f("ix_group_role_assignments_group_id"), table_name="group_role_assignments")
op.drop_index(op.f("ix_group_role_assignments_tenant_id"), table_name="group_role_assignments")
op.drop_table("group_role_assignments")
op.drop_index(op.f("ix_access_group_role_assignments_role_id"), table_name="access_group_role_assignments")
op.drop_index(op.f("ix_access_group_role_assignments_group_id"), table_name="access_group_role_assignments")
op.drop_index(op.f("ix_access_group_role_assignments_tenant_id"), table_name="access_group_role_assignments")
op.drop_table("access_group_role_assignments")
op.drop_index(op.f("ix_user_role_assignments_role_id"), table_name="user_role_assignments")
op.drop_index(op.f("ix_user_role_assignments_user_id"), table_name="user_role_assignments")
op.drop_index(op.f("ix_user_role_assignments_tenant_id"), table_name="user_role_assignments")
op.drop_table("user_role_assignments")
op.drop_index(op.f("ix_access_user_role_assignments_role_id"), table_name="access_user_role_assignments")
op.drop_index(op.f("ix_access_user_role_assignments_user_id"), table_name="access_user_role_assignments")
op.drop_index(op.f("ix_access_user_role_assignments_tenant_id"), table_name="access_user_role_assignments")
op.drop_table("access_user_role_assignments")
op.drop_index(op.f("ix_user_group_memberships_group_id"), table_name="user_group_memberships")
op.drop_index(op.f("ix_user_group_memberships_user_id"), table_name="user_group_memberships")
op.drop_index(op.f("ix_user_group_memberships_tenant_id"), table_name="user_group_memberships")
op.drop_table("user_group_memberships")
op.drop_index(op.f("ix_access_user_group_memberships_group_id"), table_name="access_user_group_memberships")
op.drop_index(op.f("ix_access_user_group_memberships_user_id"), table_name="access_user_group_memberships")
op.drop_index(op.f("ix_access_user_group_memberships_tenant_id"), table_name="access_user_group_memberships")
op.drop_table("access_user_group_memberships")
with op.batch_alter_table("users") as batch_op:
with op.batch_alter_table("access_users") as batch_op:
batch_op.drop_column("last_login_at")
batch_op.drop_column("password_hash")
batch_op.drop_column("auth_provider")

View File

@@ -0,0 +1,78 @@
"""namespace platform-owned tables
Revision ID: 2e3f4a5b6c7d
Revises: 1b2c3d4e5f70
Create Date: 2026-07-09 00:00:00.000000
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
revision = "2e3f4a5b6c7d"
down_revision = "1b2c3d4e5f70"
branch_labels = None
depends_on = None
_TABLE_RENAMES = (
("tenants", "tenancy_tenants"),
("accounts", "access_accounts"),
("users", "access_users"),
("groups", "access_groups"),
("roles", "access_roles"),
("system_role_assignments", "access_system_role_assignments"),
("user_group_memberships", "access_user_group_memberships"),
("user_role_assignments", "access_user_role_assignments"),
("group_role_assignments", "access_group_role_assignments"),
("api_keys", "access_api_keys"),
("auth_sessions", "access_auth_sessions"),
("system_settings", "core_system_settings"),
("governance_templates", "admin_governance_templates"),
("governance_template_assignments", "admin_governance_template_assignments"),
)
def _row_count(bind: sa.Connection, table_name: str) -> int:
return int(bind.execute(sa.text(f"SELECT COUNT(*) FROM {table_name}")).scalar_one())
def _rename_tables(renames: tuple[tuple[str, str], ...]) -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = set(inspector.get_table_names())
old_tables_to_drop: set[str] = set()
new_tables_to_drop: set[str] = set()
for old_name, new_name in renames:
if old_name not in tables or new_name not in tables:
continue
if _row_count(bind, old_name) == 0:
old_tables_to_drop.add(old_name)
elif _row_count(bind, new_name) == 0:
new_tables_to_drop.add(new_name)
else:
raise RuntimeError(f"Cannot rename non-empty {old_name} over non-empty {new_name}")
for old_name, _new_name in reversed(renames):
if old_name in old_tables_to_drop:
op.drop_table(old_name)
tables.remove(old_name)
for _old_name, new_name in reversed(renames):
if new_name in new_tables_to_drop:
op.drop_table(new_name)
tables.remove(new_name)
for old_name, new_name in renames:
if old_name not in tables:
continue
op.rename_table(old_name, new_name)
tables.remove(old_name)
tables.add(new_name)
def upgrade() -> None:
_rename_tables(_TABLE_RENAMES)
def downgrade() -> None:
_rename_tables(tuple((new_name, old_name) for old_name, new_name in reversed(_TABLE_RENAMES)))

View File

@@ -32,7 +32,7 @@ def upgrade() -> None:
sa.Column("retained_until", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("tenant_id", "checksum_sha256", "size_bytes", name="uq_file_blobs_tenant_checksum_size"),
)
@@ -55,10 +55,10 @@ def upgrade() -> None:
sa.Column("metadata", sa.JSON(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["owner_group_id"], ["groups.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["owner_user_id"], ["users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["access_users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["owner_group_id"], ["access_groups.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["owner_user_id"], ["access_users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
for col in ["tenant_id", "owner_type", "owner_user_id", "owner_group_id", "current_version_id", "display_path", "filename", "created_by_user_id", "deleted_at"]:
@@ -80,9 +80,9 @@ def upgrade() -> None:
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["blob_id"], ["file_blobs.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["access_users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["file_asset_id"], ["file_assets.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("file_asset_id", "version_number", name="uq_file_versions_asset_number"),
)
@@ -101,9 +101,9 @@ def upgrade() -> None:
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["access_users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["file_asset_id"], ["file_assets.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("file_asset_id", "target_type", "target_id", "revoked_at", name="uq_file_shares_active_target"),
)
@@ -136,7 +136,7 @@ def upgrade() -> None:
sa.ForeignKeyConstraint(["file_asset_id"], ["file_assets.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["file_blob_id"], ["file_blobs.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["file_version_id"], ["file_versions.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("campaign_job_id", "file_version_id", "filename_used", "use_stage", name="uq_campaign_attachment_uses_job_file_stage"),
)

View File

@@ -0,0 +1,111 @@
"""add core change sequence
Revision ID: 3f4a5b6c7d8e
Revises: 2e3f4a5b6c7d
Create Date: 2026-07-09 00:00:00.000000
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
revision = "3f4a5b6c7d8e"
down_revision = "2e3f4a5b6c7d"
branch_labels = None
depends_on = None
def upgrade() -> None:
inspector = sa.inspect(op.get_bind())
if "core_change_sequence" not in inspector.get_table_names():
op.create_table(
"core_change_sequence",
sa.Column("id", sa.BigInteger().with_variant(sa.Integer(), "sqlite"), autoincrement=True, nullable=False),
sa.Column("tenant_id", sa.String(length=36), nullable=True),
sa.Column("module_id", sa.String(length=100), nullable=False),
sa.Column("collection", sa.String(length=150), nullable=False),
sa.Column("resource_type", sa.String(length=100), nullable=False),
sa.Column("resource_id", sa.String(length=255), nullable=False),
sa.Column("operation", sa.String(length=30), nullable=False),
sa.Column("actor_type", sa.String(length=30), nullable=True),
sa.Column("actor_id", sa.String(length=255), nullable=True),
sa.Column("payload", sa.JSON(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint("id", name=op.f("pk_core_change_sequence")),
)
inspector = sa.inspect(op.get_bind())
indexes = {item["name"] for item in inspector.get_indexes("core_change_sequence")}
for column in (
"tenant_id",
"module_id",
"collection",
"resource_type",
"resource_id",
"operation",
"actor_type",
"actor_id",
"created_at",
):
name = op.f(f"ix_core_change_sequence_{column}")
if name not in indexes:
op.create_index(name, "core_change_sequence", [column], unique=False)
for name, columns in (
("ix_core_change_sequence_tenant_module_id", ["tenant_id", "module_id", "id"]),
("ix_core_change_sequence_collection_id", ["collection", "id"]),
("ix_core_change_sequence_resource", ["module_id", "resource_type", "resource_id"]),
):
if name not in indexes:
op.create_index(name, "core_change_sequence", columns, unique=False)
inspector = sa.inspect(op.get_bind())
if "core_change_sequence_retention_floor" not in inspector.get_table_names():
op.create_table(
"core_change_sequence_retention_floor",
sa.Column("id", sa.BigInteger().with_variant(sa.Integer(), "sqlite"), autoincrement=True, nullable=False),
sa.Column("tenant_key", sa.String(length=36), nullable=False),
sa.Column("module_id", sa.String(length=100), nullable=False),
sa.Column("collection", sa.String(length=150), nullable=False),
sa.Column("min_valid_sequence", sa.BigInteger().with_variant(sa.Integer(), "sqlite"), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint("id", name=op.f("pk_core_change_sequence_retention_floor")),
sa.UniqueConstraint("tenant_key", "module_id", "collection", name="uq_core_change_sequence_retention_scope"),
)
indexes = {item["name"] for item in inspector.get_indexes("core_change_sequence_retention_floor")}
if "ix_core_change_sequence_retention_scope" not in indexes:
op.create_index(
"ix_core_change_sequence_retention_scope",
"core_change_sequence_retention_floor",
["tenant_key", "module_id", "collection"],
unique=False,
)
def downgrade() -> None:
inspector = sa.inspect(op.get_bind())
if "core_change_sequence_retention_floor" in inspector.get_table_names():
indexes = {item["name"] for item in inspector.get_indexes("core_change_sequence_retention_floor")}
if "ix_core_change_sequence_retention_scope" in indexes:
op.drop_index("ix_core_change_sequence_retention_scope", table_name="core_change_sequence_retention_floor")
op.drop_table("core_change_sequence_retention_floor")
if "core_change_sequence" not in inspector.get_table_names():
return
indexes = {item["name"] for item in inspector.get_indexes("core_change_sequence")}
for name in (
"ix_core_change_sequence_resource",
"ix_core_change_sequence_collection_id",
"ix_core_change_sequence_tenant_module_id",
op.f("ix_core_change_sequence_created_at"),
op.f("ix_core_change_sequence_actor_id"),
op.f("ix_core_change_sequence_actor_type"),
op.f("ix_core_change_sequence_operation"),
op.f("ix_core_change_sequence_resource_id"),
op.f("ix_core_change_sequence_resource_type"),
op.f("ix_core_change_sequence_collection"),
op.f("ix_core_change_sequence_module_id"),
op.f("ix_core_change_sequence_tenant_id"),
):
if name in indexes:
op.drop_index(name, table_name="core_change_sequence")
op.drop_table("core_change_sequence")

View File

@@ -31,10 +31,10 @@ def upgrade() -> None:
sa.Column("metadata", sa.JSON(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["owner_group_id"], ["groups.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["owner_user_id"], ["users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["access_users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["owner_group_id"], ["access_groups.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["owner_user_id"], ["access_users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
for col in ["tenant_id", "owner_type", "owner_user_id", "owner_group_id", "path", "created_by_user_id", "deleted_at"]:

View File

@@ -24,7 +24,7 @@ def upgrade() -> None:
batch_op.add_column(sa.Column("user_locked_by_user_id", sa.String(length=36), nullable=True))
batch_op.create_foreign_key(
"fk_campaign_versions_user_locked_by_user_id_users",
"users",
"access_users",
["user_locked_by_user_id"],
["id"],
ondelete="SET NULL",

View File

@@ -62,25 +62,25 @@ def upgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = set(inspector.get_table_names())
user_columns = {column["name"] for column in inspector.get_columns("users")}
user_columns = {column["name"] for column in inspector.get_columns("access_users")}
# Base.metadata.create_all() from a newer application can create brand-new
# tables while leaving existing tables unaltered. Repair that known drift by
# removing only empty, unreferenced administration tables before applying the
# real migration. A non-empty table is never guessed at or discarded.
if "account_id" not in user_columns and "system_role_assignments" in tables:
count = bind.execute(sa.text("SELECT COUNT(*) FROM system_role_assignments")).scalar_one()
if "account_id" not in user_columns and "access_system_role_assignments" in tables:
count = bind.execute(sa.text("SELECT COUNT(*) FROM access_system_role_assignments")).scalar_one()
if count:
raise RuntimeError("Cannot reconcile non-empty create_all system_role_assignments table")
op.drop_table("system_role_assignments")
tables.remove("system_role_assignments")
if "account_id" not in user_columns and "accounts" in tables:
count = bind.execute(sa.text("SELECT COUNT(*) FROM accounts")).scalar_one()
op.drop_table("access_system_role_assignments")
tables.remove("access_system_role_assignments")
if "account_id" not in user_columns and "access_accounts" in tables:
count = bind.execute(sa.text("SELECT COUNT(*) FROM access_accounts")).scalar_one()
if count:
raise RuntimeError("Cannot reconcile non-empty create_all accounts table")
op.drop_table("accounts")
op.drop_table("access_accounts")
op.create_table(
"accounts",
"access_accounts",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("email", sa.String(length=320), nullable=False),
sa.Column("normalized_email", sa.String(length=320), nullable=False),
@@ -95,29 +95,29 @@ def upgrade() -> None:
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("normalized_email", name="uq_accounts_normalized_email"),
)
op.create_index(op.f("ix_accounts_normalized_email"), "accounts", ["normalized_email"])
op.create_index(op.f("ix_access_accounts_normalized_email"), "access_accounts", ["normalized_email"])
with op.batch_alter_table("tenants") as batch_op:
with op.batch_alter_table("tenancy_tenants") as batch_op:
batch_op.add_column(sa.Column("description", sa.Text(), nullable=True))
batch_op.add_column(sa.Column("default_locale", sa.String(length=20), nullable=False, server_default="en"))
batch_op.add_column(sa.Column("settings", sa.JSON(), nullable=False, server_default="{}"))
with op.batch_alter_table("groups") as batch_op:
with op.batch_alter_table("access_groups") as batch_op:
batch_op.add_column(sa.Column("description", sa.Text(), nullable=True))
batch_op.add_column(sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()))
with op.batch_alter_table("roles") as batch_op:
with op.batch_alter_table("access_roles") as batch_op:
batch_op.add_column(sa.Column("description", sa.Text(), nullable=True))
batch_op.add_column(sa.Column("is_builtin", sa.Boolean(), nullable=False, server_default=sa.false()))
batch_op.add_column(sa.Column("is_assignable", sa.Boolean(), nullable=False, server_default=sa.true()))
with op.batch_alter_table("users") as batch_op:
with op.batch_alter_table("access_users") as batch_op:
batch_op.add_column(sa.Column("account_id", sa.String(length=36), nullable=True))
with op.batch_alter_table("auth_sessions") as batch_op:
with op.batch_alter_table("access_auth_sessions") as batch_op:
batch_op.add_column(sa.Column("account_id", sa.String(length=36), nullable=True))
users = bind.execute(sa.text(
"SELECT id, tenant_id, email, display_name, is_active, is_tenant_admin, auth_provider, password_hash, last_login_at, created_at, updated_at FROM users ORDER BY created_at, id"
"SELECT id, tenant_id, email, display_name, is_active, is_tenant_admin, auth_provider, password_hash, last_login_at, created_at, updated_at FROM access_users ORDER BY created_at, id"
)).mappings().all()
accounts_by_email: dict[str, str] = {}
@@ -155,7 +155,7 @@ def upgrade() -> None:
first_system_owner_account_id = account_id
accounts_table = sa.table(
"accounts",
"access_accounts",
sa.column("id", sa.String), sa.column("email", sa.String), sa.column("normalized_email", sa.String),
sa.column("display_name", sa.String), sa.column("is_active", sa.Boolean), sa.column("auth_provider", sa.String),
sa.column("password_hash", sa.String), sa.column("password_reset_required", sa.Boolean),
@@ -166,54 +166,54 @@ def upgrade() -> None:
bind.execute(accounts_table.insert(), list(account_rows.values()))
for row in users:
bind.execute(
sa.text("UPDATE users SET account_id = :account_id WHERE id = :user_id"),
sa.text("UPDATE access_users SET account_id = :account_id WHERE id = :user_id"),
{"account_id": accounts_by_email[_normalize_email(row["email"])], "user_id": row["id"]},
)
bind.execute(sa.text(
"UPDATE auth_sessions SET account_id = (SELECT users.account_id FROM users WHERE users.id = auth_sessions.user_id)"
"UPDATE access_auth_sessions SET account_id = (SELECT access_users.account_id FROM access_users WHERE access_users.id = access_auth_sessions.user_id)"
))
with op.batch_alter_table("users") as batch_op:
with op.batch_alter_table("access_users") as batch_op:
batch_op.alter_column("account_id", existing_type=sa.String(length=36), nullable=False)
batch_op.create_foreign_key("fk_users_account_id_accounts", "accounts", ["account_id"], ["id"], ondelete="CASCADE")
batch_op.create_foreign_key("fk_users_account_id_accounts", "access_accounts", ["account_id"], ["id"], ondelete="CASCADE")
batch_op.create_unique_constraint("uq_users_tenant_account", ["tenant_id", "account_id"])
op.create_index(op.f("ix_users_account_id"), "users", ["account_id"])
op.create_index(op.f("ix_access_users_account_id"), "access_users", ["account_id"])
with op.batch_alter_table("auth_sessions") as batch_op:
with op.batch_alter_table("access_auth_sessions") as batch_op:
batch_op.alter_column("account_id", existing_type=sa.String(length=36), nullable=False)
batch_op.create_foreign_key("fk_auth_sessions_account_id_accounts", "accounts", ["account_id"], ["id"], ondelete="CASCADE")
op.create_index(op.f("ix_auth_sessions_account_id"), "auth_sessions", ["account_id"])
batch_op.create_foreign_key("fk_auth_sessions_account_id_accounts", "access_accounts", ["account_id"], ["id"], ondelete="CASCADE")
op.create_index(op.f("ix_access_auth_sessions_account_id"), "access_auth_sessions", ["account_id"])
op.create_table(
"system_role_assignments",
"access_system_role_assignments",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("account_id", sa.String(length=36), nullable=False),
sa.Column("role_id", sa.String(length=36), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["account_id"], ["accounts.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["role_id"], ["roles.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["account_id"], ["access_accounts.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["role_id"], ["access_roles.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("account_id", "role_id", name="uq_system_role_assignments"),
)
op.create_index(op.f("ix_system_role_assignments_account_id"), "system_role_assignments", ["account_id"])
op.create_index(op.f("ix_system_role_assignments_role_id"), "system_role_assignments", ["role_id"])
op.create_index(op.f("ix_access_system_role_assignments_account_id"), "access_system_role_assignments", ["account_id"])
op.create_index(op.f("ix_access_system_role_assignments_role_id"), "access_system_role_assignments", ["role_id"])
roles_table = sa.table(
"roles",
"access_roles",
sa.column("id", sa.String), sa.column("tenant_id", sa.String), sa.column("slug", sa.String),
sa.column("name", sa.String), sa.column("description", sa.Text), sa.column("permissions", sa.JSON),
sa.column("is_builtin", sa.Boolean), sa.column("is_assignable", sa.Boolean),
sa.column("created_at", sa.DateTime(timezone=True)), sa.column("updated_at", sa.DateTime(timezone=True)),
)
now = _now()
tenant_ids = [row[0] for row in bind.execute(sa.text("SELECT id FROM tenants")).all()]
tenant_ids = [row[0] for row in bind.execute(sa.text("SELECT id FROM tenancy_tenants")).all()]
definitions = _role_definitions()
tenant_role_ids: dict[tuple[str, str], str] = {}
for tenant_id in tenant_ids:
for slug, definition in definitions.items():
existing = bind.execute(
sa.text("SELECT id FROM roles WHERE tenant_id = :tenant_id AND slug = :slug"),
sa.text("SELECT id FROM access_roles WHERE tenant_id = :tenant_id AND slug = :slug"),
{"tenant_id": tenant_id, "slug": slug},
).scalar_one_or_none()
if existing:
@@ -253,7 +253,7 @@ def upgrade() -> None:
op.create_index(
"uq_roles_system_slug",
"roles",
"access_roles",
["slug"],
unique=True,
sqlite_where=sa.text("tenant_id IS NULL"),
@@ -267,11 +267,11 @@ def upgrade() -> None:
continue
owner_role_id = tenant_role_ids[(row["tenant_id"], "owner")]
exists = bind.execute(sa.text(
"SELECT 1 FROM user_role_assignments WHERE tenant_id = :tenant_id AND user_id = :user_id AND role_id = :role_id"
"SELECT 1 FROM access_user_role_assignments WHERE tenant_id = :tenant_id AND user_id = :user_id AND role_id = :role_id"
), {"tenant_id": row["tenant_id"], "user_id": row["id"], "role_id": owner_role_id}).first()
if not exists:
bind.execute(sa.text(
"INSERT INTO user_role_assignments (id, tenant_id, user_id, role_id, created_at, updated_at) VALUES (:id, :tenant_id, :user_id, :role_id, :created_at, :updated_at)"
"INSERT INTO access_user_role_assignments (id, tenant_id, user_id, role_id, created_at, updated_at) VALUES (:id, :tenant_id, :user_id, :role_id, :created_at, :updated_at)"
), {"id": str(uuid.uuid4()), "tenant_id": row["tenant_id"], "user_id": row["id"], "role_id": owner_role_id, "created_at": now, "updated_at": now})
# Bootstrap rule for existing installations: the earliest active legacy
@@ -284,40 +284,40 @@ def upgrade() -> None:
), None)
if first_system_owner_account_id:
bind.execute(sa.text(
"INSERT INTO system_role_assignments (id, account_id, role_id, created_at, updated_at) VALUES (:id, :account_id, :role_id, :created_at, :updated_at)"
"INSERT INTO access_system_role_assignments (id, account_id, role_id, created_at, updated_at) VALUES (:id, :account_id, :role_id, :created_at, :updated_at)"
), {"id": str(uuid.uuid4()), "account_id": first_system_owner_account_id, "role_id": system_owner_role_id, "created_at": now, "updated_at": now})
def downgrade() -> None:
op.drop_index("uq_roles_system_slug", table_name="roles")
op.drop_index(op.f("ix_system_role_assignments_role_id"), table_name="system_role_assignments")
op.drop_index(op.f("ix_system_role_assignments_account_id"), table_name="system_role_assignments")
op.drop_table("system_role_assignments")
op.drop_index("uq_roles_system_slug", table_name="access_roles")
op.drop_index(op.f("ix_access_system_role_assignments_role_id"), table_name="access_system_role_assignments")
op.drop_index(op.f("ix_access_system_role_assignments_account_id"), table_name="access_system_role_assignments")
op.drop_table("access_system_role_assignments")
op.drop_index(op.f("ix_auth_sessions_account_id"), table_name="auth_sessions")
with op.batch_alter_table("auth_sessions") as batch_op:
op.drop_index(op.f("ix_access_auth_sessions_account_id"), table_name="access_auth_sessions")
with op.batch_alter_table("access_auth_sessions") as batch_op:
batch_op.drop_constraint("fk_auth_sessions_account_id_accounts", type_="foreignkey")
batch_op.drop_column("account_id")
op.drop_index(op.f("ix_users_account_id"), table_name="users")
with op.batch_alter_table("users") as batch_op:
op.drop_index(op.f("ix_access_users_account_id"), table_name="access_users")
with op.batch_alter_table("access_users") as batch_op:
batch_op.drop_constraint("uq_users_tenant_account", type_="unique")
batch_op.drop_constraint("fk_users_account_id_accounts", type_="foreignkey")
batch_op.drop_column("account_id")
with op.batch_alter_table("roles") as batch_op:
with op.batch_alter_table("access_roles") as batch_op:
batch_op.drop_column("is_assignable")
batch_op.drop_column("is_builtin")
batch_op.drop_column("description")
with op.batch_alter_table("groups") as batch_op:
with op.batch_alter_table("access_groups") as batch_op:
batch_op.drop_column("is_active")
batch_op.drop_column("description")
with op.batch_alter_table("tenants") as batch_op:
with op.batch_alter_table("tenancy_tenants") as batch_op:
batch_op.drop_column("settings")
batch_op.drop_column("default_locale")
batch_op.drop_column("description")
op.drop_index(op.f("ix_accounts_normalized_email"), table_name="accounts")
op.drop_table("accounts")
op.drop_index(op.f("ix_access_accounts_normalized_email"), table_name="access_accounts")
op.drop_table("access_accounts")

View File

@@ -28,7 +28,7 @@ def upgrade() -> None:
tables = set(inspector.get_table_names())
# Reconcile only the empty create_all shape for the newly introduced tables.
for table_name in ("governance_template_assignments", "governance_templates", "system_settings"):
for table_name in ("admin_governance_template_assignments", "admin_governance_templates", "core_system_settings"):
if table_name in tables:
count = bind.execute(sa.text(f"SELECT COUNT(*) FROM {table_name}")).scalar_one()
if count:
@@ -36,7 +36,7 @@ def upgrade() -> None:
op.drop_table(table_name)
op.create_table(
"system_settings",
"core_system_settings",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("default_locale", sa.String(length=20), nullable=False, server_default="en"),
sa.Column("allow_tenant_custom_groups", sa.Boolean(), nullable=False, server_default=sa.true()),
@@ -50,16 +50,23 @@ def upgrade() -> None:
now = _now()
bind.execute(sa.text(
"""
INSERT INTO system_settings
INSERT INTO core_system_settings
(id, default_locale, allow_tenant_custom_groups, allow_tenant_custom_roles,
allow_tenant_api_keys, settings, created_at, updated_at)
VALUES
('global', 'en', 1, 1, 1, '{}', :created_at, :updated_at)
('global', 'en', :allow_tenant_custom_groups, :allow_tenant_custom_roles,
:allow_tenant_api_keys, '{}', :created_at, :updated_at)
"""
), {"created_at": now, "updated_at": now})
), {
"allow_tenant_custom_groups": True,
"allow_tenant_custom_roles": True,
"allow_tenant_api_keys": True,
"created_at": now,
"updated_at": now,
})
op.create_table(
"governance_templates",
"admin_governance_templates",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("kind", sa.String(length=20), nullable=False),
sa.Column("slug", sa.String(length=100), nullable=False),
@@ -72,51 +79,51 @@ def upgrade() -> None:
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("kind", "slug", name="uq_governance_templates_kind_slug"),
)
op.create_index(op.f("ix_governance_templates_kind"), "governance_templates", ["kind"])
op.create_index(op.f("ix_admin_governance_templates_kind"), "admin_governance_templates", ["kind"])
op.create_table(
"governance_template_assignments",
"admin_governance_template_assignments",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("template_id", sa.String(length=36), nullable=False),
sa.Column("tenant_id", sa.String(length=36), nullable=False),
sa.Column("mode", sa.String(length=20), nullable=False, server_default="available"),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["template_id"], ["governance_templates.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["template_id"], ["admin_governance_templates.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("template_id", "tenant_id", name="uq_governance_template_tenant"),
)
op.create_index(op.f("ix_governance_template_assignments_template_id"), "governance_template_assignments", ["template_id"])
op.create_index(op.f("ix_governance_template_assignments_tenant_id"), "governance_template_assignments", ["tenant_id"])
op.create_index(op.f("ix_admin_governance_template_assignments_template_id"), "admin_governance_template_assignments", ["template_id"])
op.create_index(op.f("ix_admin_governance_template_assignments_tenant_id"), "admin_governance_template_assignments", ["tenant_id"])
with op.batch_alter_table("tenants") as batch_op:
with op.batch_alter_table("tenancy_tenants") as batch_op:
batch_op.add_column(sa.Column("allow_custom_groups", sa.Boolean(), nullable=True))
batch_op.add_column(sa.Column("allow_custom_roles", sa.Boolean(), nullable=True))
batch_op.add_column(sa.Column("allow_api_keys", sa.Boolean(), nullable=True))
with op.batch_alter_table("groups") as batch_op:
with op.batch_alter_table("access_groups") as batch_op:
batch_op.add_column(sa.Column("system_template_id", sa.String(length=36), nullable=True))
batch_op.add_column(sa.Column("system_required", sa.Boolean(), nullable=False, server_default=sa.false()))
batch_op.create_foreign_key(
"fk_groups_system_template_id_governance_templates",
"governance_templates", ["system_template_id"], ["id"], ondelete="SET NULL",
"admin_governance_templates", ["system_template_id"], ["id"], ondelete="SET NULL",
)
op.create_index(op.f("ix_groups_system_template_id"), "groups", ["system_template_id"])
op.create_index(op.f("ix_access_groups_system_template_id"), "access_groups", ["system_template_id"])
with op.batch_alter_table("roles") as batch_op:
with op.batch_alter_table("access_roles") as batch_op:
batch_op.add_column(sa.Column("system_template_id", sa.String(length=36), nullable=True))
batch_op.add_column(sa.Column("system_required", sa.Boolean(), nullable=False, server_default=sa.false()))
batch_op.create_foreign_key(
"fk_roles_system_template_id_governance_templates",
"governance_templates", ["system_template_id"], ["id"], ondelete="SET NULL",
"admin_governance_templates", ["system_template_id"], ["id"], ondelete="SET NULL",
)
op.create_index(op.f("ix_roles_system_template_id"), "roles", ["system_template_id"])
op.create_index(op.f("ix_access_roles_system_template_id"), "access_roles", ["system_template_id"])
# Existing system owners use system:* and need no data change. Extend the
# read-only built-in auditor role to the newly introduced read scopes.
auditor = bind.execute(sa.text(
"SELECT id, permissions FROM roles WHERE tenant_id IS NULL AND slug = 'system_auditor'"
"SELECT id, permissions FROM access_roles WHERE tenant_id IS NULL AND slug = 'system_auditor'"
)).mappings().first()
if auditor:
raw_permissions = auditor["permissions"] or []
@@ -125,7 +132,7 @@ def upgrade() -> None:
if scope not in permissions:
permissions.append(scope)
roles_table = sa.table(
"roles",
"access_roles",
sa.column("id", sa.String),
sa.column("permissions", sa.JSON),
sa.column("updated_at", sa.DateTime(timezone=True)),
@@ -138,26 +145,26 @@ def upgrade() -> None:
def downgrade() -> None:
op.drop_index(op.f("ix_roles_system_template_id"), table_name="roles")
with op.batch_alter_table("roles") as batch_op:
op.drop_index(op.f("ix_access_roles_system_template_id"), table_name="access_roles")
with op.batch_alter_table("access_roles") as batch_op:
batch_op.drop_constraint("fk_roles_system_template_id_governance_templates", type_="foreignkey")
batch_op.drop_column("system_required")
batch_op.drop_column("system_template_id")
op.drop_index(op.f("ix_groups_system_template_id"), table_name="groups")
with op.batch_alter_table("groups") as batch_op:
op.drop_index(op.f("ix_access_groups_system_template_id"), table_name="access_groups")
with op.batch_alter_table("access_groups") as batch_op:
batch_op.drop_constraint("fk_groups_system_template_id_governance_templates", type_="foreignkey")
batch_op.drop_column("system_required")
batch_op.drop_column("system_template_id")
with op.batch_alter_table("tenants") as batch_op:
with op.batch_alter_table("tenancy_tenants") as batch_op:
batch_op.drop_column("allow_api_keys")
batch_op.drop_column("allow_custom_roles")
batch_op.drop_column("allow_custom_groups")
op.drop_index(op.f("ix_governance_template_assignments_tenant_id"), table_name="governance_template_assignments")
op.drop_index(op.f("ix_governance_template_assignments_template_id"), table_name="governance_template_assignments")
op.drop_table("governance_template_assignments")
op.drop_index(op.f("ix_governance_templates_kind"), table_name="governance_templates")
op.drop_table("governance_templates")
op.drop_table("system_settings")
op.drop_index(op.f("ix_admin_governance_template_assignments_tenant_id"), table_name="admin_governance_template_assignments")
op.drop_index(op.f("ix_admin_governance_template_assignments_template_id"), table_name="admin_governance_template_assignments")
op.drop_table("admin_governance_template_assignments")
op.drop_index(op.f("ix_admin_governance_templates_kind"), table_name="admin_governance_templates")
op.drop_table("admin_governance_templates")
op.drop_table("core_system_settings")

View File

@@ -148,9 +148,9 @@ def upgrade() -> None:
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["campaign_id"], ["campaigns.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["access_users.id"], ondelete="SET NULL"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("campaign_id", "target_type", "target_id", name="uq_campaign_share_target"),
)
@@ -160,32 +160,32 @@ def upgrade() -> None:
op.create_index("ix_campaign_shares_target_id", "campaign_shares", ["target_id"])
op.create_index("ix_campaign_shares_created_by_user_id", "campaign_shares", ["created_by_user_id"])
op.create_index("ix_campaign_shares_revoked_at", "campaign_shares", ["revoked_at"])
if "roles" not in tables:
if "access_roles" not in tables:
return
rows = bind.execute(sa.text("SELECT id, permissions FROM roles")).mappings().all()
rows = bind.execute(sa.text("SELECT id, permissions FROM access_roles")).mappings().all()
for row in rows:
bind.execute(
sa.text("UPDATE roles SET permissions = :permissions WHERE id = :id"),
sa.text("UPDATE access_roles SET permissions = :permissions WHERE id = :id"),
{"id": row["id"], "permissions": _json(_expand_legacy(_decode(row["permissions"])))},
)
for slug, permissions in TENANT_ROLE_PERMISSIONS.items():
bind.execute(
sa.text("UPDATE roles SET permissions = :permissions WHERE tenant_id IS NOT NULL AND slug = :slug AND is_builtin = TRUE"),
sa.text("UPDATE access_roles SET permissions = :permissions WHERE tenant_id IS NOT NULL AND slug = :slug AND is_builtin = TRUE"),
{"slug": slug, "permissions": _json(permissions)},
)
now = datetime.now(timezone.utc)
for slug, permissions in SYSTEM_ROLE_PERMISSIONS.items():
existing = bind.execute(
sa.text("SELECT id FROM roles WHERE tenant_id IS NULL AND slug = :slug"), {"slug": slug}
sa.text("SELECT id FROM access_roles WHERE tenant_id IS NULL AND slug = :slug"), {"slug": slug}
).first()
is_protected = slug == "system_owner"
if existing:
bind.execute(
sa.text(
"UPDATE roles SET permissions = :permissions, is_builtin = :is_builtin, is_assignable = TRUE "
"UPDATE access_roles SET permissions = :permissions, is_builtin = :is_builtin, is_assignable = TRUE "
"WHERE tenant_id IS NULL AND slug = :slug"
),
{"slug": slug, "permissions": _json(permissions), "is_builtin": is_protected},
@@ -199,7 +199,7 @@ def upgrade() -> None:
name, description = names[slug]
bind.execute(
sa.text(
"INSERT INTO roles (id, tenant_id, slug, name, description, permissions, is_builtin, is_assignable, "
"INSERT INTO access_roles (id, tenant_id, slug, name, description, permissions, is_builtin, is_assignable, "
"system_template_id, system_required, created_at, updated_at) "
"VALUES (:id, NULL, :slug, :name, :description, :permissions, :is_builtin, TRUE, NULL, FALSE, :created_at, :updated_at)"
),

View File

@@ -18,7 +18,7 @@ depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tenants',
op.create_table('tenancy_tenants',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('slug', sa.String(length=100), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
@@ -27,7 +27,7 @@ def upgrade() -> None:
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_tenants'))
)
op.create_index(op.f('ix_tenants_slug'), 'tenants', ['slug'], unique=True)
op.create_index(op.f('ix_tenancy_tenants_slug'), 'tenancy_tenants', ['slug'], unique=True)
op.create_table('attachment_blobs',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
@@ -38,25 +38,25 @@ def upgrade() -> None:
sa.Column('storage_key', sa.String(length=1000), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('fk_attachment_blobs_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenancy_tenants.id'], name=op.f('fk_attachment_blobs_tenant_id_tenants'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_attachment_blobs')),
sa.UniqueConstraint('tenant_id', 'sha256', name='uq_attachment_blobs_tenant_sha256')
)
op.create_index(op.f('ix_attachment_blobs_sha256'), 'attachment_blobs', ['sha256'], unique=False)
op.create_index(op.f('ix_attachment_blobs_tenant_id'), 'attachment_blobs', ['tenant_id'], unique=False)
op.create_table('groups',
op.create_table('access_groups',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('slug', sa.String(length=100), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('fk_groups_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenancy_tenants.id'], name=op.f('fk_groups_tenant_id_tenants'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_groups')),
sa.UniqueConstraint('tenant_id', 'slug', name='uq_groups_tenant_slug')
)
op.create_index(op.f('ix_groups_tenant_id'), 'groups', ['tenant_id'], unique=False)
op.create_table('roles',
op.create_index(op.f('ix_access_groups_tenant_id'), 'access_groups', ['tenant_id'], unique=False)
op.create_table('access_roles',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=True),
sa.Column('slug', sa.String(length=100), nullable=False),
@@ -64,12 +64,12 @@ def upgrade() -> None:
sa.Column('permissions', sa.JSON(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('fk_roles_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenancy_tenants.id'], name=op.f('fk_roles_tenant_id_tenants'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_roles')),
sa.UniqueConstraint('tenant_id', 'slug', name='uq_roles_tenant_slug')
)
op.create_index(op.f('ix_roles_tenant_id'), 'roles', ['tenant_id'], unique=False)
op.create_table('users',
op.create_index(op.f('ix_access_roles_tenant_id'), 'access_roles', ['tenant_id'], unique=False)
op.create_table('access_users',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('email', sa.String(length=320), nullable=False),
@@ -78,13 +78,13 @@ def upgrade() -> None:
sa.Column('is_tenant_admin', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('fk_users_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenancy_tenants.id'], name=op.f('fk_users_tenant_id_tenants'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_users')),
sa.UniqueConstraint('tenant_id', 'email', name='uq_users_tenant_email')
)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=False)
op.create_index(op.f('ix_users_tenant_id'), 'users', ['tenant_id'], unique=False)
op.create_table('api_keys',
op.create_index(op.f('ix_access_users_email'), 'access_users', ['email'], unique=False)
op.create_index(op.f('ix_access_users_tenant_id'), 'access_users', ['tenant_id'], unique=False)
op.create_table('access_api_keys',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('user_id', sa.String(length=36), nullable=False),
@@ -97,13 +97,13 @@ def upgrade() -> None:
sa.Column('revoked_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('fk_api_keys_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_api_keys_user_id_users'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenancy_tenants.id'], name=op.f('fk_api_keys_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['access_users.id'], name=op.f('fk_api_keys_user_id_users'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_api_keys'))
)
op.create_index(op.f('ix_api_keys_prefix'), 'api_keys', ['prefix'], unique=False)
op.create_index(op.f('ix_api_keys_tenant_id'), 'api_keys', ['tenant_id'], unique=False)
op.create_index(op.f('ix_api_keys_user_id'), 'api_keys', ['user_id'], unique=False)
op.create_index(op.f('ix_access_api_keys_prefix'), 'access_api_keys', ['prefix'], unique=False)
op.create_index(op.f('ix_access_api_keys_tenant_id'), 'access_api_keys', ['tenant_id'], unique=False)
op.create_index(op.f('ix_access_api_keys_user_id'), 'access_api_keys', ['user_id'], unique=False)
op.create_table('campaigns',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
@@ -115,8 +115,8 @@ def upgrade() -> None:
sa.Column('current_version_id', sa.String(length=36), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['created_by_user_id'], ['users.id'], name=op.f('fk_campaigns_created_by_user_id_users'), ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('fk_campaigns_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['created_by_user_id'], ['access_users.id'], name=op.f('fk_campaigns_created_by_user_id_users'), ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenancy_tenants.id'], name=op.f('fk_campaigns_tenant_id_tenants'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_campaigns')),
sa.UniqueConstraint('tenant_id', 'external_id', name='uq_campaigns_tenant_external_id')
)
@@ -138,8 +138,8 @@ def upgrade() -> None:
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['blob_id'], ['attachment_blobs.id'], name=op.f('fk_attachment_instances_blob_id_attachment_blobs'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['campaign_id'], ['campaigns.id'], name=op.f('fk_attachment_instances_campaign_id_campaigns'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['owner_user_id'], ['users.id'], name=op.f('fk_attachment_instances_owner_user_id_users'), ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('fk_attachment_instances_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['owner_user_id'], ['access_users.id'], name=op.f('fk_attachment_instances_owner_user_id_users'), ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenancy_tenants.id'], name=op.f('fk_attachment_instances_tenant_id_tenants'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_attachment_instances'))
)
op.create_index(op.f('ix_attachment_instances_blob_id'), 'attachment_instances', ['blob_id'], unique=False)
@@ -157,9 +157,9 @@ def upgrade() -> None:
sa.Column('details', sa.JSON(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['api_key_id'], ['api_keys.id'], name=op.f('fk_audit_log_api_key_id_api_keys'), ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('fk_audit_log_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_audit_log_user_id_users'), ondelete='SET NULL'),
sa.ForeignKeyConstraint(['api_key_id'], ['access_api_keys.id'], name=op.f('fk_audit_log_api_key_id_api_keys'), ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenancy_tenants.id'], name=op.f('fk_audit_log_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['access_users.id'], name=op.f('fk_audit_log_user_id_users'), ondelete='SET NULL'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_audit_log'))
)
op.create_index(op.f('ix_audit_log_action'), 'audit_log', ['action'], unique=False)
@@ -213,7 +213,7 @@ def upgrade() -> None:
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['campaign_id'], ['campaigns.id'], name=op.f('fk_campaign_jobs_campaign_id_campaigns'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['campaign_version_id'], ['campaign_versions.id'], name=op.f('fk_campaign_jobs_campaign_version_id_campaign_versions'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('fk_campaign_jobs_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenancy_tenants.id'], name=op.f('fk_campaign_jobs_tenant_id_tenants'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_campaign_jobs')),
sa.UniqueConstraint('campaign_version_id', 'entry_index', name='uq_campaign_jobs_version_entry')
)
@@ -243,7 +243,7 @@ def upgrade() -> None:
sa.ForeignKeyConstraint(['campaign_id'], ['campaigns.id'], name=op.f('fk_campaign_issues_campaign_id_campaigns'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['campaign_version_id'], ['campaign_versions.id'], name=op.f('fk_campaign_issues_campaign_version_id_campaign_versions'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['job_id'], ['campaign_jobs.id'], name=op.f('fk_campaign_issues_job_id_campaign_jobs'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('fk_campaign_issues_tenant_id_tenants'), ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenancy_tenants.id'], name=op.f('fk_campaign_issues_tenant_id_tenants'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_campaign_issues'))
)
op.create_index(op.f('ix_campaign_issues_campaign_id'), 'campaign_issues', ['campaign_id'], unique=False)
@@ -327,20 +327,20 @@ def downgrade() -> None:
op.drop_index(op.f('ix_campaigns_external_id'), table_name='campaigns')
op.drop_index(op.f('ix_campaigns_created_by_user_id'), table_name='campaigns')
op.drop_table('campaigns')
op.drop_index(op.f('ix_api_keys_user_id'), table_name='api_keys')
op.drop_index(op.f('ix_api_keys_tenant_id'), table_name='api_keys')
op.drop_index(op.f('ix_api_keys_prefix'), table_name='api_keys')
op.drop_table('api_keys')
op.drop_index(op.f('ix_users_tenant_id'), table_name='users')
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_table('users')
op.drop_index(op.f('ix_roles_tenant_id'), table_name='roles')
op.drop_table('roles')
op.drop_index(op.f('ix_groups_tenant_id'), table_name='groups')
op.drop_table('groups')
op.drop_index(op.f('ix_access_api_keys_user_id'), table_name='access_api_keys')
op.drop_index(op.f('ix_access_api_keys_tenant_id'), table_name='access_api_keys')
op.drop_index(op.f('ix_access_api_keys_prefix'), table_name='access_api_keys')
op.drop_table('access_api_keys')
op.drop_index(op.f('ix_access_users_tenant_id'), table_name='access_users')
op.drop_index(op.f('ix_access_users_email'), table_name='access_users')
op.drop_table('access_users')
op.drop_index(op.f('ix_access_roles_tenant_id'), table_name='access_roles')
op.drop_table('access_roles')
op.drop_index(op.f('ix_access_groups_tenant_id'), table_name='access_groups')
op.drop_table('access_groups')
op.drop_index(op.f('ix_attachment_blobs_tenant_id'), table_name='attachment_blobs')
op.drop_index(op.f('ix_attachment_blobs_sha256'), table_name='attachment_blobs')
op.drop_table('attachment_blobs')
op.drop_index(op.f('ix_tenants_slug'), table_name='tenants')
op.drop_table('tenants')
op.drop_index(op.f('ix_tenancy_tenants_slug'), table_name='tenancy_tenants')
op.drop_table('tenancy_tenants')
# ### end Alembic commands ###

View File

@@ -19,10 +19,10 @@ def upgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = set(inspector.get_table_names())
if "auth_sessions" in tables:
columns = {column["name"] for column in inspector.get_columns("auth_sessions")}
if "access_auth_sessions" in tables:
columns = {column["name"] for column in inspector.get_columns("access_auth_sessions")}
if "csrf_token_hash" not in columns:
op.add_column("auth_sessions", sa.Column("csrf_token_hash", sa.String(length=128), nullable=True))
op.add_column("access_auth_sessions", sa.Column("csrf_token_hash", sa.String(length=128), nullable=True))
if "mail_server_profiles" not in tables:
op.create_table(
"mail_server_profiles",
@@ -40,9 +40,9 @@ def upgrade() -> None:
sa.Column("updated_by_user_id", sa.String(length=36), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["updated_by_user_id"], ["users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["access_users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["updated_by_user_id"], ["access_users.id"], ondelete="SET NULL"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("tenant_id", "slug", name="uq_mail_server_profiles_tenant_slug"),
)
@@ -67,7 +67,7 @@ def downgrade() -> None:
except Exception:
pass
op.drop_table("mail_server_profiles")
if "auth_sessions" in inspector.get_table_names():
columns = {column["name"] for column in inspector.get_columns("auth_sessions")}
if "access_auth_sessions" in inspector.get_table_names():
columns = {column["name"] for column in inspector.get_columns("access_auth_sessions")}
if "csrf_token_hash" in columns:
op.drop_column("auth_sessions", "csrf_token_hash")
op.drop_column("access_auth_sessions", "csrf_token_hash")

View File

@@ -31,7 +31,7 @@ def upgrade() -> None:
inspector = sa.inspect(bind)
tables = set(inspector.get_table_names())
for table_name in ("users", "groups", "campaigns"):
for table_name in ("access_users", "access_groups", "campaigns"):
if table_name not in tables:
continue
columns = _columns(inspector, table_name)
@@ -83,6 +83,6 @@ def downgrade() -> None:
batch.drop_column("scope_type")
batch.alter_column("tenant_id", existing_type=sa.String(length=36), nullable=False)
for table_name in ("campaigns", "groups", "users"):
for table_name in ("campaigns", "access_groups", "access_users"):
if table_name in tables and "mail_profile_policy" in _columns(inspector, table_name):
op.drop_column(table_name, "mail_profile_policy")

View File

@@ -25,7 +25,7 @@ def upgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = set(inspector.get_table_names())
for table_name in ("users", "groups", "campaigns"):
for table_name in ("access_users", "access_groups", "campaigns"):
if table_name not in tables:
continue
if "settings" not in _columns(inspector, table_name):
@@ -36,6 +36,6 @@ def downgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = set(inspector.get_table_names())
for table_name in ("campaigns", "groups", "users"):
for table_name in ("campaigns", "access_groups", "access_users"):
if table_name in tables and "settings" in _columns(inspector, table_name):
op.drop_column(table_name, "settings")