Files
leocrm/alembic/versions/0096_workspace_tenant_integrity.py
T
Agent Zero 3eb11b1745 Phase 1: Migrationsaudit + Forward-Migrationen 0093-0096
Audit (docs/migration_history_audit.md):
- files.size_bytes: INTEGER (Alembic) vs BIGINT (Produktion/Plugin)
- GIN-Indizes: Fehlendes USING GIN in Alembic 0002
- guest_users: ix_guest_users_email_tenant fehlt UNIQUE in Alembic 0059
- plugins.name: Doppelter Unique-Index in Produktion

Forward-Migrationen:
- 0093: files.size_bytes INTEGER → BIGINT
- 0094: GIN-Indizes reparieren + plugins.name doppelten Index entfernen
- 0095: guest_users email+tenant_id UNIQUE INDEX (mit Dubletten-Check)
- 0096: Workspace tenant_integrity (tenant-bound FKs)

Tests: 41/41 bestanden (17 Workspace + 24 Command)
Alembic Head: 0096
2026-08-03 13:29:16 +02:00

94 lines
3.5 KiB
Python

"""Workspace tenant integrity constraints.
Plan 4.3: Add tenant-bound foreign keys to workspace child tables.
- workspaces: UNIQUE (tenant_id, id)
- workspace_modules: FK (tenant_id, workspace_id) → workspaces (tenant_id, id)
- workspace_widgets: FK (tenant_id, workspace_id) → workspaces (tenant_id, id)
- workspace_users: FK (tenant_id, workspace_id) → workspaces (tenant_id, id)
- workspace_users: FK (tenant_id, user_id) → user_tenants (tenant_id, user_id)
Revision ID: 0096
Revises: 0095
"""
from __future__ import annotations
from alembic import op
revision = "0096"
down_revision = "0095"
branch_labels = None
depends_on = None
def upgrade() -> None:
# 1. Add UNIQUE (tenant_id, id) on workspaces
op.execute(
"CREATE UNIQUE INDEX IF NOT EXISTS uq_workspaces_tenant_id "
"ON workspaces (tenant_id, id)"
)
# 2. Drop existing FKs on workspace_modules (workspace_id → workspaces.id)
# and replace with tenant-bound FK
op.execute("ALTER TABLE workspace_modules DROP CONSTRAINT IF EXISTS workspace_modules_workspace_id_fkey")
op.execute(
"ALTER TABLE workspace_modules "
"ADD CONSTRAINT fk_wm_tenant_workspace "
"FOREIGN KEY (tenant_id, workspace_id) "
"REFERENCES workspaces (tenant_id, id) ON DELETE CASCADE"
)
# 3. Drop existing FK on workspace_widgets and replace with tenant-bound FK
op.execute("ALTER TABLE workspace_widgets DROP CONSTRAINT IF EXISTS workspace_widgets_workspace_id_fkey")
op.execute(
"ALTER TABLE workspace_widgets "
"ADD CONSTRAINT fk_ww_tenant_workspace "
"FOREIGN KEY (tenant_id, workspace_id) "
"REFERENCES workspaces (tenant_id, id) ON DELETE CASCADE"
)
# 4. Drop existing FK on workspace_users and replace with tenant-bound FK
op.execute("ALTER TABLE workspace_users DROP CONSTRAINT IF EXISTS workspace_users_workspace_id_fkey")
op.execute(
"ALTER TABLE workspace_users "
"ADD CONSTRAINT fk_wu_tenant_workspace "
"FOREIGN KEY (tenant_id, workspace_id) "
"REFERENCES workspaces (tenant_id, id) ON DELETE CASCADE"
)
# 5. Add FK on workspace_users (tenant_id, user_id) → user_tenants (tenant_id, user_id)
op.execute(
"ALTER TABLE workspace_users "
"ADD CONSTRAINT fk_wu_tenant_user "
"FOREIGN KEY (tenant_id, user_id) "
"REFERENCES user_tenants (tenant_id, user_id) ON DELETE CASCADE"
)
def downgrade() -> None:
# Remove tenant-bound FKs, restore simple FKs
op.execute("ALTER TABLE workspace_users DROP CONSTRAINT IF EXISTS fk_wu_tenant_user")
op.execute("ALTER TABLE workspace_users DROP CONSTRAINT IF EXISTS fk_wu_tenant_workspace")
op.execute(
"ALTER TABLE workspace_users "
"ADD CONSTRAINT workspace_users_workspace_id_fkey "
"FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE"
)
op.execute("ALTER TABLE workspace_widgets DROP CONSTRAINT IF EXISTS fk_ww_tenant_workspace")
op.execute(
"ALTER TABLE workspace_widgets "
"ADD CONSTRAINT workspace_widgets_workspace_id_fkey "
"FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE"
)
op.execute("ALTER TABLE workspace_modules DROP CONSTRAINT IF EXISTS fk_wm_tenant_workspace")
op.execute(
"ALTER TABLE workspace_modules "
"ADD CONSTRAINT workspace_modules_workspace_id_fkey "
"FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE"
)
op.execute("DROP INDEX IF EXISTS uq_workspaces_tenant_id")