mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-07-12 16:03:27 +08:00
feat: add user and role management pages with API integration
- Implemented user management functionality in UsersPage including user creation, editing, deletion, and role assignment. - Added role management functionality in RolesPage with role creation, editing, deletion, and path rule management. - Created users API for handling user-related operations. - Created roles API for handling role-related operations. - Integrated permissions handling in both user and role management. - Enhanced UI with Ant Design components for better user experience.
This commit is contained in:
4
domain/user/__init__.py
Normal file
4
domain/user/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .service import UserService
|
||||
|
||||
__all__ = ["UserService"]
|
||||
|
||||
94
domain/user/api.py
Normal file
94
domain/user/api.py
Normal file
@@ -0,0 +1,94 @@
|
||||
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.service import PermissionService
|
||||
from domain.permission.types import SystemPermission
|
||||
|
||||
from .service import UserService
|
||||
from .types import UserCreate, UserDetail, UserInfo, UserRoleAssign, UserUpdate
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["user"])
|
||||
|
||||
|
||||
@router.get("/users", response_model=list[UserInfo])
|
||||
async def list_users(
|
||||
current_user: Annotated[User, Depends(get_current_active_user)]
|
||||
) -> list[UserInfo]:
|
||||
await PermissionService.require_system_permission(
|
||||
current_user.id, SystemPermission.USER_LIST
|
||||
)
|
||||
return await UserService.get_all_users()
|
||||
|
||||
|
||||
@router.get("/users/{user_id}", response_model=UserDetail)
|
||||
async def get_user(
|
||||
user_id: int,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> UserDetail:
|
||||
await PermissionService.require_system_permission(
|
||||
current_user.id, SystemPermission.USER_LIST
|
||||
)
|
||||
return await UserService.get_user(user_id)
|
||||
|
||||
|
||||
@router.post("/users", response_model=UserDetail)
|
||||
async def create_user(
|
||||
data: UserCreate,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> UserDetail:
|
||||
await PermissionService.require_system_permission(
|
||||
current_user.id, SystemPermission.USER_CREATE
|
||||
)
|
||||
return await UserService.create_user(data, current_user.id)
|
||||
|
||||
|
||||
@router.put("/users/{user_id}", response_model=UserDetail)
|
||||
async def update_user(
|
||||
user_id: int,
|
||||
data: UserUpdate,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> UserDetail:
|
||||
await PermissionService.require_system_permission(
|
||||
current_user.id, SystemPermission.USER_EDIT
|
||||
)
|
||||
return await UserService.update_user(user_id, data, current_user.id)
|
||||
|
||||
|
||||
@router.delete("/users/{user_id}")
|
||||
async def delete_user(
|
||||
user_id: int,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> dict:
|
||||
await PermissionService.require_system_permission(
|
||||
current_user.id, SystemPermission.USER_DELETE
|
||||
)
|
||||
await UserService.delete_user(user_id, current_user.id)
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.post("/users/{user_id}/roles", response_model=list[str])
|
||||
async def set_user_roles(
|
||||
user_id: int,
|
||||
data: UserRoleAssign,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> list[str]:
|
||||
await PermissionService.require_system_permission(
|
||||
current_user.id, SystemPermission.USER_EDIT
|
||||
)
|
||||
return await UserService.set_user_roles(user_id, data.role_ids)
|
||||
|
||||
|
||||
@router.delete("/users/{user_id}/roles/{role_id}", response_model=list[str])
|
||||
async def remove_user_role(
|
||||
user_id: int,
|
||||
role_id: int,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> list[str]:
|
||||
await PermissionService.require_system_permission(
|
||||
current_user.id, SystemPermission.USER_EDIT
|
||||
)
|
||||
return await UserService.remove_user_role(user_id, role_id)
|
||||
|
||||
190
domain/user/service.py
Normal file
190
domain/user/service.py
Normal file
@@ -0,0 +1,190 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from domain.auth.service import AuthService
|
||||
from domain.permission.service import PermissionService
|
||||
from models.database import Role, UserAccount, UserRole
|
||||
|
||||
from .types import UserCreate, UserDetail, UserInfo, UserUpdate
|
||||
|
||||
|
||||
class UserService:
|
||||
"""用户管理服务"""
|
||||
|
||||
@classmethod
|
||||
async def get_all_users(cls) -> List[UserInfo]:
|
||||
users = await UserAccount.all().order_by("id")
|
||||
return [
|
||||
UserInfo(
|
||||
id=u.id,
|
||||
username=u.username,
|
||||
email=u.email,
|
||||
full_name=u.full_name,
|
||||
disabled=u.disabled,
|
||||
is_admin=u.is_admin,
|
||||
created_at=u.created_at,
|
||||
last_login=u.last_login,
|
||||
)
|
||||
for u in users
|
||||
]
|
||||
|
||||
@classmethod
|
||||
async def get_user(cls, user_id: int) -> UserDetail:
|
||||
user = await UserAccount.get_or_none(id=user_id).prefetch_related("created_by")
|
||||
if not user:
|
||||
raise HTTPException(404, detail="用户不存在")
|
||||
|
||||
user_roles = await UserRole.filter(user_id=user_id).prefetch_related("role")
|
||||
roles = [ur.role.name for ur in user_roles]
|
||||
|
||||
created_by_username = None
|
||||
if user.created_by_id:
|
||||
creator = await UserAccount.get_or_none(id=user.created_by_id)
|
||||
if creator:
|
||||
created_by_username = creator.username
|
||||
|
||||
return UserDetail(
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
email=user.email,
|
||||
full_name=user.full_name,
|
||||
disabled=user.disabled,
|
||||
is_admin=user.is_admin,
|
||||
created_at=user.created_at,
|
||||
last_login=user.last_login,
|
||||
roles=roles,
|
||||
created_by_username=created_by_username,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_users_by_role(cls, role_id: int) -> List[UserInfo]:
|
||||
role = await Role.get_or_none(id=role_id)
|
||||
if not role:
|
||||
raise HTTPException(404, detail="角色不存在")
|
||||
|
||||
user_roles = await UserRole.filter(role_id=role_id).prefetch_related("user")
|
||||
users = [ur.user for ur in user_roles if ur.user]
|
||||
users.sort(key=lambda u: u.id)
|
||||
return [
|
||||
UserInfo(
|
||||
id=u.id,
|
||||
username=u.username,
|
||||
email=u.email,
|
||||
full_name=u.full_name,
|
||||
disabled=u.disabled,
|
||||
is_admin=u.is_admin,
|
||||
created_at=u.created_at,
|
||||
last_login=u.last_login,
|
||||
)
|
||||
for u in users
|
||||
]
|
||||
|
||||
@classmethod
|
||||
async def create_user(cls, data: UserCreate, creator_id: int) -> UserDetail:
|
||||
existing = await UserAccount.get_or_none(username=data.username)
|
||||
if existing:
|
||||
raise HTTPException(400, detail="用户名已存在")
|
||||
|
||||
if data.email:
|
||||
existing_email = await UserAccount.get_or_none(email=data.email)
|
||||
if existing_email:
|
||||
raise HTTPException(400, detail="邮箱已被使用")
|
||||
|
||||
hashed_password = AuthService.get_password_hash(data.password)
|
||||
user = await UserAccount.create(
|
||||
username=data.username,
|
||||
email=data.email,
|
||||
full_name=data.full_name,
|
||||
hashed_password=hashed_password,
|
||||
disabled=data.disabled,
|
||||
is_admin=data.is_admin,
|
||||
created_by_id=creator_id,
|
||||
)
|
||||
|
||||
if data.role_ids:
|
||||
for role_id in data.role_ids:
|
||||
role = await Role.get_or_none(id=role_id)
|
||||
if role:
|
||||
await UserRole.create(user_id=user.id, role_id=role_id)
|
||||
|
||||
return await cls.get_user(user.id)
|
||||
|
||||
@classmethod
|
||||
async def update_user(cls, user_id: int, data: UserUpdate, operator_id: int) -> UserDetail:
|
||||
user = await UserAccount.get_or_none(id=user_id)
|
||||
if not user:
|
||||
raise HTTPException(404, detail="用户不存在")
|
||||
|
||||
if data.is_admin is not None and user_id == operator_id:
|
||||
raise HTTPException(400, detail="不能修改自己的管理员状态")
|
||||
|
||||
if data.email is not None:
|
||||
existing = await UserAccount.filter(email=data.email).exclude(id=user_id).first()
|
||||
if existing:
|
||||
raise HTTPException(400, detail="邮箱已被使用")
|
||||
user.email = data.email
|
||||
|
||||
if data.full_name is not None:
|
||||
user.full_name = data.full_name
|
||||
|
||||
if data.password is not None:
|
||||
user.hashed_password = AuthService.get_password_hash(data.password)
|
||||
|
||||
if data.is_admin is not None:
|
||||
user.is_admin = data.is_admin
|
||||
|
||||
if data.disabled is not None:
|
||||
if user_id == operator_id and data.disabled:
|
||||
raise HTTPException(400, detail="不能禁用自己")
|
||||
user.disabled = data.disabled
|
||||
|
||||
await user.save()
|
||||
|
||||
PermissionService.clear_cache(user_id)
|
||||
return await cls.get_user(user_id)
|
||||
|
||||
@classmethod
|
||||
async def delete_user(cls, user_id: int, operator_id: int) -> None:
|
||||
if user_id == operator_id:
|
||||
raise HTTPException(400, detail="不能删除自己")
|
||||
|
||||
user = await UserAccount.get_or_none(id=user_id)
|
||||
if not user:
|
||||
raise HTTPException(404, detail="用户不存在")
|
||||
|
||||
await UserRole.filter(user_id=user_id).delete()
|
||||
await user.delete()
|
||||
PermissionService.clear_cache(user_id)
|
||||
|
||||
@classmethod
|
||||
async def set_user_roles(cls, user_id: int, role_ids: List[int]) -> List[str]:
|
||||
user = await UserAccount.get_or_none(id=user_id)
|
||||
if not user:
|
||||
raise HTTPException(404, detail="用户不存在")
|
||||
|
||||
roles = await Role.filter(id__in=role_ids)
|
||||
valid_role_ids = {r.id for r in roles}
|
||||
invalid_ids = set(role_ids) - valid_role_ids
|
||||
if invalid_ids:
|
||||
raise HTTPException(400, detail=f"无效的角色ID: {invalid_ids}")
|
||||
|
||||
await UserRole.filter(user_id=user_id).delete()
|
||||
for role_id in role_ids:
|
||||
await UserRole.create(user_id=user_id, role_id=role_id)
|
||||
|
||||
PermissionService.clear_cache(user_id)
|
||||
return [r.name for r in roles if r.id in role_ids]
|
||||
|
||||
@classmethod
|
||||
async def remove_user_role(cls, user_id: int, role_id: int) -> List[str]:
|
||||
user = await UserAccount.get_or_none(id=user_id)
|
||||
if not user:
|
||||
raise HTTPException(404, detail="用户不存在")
|
||||
|
||||
await UserRole.filter(user_id=user_id, role_id=role_id).delete()
|
||||
PermissionService.clear_cache(user_id)
|
||||
|
||||
user_roles = await UserRole.filter(user_id=user_id).prefetch_related("role")
|
||||
return [ur.role.name for ur in user_roles]
|
||||
|
||||
42
domain/user/types.py
Normal file
42
domain/user/types.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class UserInfo(BaseModel):
|
||||
id: int
|
||||
username: str
|
||||
email: str | None = None
|
||||
full_name: str | None = None
|
||||
disabled: bool
|
||||
is_admin: bool
|
||||
created_at: datetime
|
||||
last_login: datetime | None = None
|
||||
|
||||
|
||||
class UserDetail(UserInfo):
|
||||
roles: list[str]
|
||||
created_by_username: str | None = None
|
||||
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
email: str | None = None
|
||||
full_name: str | None = None
|
||||
is_admin: bool = False
|
||||
disabled: bool = False
|
||||
role_ids: list[int] = []
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
email: str | None = None
|
||||
full_name: str | None = None
|
||||
password: str | None = None
|
||||
is_admin: bool | None = None
|
||||
disabled: bool | None = None
|
||||
|
||||
|
||||
class UserRoleAssign(BaseModel):
|
||||
role_ids: list[int]
|
||||
|
||||
Reference in New Issue
Block a user