feat: add admin session rbac foundation
This commit is contained in:
1 parent
8f000811a8
commit
2cdf7f9d7a
22 files changed
+1945
-68
No files matched your search
@@ -0,0 +1,149 @@
|
||||
"""Add normalized administrator RBAC tables and bootstrap the current admin."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "0025_admin_rbac"
|
||||
down_revision = "0024_vehicle_demand"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
RBAC_TABLES = (
|
||||
"AdminRole",
|
||||
"AdminMenu",
|
||||
"AdminDepartment",
|
||||
"AdminUserRole",
|
||||
"AdminRoleMenu",
|
||||
"AdminUserDepartment",
|
||||
"AdminRoleDepartment",
|
||||
)
|
||||
|
||||
DEFAULT_DEPT_ID = "00000000-0000-0000-0000-000000000001"
|
||||
SUPER_ROLE_ID = "00000000-0000-0000-0000-000000000002"
|
||||
|
||||
MENU_ROWS = (
|
||||
("00000000-0000-0000-0000-000000000101", "仪表盘", "page", "/dashboard", "Dashboard", "admin:dashboard", 10),
|
||||
("00000000-0000-0000-0000-000000000102", "首页配置", "page", "/home", "HomePage", "admin:site-config", 20),
|
||||
("00000000-0000-0000-0000-000000000103", "玩法与详情", "page", "/play", "WanfaPage", "admin:wanfa:read", 30),
|
||||
("00000000-0000-0000-0000-000000000104", "管家管理", "page", "/concierge", "ConciergePage", "admin:concierge:read", 40),
|
||||
("00000000-0000-0000-0000-000000000105", "需求线索", "page", "/leads", "LeadsPage", "admin:leads:read", 50),
|
||||
("00000000-0000-0000-0000-000000000106", "用户管理", "page", "/system/users", "SystemUsers", "system:user:read", 60),
|
||||
("00000000-0000-0000-0000-000000000107", "角色管理", "page", "/system/roles", "SystemRoles", "system:role:read", 70),
|
||||
("00000000-0000-0000-0000-000000000108", "菜单管理", "page", "/system/menus", "SystemMenus", "system:menu:read", 80),
|
||||
("00000000-0000-0000-0000-000000000109", "部门管理", "page", "/system/depts", "SystemDepts", "system:dept:read", 90),
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
|
||||
op.create_table(
|
||||
"AdminRole",
|
||||
sa.Column("id", sa.String(), nullable=False),
|
||||
sa.Column("name", sa.String(), nullable=False),
|
||||
sa.Column("code", sa.String(), nullable=False),
|
||||
sa.Column("dataScope", sa.String(), nullable=False, server_default="all"),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("isActive", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
sa.Column("createdAt", sa.DateTime(), nullable=False),
|
||||
sa.Column("updatedAt", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("code", name="uq_AdminRole_code"),
|
||||
)
|
||||
op.create_table(
|
||||
"AdminDepartment",
|
||||
sa.Column("id", sa.String(), nullable=False),
|
||||
sa.Column("parentId", sa.String(), nullable=True),
|
||||
sa.Column("name", sa.String(), nullable=False),
|
||||
sa.Column("sortOrder", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("isActive", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
sa.Column("createdAt", sa.DateTime(), nullable=False),
|
||||
sa.Column("updatedAt", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["parentId"], ["AdminDepartment.id"], ondelete="SET NULL"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"AdminMenu",
|
||||
sa.Column("id", sa.String(), nullable=False),
|
||||
sa.Column("parentId", sa.String(), nullable=True),
|
||||
sa.Column("name", sa.String(), nullable=False),
|
||||
sa.Column("type", sa.String(), nullable=False, server_default="page"),
|
||||
sa.Column("path", sa.String(), nullable=True),
|
||||
sa.Column("componentKey", sa.String(), nullable=True),
|
||||
sa.Column("permissionCode", sa.String(), nullable=True),
|
||||
sa.Column("icon", sa.String(), nullable=True),
|
||||
sa.Column("sortOrder", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("isVisible", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
sa.Column("isActive", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
sa.Column("createdAt", sa.DateTime(), nullable=False),
|
||||
sa.Column("updatedAt", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["parentId"], ["AdminMenu.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("permissionCode", name="uq_AdminMenu_permissionCode"),
|
||||
)
|
||||
op.create_table(
|
||||
"AdminUserRole",
|
||||
sa.Column("userId", sa.String(), nullable=False),
|
||||
sa.Column("roleId", sa.String(), nullable=False),
|
||||
sa.Column("createdAt", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["roleId"], ["AdminRole.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["userId"], ["AdminUser.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("userId", "roleId"),
|
||||
)
|
||||
op.create_table(
|
||||
"AdminRoleMenu",
|
||||
sa.Column("roleId", sa.String(), nullable=False),
|
||||
sa.Column("menuId", sa.String(), nullable=False),
|
||||
sa.Column("createdAt", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["menuId"], ["AdminMenu.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["roleId"], ["AdminRole.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("roleId", "menuId"),
|
||||
)
|
||||
op.create_table(
|
||||
"AdminUserDepartment",
|
||||
sa.Column("userId", sa.String(), nullable=False),
|
||||
sa.Column("deptId", sa.String(), nullable=False),
|
||||
sa.Column("createdAt", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["deptId"], ["AdminDepartment.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["userId"], ["AdminUser.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("userId", "deptId"),
|
||||
)
|
||||
op.create_table(
|
||||
"AdminRoleDepartment",
|
||||
sa.Column("roleId", sa.String(), nullable=False),
|
||||
sa.Column("deptId", sa.String(), nullable=False),
|
||||
sa.Column("createdAt", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["deptId"], ["AdminDepartment.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["roleId"], ["AdminRole.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("roleId", "deptId"),
|
||||
)
|
||||
|
||||
role_table = sa.table("AdminRole", sa.column("id", sa.String), sa.column("name", sa.String), sa.column("code", sa.String), sa.column("dataScope", sa.String), sa.column("isActive", sa.Boolean), sa.column("createdAt", sa.DateTime), sa.column("updatedAt", sa.DateTime))
|
||||
dept_table = sa.table("AdminDepartment", sa.column("id", sa.String), sa.column("name", sa.String), sa.column("sortOrder", sa.Integer), sa.column("isActive", sa.Boolean), sa.column("createdAt", sa.DateTime), sa.column("updatedAt", sa.DateTime))
|
||||
menu_table = sa.table("AdminMenu", sa.column("id", sa.String), sa.column("name", sa.String), sa.column("type", sa.String), sa.column("path", sa.String), sa.column("componentKey", sa.String), sa.column("permissionCode", sa.String), sa.column("sortOrder", sa.Integer), sa.column("isVisible", sa.Boolean), sa.column("isActive", sa.Boolean), sa.column("createdAt", sa.DateTime), sa.column("updatedAt", sa.DateTime))
|
||||
op.bulk_insert(role_table, [{"id": SUPER_ROLE_ID, "name": "超级管理员", "code": "super_admin", "dataScope": "all", "isActive": True, "createdAt": now, "updatedAt": now}])
|
||||
op.bulk_insert(dept_table, [{"id": DEFAULT_DEPT_ID, "name": "默认部门", "sortOrder": 0, "isActive": True, "createdAt": now, "updatedAt": now}])
|
||||
op.bulk_insert(
|
||||
menu_table,
|
||||
[
|
||||
{"id": menu_id, "name": name, "type": menu_type, "path": path, "componentKey": component, "permissionCode": permission, "sortOrder": sort_order, "isVisible": True, "isActive": True, "createdAt": now, "updatedAt": now}
|
||||
for menu_id, name, menu_type, path, component, permission, sort_order in MENU_ROWS
|
||||
],
|
||||
)
|
||||
op.execute(sa.text('INSERT INTO "AdminRoleMenu" ("roleId", "menuId", "createdAt") SELECT :role_id, "id", :created_at FROM "AdminMenu" ON CONFLICT DO NOTHING').bindparams(role_id=SUPER_ROLE_ID, created_at=now))
|
||||
op.execute(sa.text('INSERT INTO "AdminRoleDepartment" ("roleId", "deptId", "createdAt") VALUES (:role_id, :dept_id, :created_at) ON CONFLICT DO NOTHING').bindparams(role_id=SUPER_ROLE_ID, dept_id=DEFAULT_DEPT_ID, created_at=now))
|
||||
op.execute(sa.text('INSERT INTO "AdminUserRole" ("userId", "roleId", "createdAt") SELECT "id", :role_id, :created_at FROM "AdminUser" ON CONFLICT DO NOTHING').bindparams(role_id=SUPER_ROLE_ID, created_at=now))
|
||||
op.execute(sa.text('INSERT INTO "AdminUserDepartment" ("userId", "deptId", "createdAt") SELECT "id", :dept_id, :created_at FROM "AdminUser" ON CONFLICT DO NOTHING').bindparams(dept_id=DEFAULT_DEPT_ID, created_at=now))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("AdminRoleDepartment")
|
||||
op.drop_table("AdminUserDepartment")
|
||||
op.drop_table("AdminRoleMenu")
|
||||
op.drop_table("AdminUserRole")
|
||||
op.drop_table("AdminMenu")
|
||||
op.drop_table("AdminDepartment")
|
||||
op.drop_table("AdminRole")
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Add department and creator ownership to operator-managed resources."""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "0026_admin_ownership"
|
||||
down_revision = "0025_admin_rbac"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
DEFAULT_DEPT_ID = "00000000-0000-0000-0000-000000000001"
|
||||
OWNED_TABLES = (
|
||||
"MediaAsset",
|
||||
"HeroSlide",
|
||||
"DestinationHero",
|
||||
"VehicleOption",
|
||||
"VehicleServiceConfig",
|
||||
"HomeExperience",
|
||||
"HomeTeamBuilding",
|
||||
"HomeWildArchive",
|
||||
"HomeWanfaRecommendation",
|
||||
"WanfaCategory",
|
||||
"WanfaRoute",
|
||||
"DetailRecord",
|
||||
"ConciergeAdvisor",
|
||||
"DemandHero",
|
||||
"DemandFeatureCard",
|
||||
"DemandForm",
|
||||
"Lead",
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
for table_name in OWNED_TABLES:
|
||||
op.add_column(
|
||||
table_name,
|
||||
sa.Column(
|
||||
"deptId",
|
||||
sa.String(),
|
||||
nullable=True,
|
||||
server_default=sa.text(f"'{DEFAULT_DEPT_ID}'"),
|
||||
),
|
||||
)
|
||||
op.add_column(table_name, sa.Column("createdById", sa.String(), nullable=True))
|
||||
op.create_index(f"ix_{table_name}_deptId", table_name, ["deptId"])
|
||||
op.create_index(f"ix_{table_name}_createdById", table_name, ["createdById"])
|
||||
op.create_foreign_key(
|
||||
f"fk_{table_name}_deptId",
|
||||
table_name,
|
||||
"AdminDepartment",
|
||||
["deptId"],
|
||||
["id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
f"fk_{table_name}_createdById",
|
||||
table_name,
|
||||
"AdminUser",
|
||||
["createdById"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
quoted_table = f'"{table_name}"'
|
||||
op.execute(
|
||||
sa.text(f'UPDATE {quoted_table} SET "deptId" = :dept_id WHERE "deptId" IS NULL').bindparams(
|
||||
dept_id=DEFAULT_DEPT_ID
|
||||
)
|
||||
)
|
||||
op.alter_column(
|
||||
table_name,
|
||||
"deptId",
|
||||
existing_type=sa.String(),
|
||||
nullable=False,
|
||||
server_default=sa.text(f"'{DEFAULT_DEPT_ID}'"),
|
||||
)
|
||||
op.execute(
|
||||
sa.text(
|
||||
f'UPDATE {quoted_table} SET "createdById" = '
|
||||
'(SELECT "id" FROM "AdminUser" ORDER BY "createdAt" ASC LIMIT 1) '
|
||||
'WHERE "createdById" IS NULL'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
for table_name in reversed(OWNED_TABLES):
|
||||
op.drop_constraint(f"fk_{table_name}_createdById", table_name, type_="foreignkey")
|
||||
op.drop_constraint(f"fk_{table_name}_deptId", table_name, type_="foreignkey")
|
||||
op.drop_index(f"ix_{table_name}_createdById", table_name=table_name)
|
||||
op.drop_index(f"ix_{table_name}_deptId", table_name=table_name)
|
||||
op.drop_column(table_name, "createdById")
|
||||
op.drop_column(table_name, "deptId")
|
||||
Reference in new issue
Block a user