diff --git a/alembic/versions/0019_rbac_groups.py b/alembic/versions/0019_rbac_groups.py index cc32318..2136f1f 100644 --- a/alembic/versions/0019_rbac_groups.py +++ b/alembic/versions/0019_rbac_groups.py @@ -67,29 +67,35 @@ def upgrade() -> None: # ── Seed default roles per tenant ── # For each tenant, create admin/editor/viewer role records if they don't exist - op.execute(""" - INSERT INTO roles (id, tenant_id, name, permissions, denied_permissions, field_permissions, permission_version, created_at, updated_at) - SELECT - gen_random_uuid(), - t.id, - r.role_name, - r.permissions::jsonb, - '[]'::jsonb, - '{}'::jsonb, - 1, - now(), - now() - FROM tenants t - CROSS JOIN (VALUES - ('admin', '{"*:*": true}'::json), - ('editor', '{"core:*:read": true, "core:*:write": true, "core:*:create": true}'::json), - ('viewer', '{"core:*:read": true}'::json) - ) AS r(role_name, permissions) - WHERE NOT EXISTS ( - SELECT 1 FROM roles ro - WHERE ro.tenant_id = t.id AND ro.name = r.role_name + # Use sa.text() with bindparams to avoid SQLAlchemy interpreting :read/:write as bind params + for role_name, perms_json in [ + ("admin", '{"*:*": true}'), + ("editor", '{"core:*:read": true, "core:*:write": true, "core:*:create": true}'), + ("viewer", '{"core:*:read": true}'), + ]: + op.execute( + sa.text(""" + INSERT INTO roles (id, tenant_id, name, permissions, denied_permissions, field_permissions, permission_version, created_at, updated_at) + SELECT + gen_random_uuid(), + t.id, + :role_name, + :perms::jsonb, + '[]'::jsonb, + '{}'::jsonb, + 1, + now(), + now() + FROM tenants t + WHERE NOT EXISTS ( + SELECT 1 FROM roles ro + WHERE ro.tenant_id = t.id AND ro.name = :role_name + ) + """).bindparams( + sa.bindparam("role_name", value=role_name), + sa.bindparam("perms", value=perms_json), + ) ) - """) # ── Migrate existing user.role_id to user_tenants.role_id ── # For each user_tenants row, set role_id from users table if the user has one