Skip to content

Commit c7f2a51

Browse files
committed
chore(rbac): drop role from membership and invitation tables
1 parent 0108767 commit c7f2a51

File tree

13 files changed

+934
-137
lines changed

13 files changed

+934
-137
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""drop_role_from_membership_and_invitation_tables
2+
3+
Revision ID: 4bb7e59026f3
4+
Revises: 8b630b0a094e
5+
Create Date: 2026-01-29 14:55:11.408522
6+
7+
"""
8+
9+
from collections.abc import Sequence
10+
11+
import sqlalchemy as sa
12+
from sqlalchemy.dialects import postgresql
13+
14+
from alembic import op
15+
16+
# revision identifiers, used by Alembic.
17+
revision: str = "4bb7e59026f3"
18+
down_revision: str | None = "8b630b0a094e"
19+
branch_labels: str | Sequence[str] | None = None
20+
depends_on: str | Sequence[str] | None = None
21+
22+
23+
def upgrade() -> None:
24+
# ### commands auto generated by Alembic - please adjust! ###
25+
op.add_column("invitation", sa.Column("role_id", sa.UUID(), nullable=False))
26+
op.create_index(
27+
op.f("ix_invitation_role_id"), "invitation", ["role_id"], unique=False
28+
)
29+
op.create_foreign_key(
30+
op.f("fk_invitation_role_id_role"),
31+
"invitation",
32+
"role",
33+
["role_id"],
34+
["id"],
35+
ondelete="RESTRICT",
36+
)
37+
op.drop_column("invitation", "role")
38+
op.drop_column("membership", "role")
39+
op.add_column(
40+
"organization_invitation", sa.Column("role_id", sa.UUID(), nullable=False)
41+
)
42+
op.create_index(
43+
op.f("ix_organization_invitation_role_id"),
44+
"organization_invitation",
45+
["role_id"],
46+
unique=False,
47+
)
48+
op.create_foreign_key(
49+
op.f("fk_organization_invitation_role_id_role"),
50+
"organization_invitation",
51+
"role",
52+
["role_id"],
53+
["id"],
54+
ondelete="RESTRICT",
55+
)
56+
op.drop_column("organization_invitation", "role")
57+
op.drop_column("organization_membership", "role")
58+
sa.Enum("MEMBER", "ADMIN", "OWNER", name="orgrole").drop(op.get_bind())
59+
sa.Enum("VIEWER", "EDITOR", "ADMIN", name="workspacerole").drop(op.get_bind())
60+
# ### end Alembic commands ###
61+
62+
63+
def downgrade() -> None:
64+
# ### commands auto generated by Alembic - please adjust! ###
65+
sa.Enum("VIEWER", "EDITOR", "ADMIN", name="workspacerole").create(op.get_bind())
66+
sa.Enum("MEMBER", "ADMIN", "OWNER", name="orgrole").create(op.get_bind())
67+
op.add_column(
68+
"organization_membership",
69+
sa.Column(
70+
"role",
71+
postgresql.ENUM(
72+
"MEMBER", "ADMIN", "OWNER", name="orgrole", create_type=False
73+
),
74+
server_default=sa.text("'MEMBER'::orgrole"),
75+
autoincrement=False,
76+
nullable=False,
77+
),
78+
)
79+
op.add_column(
80+
"organization_invitation",
81+
sa.Column(
82+
"role",
83+
postgresql.ENUM(
84+
"MEMBER", "ADMIN", "OWNER", name="orgrole", create_type=False
85+
),
86+
autoincrement=False,
87+
nullable=False,
88+
),
89+
)
90+
op.drop_constraint(
91+
op.f("fk_organization_invitation_role_id_role"),
92+
"organization_invitation",
93+
type_="foreignkey",
94+
)
95+
op.drop_index(
96+
op.f("ix_organization_invitation_role_id"), table_name="organization_invitation"
97+
)
98+
op.drop_column("organization_invitation", "role_id")
99+
op.add_column(
100+
"membership",
101+
sa.Column(
102+
"role",
103+
postgresql.ENUM(
104+
"VIEWER", "EDITOR", "ADMIN", name="workspacerole", create_type=False
105+
),
106+
server_default=sa.text("'EDITOR'::workspacerole"),
107+
autoincrement=False,
108+
nullable=False,
109+
),
110+
)
111+
op.add_column(
112+
"invitation",
113+
sa.Column(
114+
"role",
115+
postgresql.ENUM(
116+
"VIEWER", "EDITOR", "ADMIN", name="workspacerole", create_type=False
117+
),
118+
autoincrement=False,
119+
nullable=False,
120+
),
121+
)
122+
op.drop_constraint(
123+
op.f("fk_invitation_role_id_role"), "invitation", type_="foreignkey"
124+
)
125+
op.drop_index(op.f("ix_invitation_role_id"), table_name="invitation")
126+
op.drop_column("invitation", "role_id")
127+
# ### end Alembic commands ###

frontend/src/client/schemas.gen.ts

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9914,15 +9914,37 @@ export const $OrgInvitationCreate = {
99149914
format: "email",
99159915
title: "Email",
99169916
},
9917-
role: {
9918-
$ref: "#/components/schemas/OrgRole",
9919-
default: "member",
9917+
role_id: {
9918+
anyOf: [
9919+
{
9920+
type: "string",
9921+
format: "uuid",
9922+
},
9923+
{
9924+
type: "null",
9925+
},
9926+
],
9927+
title: "Role Id",
9928+
},
9929+
role_slug: {
9930+
anyOf: [
9931+
{
9932+
type: "string",
9933+
},
9934+
{
9935+
type: "null",
9936+
},
9937+
],
9938+
title: "Role Slug",
99209939
},
99219940
},
99229941
type: "object",
99239942
required: ["email"],
99249943
title: "OrgInvitationCreate",
9925-
description: "Request body for creating an organization invitation.",
9944+
description: `Request body for creating an organization invitation.
9945+
9946+
Either role_id or role_slug must be provided to specify the role to grant.
9947+
If both are provided, role_id takes precedence.`,
99269948
} as const
99279949

99289950
export const $OrgInvitationRead = {
@@ -9942,8 +9964,25 @@ export const $OrgInvitationRead = {
99429964
format: "email",
99439965
title: "Email",
99449966
},
9945-
role: {
9946-
$ref: "#/components/schemas/OrgRole",
9967+
role_id: {
9968+
type: "string",
9969+
format: "uuid",
9970+
title: "Role Id",
9971+
},
9972+
role_slug: {
9973+
anyOf: [
9974+
{
9975+
type: "string",
9976+
},
9977+
{
9978+
type: "null",
9979+
},
9980+
],
9981+
title: "Role Slug",
9982+
},
9983+
role_name: {
9984+
type: "string",
9985+
title: "Role Name",
99479986
},
99489987
status: {
99499988
$ref: "#/components/schemas/InvitationStatus",
@@ -9988,7 +10027,9 @@ export const $OrgInvitationRead = {
998810027
"id",
998910028
"organization_id",
999010029
"email",
9991-
"role",
10030+
"role_id",
10031+
"role_slug",
10032+
"role_name",
999210033
"status",
999310034
"invited_by",
999410035
"expires_at",
@@ -10032,8 +10073,20 @@ export const $OrgInvitationReadMinimal = {
1003210073
],
1003310074
title: "Inviter Email",
1003410075
},
10035-
role: {
10036-
$ref: "#/components/schemas/OrgRole",
10076+
role_slug: {
10077+
anyOf: [
10078+
{
10079+
type: "string",
10080+
},
10081+
{
10082+
type: "null",
10083+
},
10084+
],
10085+
title: "Role Slug",
10086+
},
10087+
role_name: {
10088+
type: "string",
10089+
title: "Role Name",
1003710090
},
1003810091
status: {
1003910092
$ref: "#/components/schemas/InvitationStatus",
@@ -10061,7 +10114,8 @@ export const $OrgInvitationReadMinimal = {
1006110114
"organization_name",
1006210115
"inviter_name",
1006310116
"inviter_email",
10064-
"role",
10117+
"role_slug",
10118+
"role_name",
1006510119
"status",
1006610120
"expires_at",
1006710121
],
@@ -10106,8 +10160,28 @@ export const $OrgMemberRead = {
1010610160
format: "email",
1010710161
title: "Email",
1010810162
},
10109-
role: {
10110-
$ref: "#/components/schemas/OrgRole",
10163+
role_id: {
10164+
anyOf: [
10165+
{
10166+
type: "string",
10167+
format: "uuid",
10168+
},
10169+
{
10170+
type: "null",
10171+
},
10172+
],
10173+
title: "Role Id",
10174+
},
10175+
role_slug: {
10176+
anyOf: [
10177+
{
10178+
type: "string",
10179+
},
10180+
{
10181+
type: "null",
10182+
},
10183+
],
10184+
title: "Role Slug",
1011110185
},
1011210186
is_active: {
1011310187
type: "boolean",
@@ -10140,7 +10214,8 @@ export const $OrgMemberRead = {
1014010214
"first_name",
1014110215
"last_name",
1014210216
"email",
10143-
"role",
10217+
"role_id",
10218+
"role_slug",
1014410219
"is_active",
1014510220
"is_superuser",
1014610221
"is_verified",

frontend/src/client/types.gen.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3200,10 +3200,14 @@ export type OrgInvitationAccept = {
32003200

32013201
/**
32023202
* Request body for creating an organization invitation.
3203+
*
3204+
* Either role_id or role_slug must be provided to specify the role to grant.
3205+
* If both are provided, role_id takes precedence.
32033206
*/
32043207
export type OrgInvitationCreate = {
32053208
email: string
3206-
role?: OrgRole
3209+
role_id?: string | null
3210+
role_slug?: string | null
32073211
}
32083212

32093213
/**
@@ -3213,7 +3217,9 @@ export type OrgInvitationRead = {
32133217
id: string
32143218
organization_id: string
32153219
email: string
3216-
role: OrgRole
3220+
role_id: string
3221+
role_slug: string | null
3222+
role_name: string
32173223
status: InvitationStatus
32183224
invited_by: string | null
32193225
expires_at: string
@@ -3232,7 +3238,8 @@ export type OrgInvitationReadMinimal = {
32323238
organization_name: string
32333239
inviter_name: string | null
32343240
inviter_email: string | null
3235-
role: OrgRole
3241+
role_slug: string | null
3242+
role_name: string
32363243
status: InvitationStatus
32373244
expires_at: string
32383245
email_matches?: boolean | null
@@ -3243,7 +3250,8 @@ export type OrgMemberRead = {
32433250
first_name: string | null
32443251
last_name: string | null
32453252
email: string
3246-
role: OrgRole
3253+
role_id: string | null
3254+
role_slug: string | null
32473255
is_active: boolean
32483256
is_superuser: boolean
32493257
is_verified: boolean

packages/tracecat-ee/tracecat_ee/admin/organizations/service.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from sqlalchemy.orm import selectinload
1212

1313
from tracecat.auth.types import AccessLevel, Role
14+
from tracecat.authz.seeding import seed_system_roles_for_org
1415
from tracecat.db.models import (
1516
Organization,
1617
RegistryRepository,
@@ -47,6 +48,7 @@ async def create_organization(self, params: OrgCreate) -> OrgRead:
4748
name=params.name,
4849
slug=params.slug,
4950
)
51+
await seed_system_roles_for_org(self.session, org.id)
5052
return OrgRead.model_validate(org)
5153

5254
async def get_organization(self, org_id: uuid.UUID) -> OrgRead:

0 commit comments

Comments
 (0)