mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-09-05 15:47:00 +08:00
Initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from .service import RoleService
|
||||
|
||||
__all__ = ["RoleService"]
|
||||
@@ -0,0 +1,119 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from domain.auth.service import get_current_active_user
|
||||
from domain.auth.types import User
|
||||
from domain.permission import require_system_permission
|
||||
from domain.permission.types import PathRuleCreate, PathRuleInfo, SystemPermission
|
||||
from domain.user.service import UserService
|
||||
from domain.user.types import UserInfo
|
||||
|
||||
from .service import RoleService
|
||||
from .types import RoleCreate, RoleDetail, RoleInfo, RolePermissionsUpdate, RoleUpdate
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["role"])
|
||||
|
||||
|
||||
@router.get("/roles", response_model=list[RoleInfo])
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def list_roles(
|
||||
current_user: Annotated[User, Depends(get_current_active_user)]
|
||||
) -> list[RoleInfo]:
|
||||
return await RoleService.get_all_roles()
|
||||
|
||||
|
||||
@router.get("/roles/{role_id}", response_model=RoleDetail)
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def get_role(
|
||||
role_id: int,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> RoleDetail:
|
||||
return await RoleService.get_role(role_id)
|
||||
|
||||
|
||||
@router.get("/roles/{role_id}/users", response_model=list[UserInfo])
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def list_role_users(
|
||||
role_id: int,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> list[UserInfo]:
|
||||
return await UserService.get_users_by_role(role_id)
|
||||
|
||||
|
||||
@router.post("/roles", response_model=RoleInfo)
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def create_role(
|
||||
data: RoleCreate,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> RoleInfo:
|
||||
return await RoleService.create_role(data)
|
||||
|
||||
|
||||
@router.put("/roles/{role_id}", response_model=RoleInfo)
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def update_role(
|
||||
role_id: int,
|
||||
data: RoleUpdate,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> RoleInfo:
|
||||
return await RoleService.update_role(role_id, data)
|
||||
|
||||
|
||||
@router.delete("/roles/{role_id}")
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def delete_role(
|
||||
role_id: int,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> dict:
|
||||
await RoleService.delete_role(role_id)
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.post("/roles/{role_id}/permissions", response_model=list[str])
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def set_role_permissions(
|
||||
role_id: int,
|
||||
data: RolePermissionsUpdate,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> list[str]:
|
||||
return await RoleService.set_role_permissions(role_id, data.permission_codes)
|
||||
|
||||
|
||||
@router.get("/roles/{role_id}/path-rules", response_model=list[PathRuleInfo])
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def get_role_path_rules(
|
||||
role_id: int,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> list[PathRuleInfo]:
|
||||
return await RoleService.get_role_path_rules(role_id)
|
||||
|
||||
|
||||
@router.post("/roles/{role_id}/path-rules", response_model=PathRuleInfo)
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def add_path_rule(
|
||||
role_id: int,
|
||||
data: PathRuleCreate,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> PathRuleInfo:
|
||||
return await RoleService.add_path_rule(role_id, data)
|
||||
|
||||
|
||||
@router.put("/path-rules/{rule_id}", response_model=PathRuleInfo)
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def update_path_rule(
|
||||
rule_id: int,
|
||||
data: PathRuleCreate,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> PathRuleInfo:
|
||||
return await RoleService.update_path_rule(rule_id, data)
|
||||
|
||||
|
||||
@router.delete("/path-rules/{rule_id}")
|
||||
@require_system_permission(SystemPermission.ROLE_MANAGE)
|
||||
async def delete_path_rule(
|
||||
rule_id: int,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> dict:
|
||||
await RoleService.delete_path_rule(rule_id)
|
||||
return {"success": True}
|
||||
@@ -0,0 +1,288 @@
|
||||
from typing import List
|
||||
from fastapi import HTTPException
|
||||
|
||||
from models.database import Role, RolePermission, PathRule, UserRole
|
||||
from domain.permission.service import PermissionService
|
||||
from domain.permission.types import PathRuleCreate, PathRuleInfo, PERMISSION_DEFINITIONS
|
||||
from .types import RoleInfo, RoleDetail, RoleCreate, RoleUpdate, SystemRoles
|
||||
|
||||
|
||||
class RoleService:
|
||||
"""角色管理服务"""
|
||||
|
||||
@classmethod
|
||||
async def get_all_roles(cls) -> List[RoleInfo]:
|
||||
"""获取所有角色"""
|
||||
roles = await Role.all().order_by("id")
|
||||
return [
|
||||
RoleInfo(
|
||||
id=r.id,
|
||||
name=r.name,
|
||||
description=r.description,
|
||||
is_system=r.is_system,
|
||||
created_at=r.created_at,
|
||||
)
|
||||
for r in roles
|
||||
]
|
||||
|
||||
@classmethod
|
||||
async def get_role(cls, role_id: int) -> RoleDetail:
|
||||
"""获取角色详情"""
|
||||
role = await Role.get_or_none(id=role_id)
|
||||
if not role:
|
||||
raise HTTPException(404, detail="角色不存在")
|
||||
|
||||
# 获取权限
|
||||
role_permissions = await RolePermission.filter(role_id=role_id)
|
||||
permissions = sorted(set(rp.permission_code for rp in role_permissions))
|
||||
|
||||
# 获取路径规则数量
|
||||
path_rules_count = await PathRule.filter(role_id=role_id).count()
|
||||
|
||||
return RoleDetail(
|
||||
id=role.id,
|
||||
name=role.name,
|
||||
description=role.description,
|
||||
is_system=role.is_system,
|
||||
created_at=role.created_at,
|
||||
permissions=permissions,
|
||||
path_rules_count=path_rules_count,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def create_role(cls, data: RoleCreate) -> RoleInfo:
|
||||
"""创建角色"""
|
||||
# 检查名称是否已存在
|
||||
existing = await Role.get_or_none(name=data.name)
|
||||
if existing:
|
||||
raise HTTPException(400, detail="角色名称已存在")
|
||||
|
||||
role = await Role.create(
|
||||
name=data.name,
|
||||
description=data.description,
|
||||
is_system=False,
|
||||
)
|
||||
|
||||
return RoleInfo(
|
||||
id=role.id,
|
||||
name=role.name,
|
||||
description=role.description,
|
||||
is_system=role.is_system,
|
||||
created_at=role.created_at,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def update_role(cls, role_id: int, data: RoleUpdate) -> RoleInfo:
|
||||
"""更新角色"""
|
||||
role = await Role.get_or_none(id=role_id)
|
||||
if not role:
|
||||
raise HTTPException(404, detail="角色不存在")
|
||||
|
||||
if data.name is not None:
|
||||
# 检查名称是否与其他角色冲突
|
||||
existing = await Role.filter(name=data.name).exclude(id=role_id).first()
|
||||
if existing:
|
||||
raise HTTPException(400, detail="角色名称已存在")
|
||||
role.name = data.name
|
||||
|
||||
if data.description is not None:
|
||||
role.description = data.description
|
||||
|
||||
await role.save()
|
||||
|
||||
return RoleInfo(
|
||||
id=role.id,
|
||||
name=role.name,
|
||||
description=role.description,
|
||||
is_system=role.is_system,
|
||||
created_at=role.created_at,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def delete_role(cls, role_id: int) -> None:
|
||||
"""删除角色"""
|
||||
role = await Role.get_or_none(id=role_id)
|
||||
if not role:
|
||||
raise HTTPException(404, detail="角色不存在")
|
||||
|
||||
if role.is_system:
|
||||
raise HTTPException(400, detail="系统内置角色不可删除")
|
||||
|
||||
# 检查是否有用户使用此角色
|
||||
user_count = await UserRole.filter(role_id=role_id).count()
|
||||
if user_count > 0:
|
||||
raise HTTPException(400, detail=f"有 {user_count} 个用户正在使用此角色,无法删除")
|
||||
|
||||
await role.delete()
|
||||
# 清除权限缓存
|
||||
PermissionService.clear_cache()
|
||||
|
||||
@classmethod
|
||||
async def set_role_permissions(cls, role_id: int, permission_codes: List[str]) -> List[str]:
|
||||
"""设置角色的权限"""
|
||||
role = await Role.get_or_none(id=role_id)
|
||||
if not role:
|
||||
raise HTTPException(404, detail="角色不存在")
|
||||
|
||||
all_permission_codes = {item["code"] for item in PERMISSION_DEFINITIONS}
|
||||
invalid_codes = set(permission_codes) - all_permission_codes
|
||||
if invalid_codes:
|
||||
raise HTTPException(400, detail=f"无效的权限代码: {', '.join(invalid_codes)}")
|
||||
|
||||
# 删除现有权限
|
||||
await RolePermission.filter(role_id=role_id).delete()
|
||||
|
||||
# 添加新权限
|
||||
for code in permission_codes:
|
||||
await RolePermission.create(
|
||||
role_id=role_id,
|
||||
permission_code=code,
|
||||
)
|
||||
|
||||
# 清除权限缓存
|
||||
PermissionService.clear_cache()
|
||||
|
||||
return list(permission_codes)
|
||||
|
||||
@classmethod
|
||||
async def get_role_path_rules(cls, role_id: int) -> List[PathRuleInfo]:
|
||||
"""获取角色的路径规则"""
|
||||
role = await Role.get_or_none(id=role_id)
|
||||
if not role:
|
||||
raise HTTPException(404, detail="角色不存在")
|
||||
|
||||
rules = await PathRule.filter(role_id=role_id).order_by("-priority", "id")
|
||||
return [
|
||||
PathRuleInfo(
|
||||
id=r.id,
|
||||
role_id=r.role_id,
|
||||
path_pattern=r.path_pattern,
|
||||
is_regex=r.is_regex,
|
||||
can_read=r.can_read,
|
||||
can_write=r.can_write,
|
||||
can_delete=r.can_delete,
|
||||
can_share=r.can_share,
|
||||
priority=r.priority,
|
||||
created_at=r.created_at,
|
||||
)
|
||||
for r in rules
|
||||
]
|
||||
|
||||
@classmethod
|
||||
async def add_path_rule(cls, role_id: int, data: PathRuleCreate) -> PathRuleInfo:
|
||||
"""添加路径规则"""
|
||||
role = await Role.get_or_none(id=role_id)
|
||||
if not role:
|
||||
raise HTTPException(404, detail="角色不存在")
|
||||
|
||||
# 验证路径模式
|
||||
if data.is_regex:
|
||||
import re
|
||||
try:
|
||||
re.compile(data.path_pattern)
|
||||
except re.error as e:
|
||||
raise HTTPException(400, detail=f"无效的正则表达式: {e}")
|
||||
|
||||
rule = await PathRule.create(
|
||||
role_id=role_id,
|
||||
path_pattern=data.path_pattern,
|
||||
is_regex=data.is_regex,
|
||||
can_read=data.can_read,
|
||||
can_write=data.can_write,
|
||||
can_delete=data.can_delete,
|
||||
can_share=data.can_share,
|
||||
priority=data.priority,
|
||||
)
|
||||
|
||||
# 清除权限缓存
|
||||
PermissionService.clear_cache()
|
||||
|
||||
return PathRuleInfo(
|
||||
id=rule.id,
|
||||
role_id=rule.role_id,
|
||||
path_pattern=rule.path_pattern,
|
||||
is_regex=rule.is_regex,
|
||||
can_read=rule.can_read,
|
||||
can_write=rule.can_write,
|
||||
can_delete=rule.can_delete,
|
||||
can_share=rule.can_share,
|
||||
priority=rule.priority,
|
||||
created_at=rule.created_at,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def update_path_rule(cls, rule_id: int, data: PathRuleCreate) -> PathRuleInfo:
|
||||
"""更新路径规则"""
|
||||
rule = await PathRule.get_or_none(id=rule_id)
|
||||
if not rule:
|
||||
raise HTTPException(404, detail="路径规则不存在")
|
||||
|
||||
# 验证路径模式
|
||||
if data.is_regex:
|
||||
import re
|
||||
try:
|
||||
re.compile(data.path_pattern)
|
||||
except re.error as e:
|
||||
raise HTTPException(400, detail=f"无效的正则表达式: {e}")
|
||||
|
||||
rule.path_pattern = data.path_pattern
|
||||
rule.is_regex = data.is_regex
|
||||
rule.can_read = data.can_read
|
||||
rule.can_write = data.can_write
|
||||
rule.can_delete = data.can_delete
|
||||
rule.can_share = data.can_share
|
||||
rule.priority = data.priority
|
||||
await rule.save()
|
||||
|
||||
# 清除权限缓存
|
||||
PermissionService.clear_cache()
|
||||
|
||||
return PathRuleInfo(
|
||||
id=rule.id,
|
||||
role_id=rule.role_id,
|
||||
path_pattern=rule.path_pattern,
|
||||
is_regex=rule.is_regex,
|
||||
can_read=rule.can_read,
|
||||
can_write=rule.can_write,
|
||||
can_delete=rule.can_delete,
|
||||
can_share=rule.can_share,
|
||||
priority=rule.priority,
|
||||
created_at=rule.created_at,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def delete_path_rule(cls, rule_id: int) -> None:
|
||||
"""删除路径规则"""
|
||||
rule = await PathRule.get_or_none(id=rule_id)
|
||||
if not rule:
|
||||
raise HTTPException(404, detail="路径规则不存在")
|
||||
|
||||
await rule.delete()
|
||||
# 清除权限缓存
|
||||
PermissionService.clear_cache()
|
||||
|
||||
@classmethod
|
||||
async def ensure_system_roles(cls) -> None:
|
||||
"""确保系统内置角色存在"""
|
||||
system_roles = [
|
||||
{
|
||||
"name": SystemRoles.ADMIN,
|
||||
"description": "管理员角色,拥有所有系统和适配器权限",
|
||||
"is_system": True,
|
||||
},
|
||||
{
|
||||
"name": SystemRoles.USER,
|
||||
"description": "普通用户角色,需要管理员配置路径权限",
|
||||
"is_system": True,
|
||||
},
|
||||
{
|
||||
"name": SystemRoles.VIEWER,
|
||||
"description": "只读用户角色,仅可查看文件",
|
||||
"is_system": True,
|
||||
},
|
||||
]
|
||||
|
||||
for role_data in system_roles:
|
||||
existing = await Role.get_or_none(name=role_data["name"])
|
||||
if not existing:
|
||||
await Role.create(**role_data)
|
||||
@@ -0,0 +1,36 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class RoleInfo(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: str | None = None
|
||||
is_system: bool
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class RoleDetail(RoleInfo):
|
||||
permissions: list[str] # 权限代码列表
|
||||
path_rules_count: int
|
||||
|
||||
|
||||
class RoleCreate(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class RoleUpdate(BaseModel):
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class RolePermissionsUpdate(BaseModel):
|
||||
permission_codes: list[str]
|
||||
|
||||
|
||||
# 预置角色名称
|
||||
class SystemRoles:
|
||||
ADMIN = "Admin"
|
||||
USER = "User"
|
||||
VIEWER = "Viewer"
|
||||
Reference in New Issue
Block a user