Fix API compatibility and add user/role/permission and asset import/export
This commit is contained in:
221
backend/app/api/v1/roles.py
Normal file
221
backend/app/api/v1/roles.py
Normal file
@@ -0,0 +1,221 @@
|
||||
"""
|
||||
Role management API routes.
|
||||
"""
|
||||
from typing import List, Dict
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import select, func, delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_db, get_current_user
|
||||
from app.core.response import success_response
|
||||
from app.models.user import Role, Permission, RolePermission, UserRole
|
||||
from app.schemas.user import RoleCreate, RoleUpdate
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _permission_to_dict(permission: Permission) -> Dict:
|
||||
return {
|
||||
"id": permission.id,
|
||||
"permission_name": permission.permission_name,
|
||||
"permission_code": permission.permission_code,
|
||||
"module": permission.module,
|
||||
"module_name": permission.module,
|
||||
"resource": permission.resource,
|
||||
"action": permission.action,
|
||||
"description": permission.description,
|
||||
"created_at": permission.created_at,
|
||||
}
|
||||
|
||||
|
||||
def _role_to_dict(role: Role, permissions: List[Permission], user_count: int) -> Dict:
|
||||
return {
|
||||
"id": role.id,
|
||||
"role_name": role.role_name,
|
||||
"role_code": role.role_code,
|
||||
"description": role.description,
|
||||
"status": role.status,
|
||||
"sort_order": role.sort_order,
|
||||
"created_at": role.created_at,
|
||||
"permissions": [_permission_to_dict(p) for p in permissions],
|
||||
"user_count": user_count,
|
||||
}
|
||||
|
||||
|
||||
async def _ensure_permissions_exist(db: AsyncSession, permission_ids: List[int]) -> None:
|
||||
if not permission_ids:
|
||||
return
|
||||
result = await db.execute(select(Permission.id).where(Permission.id.in_(permission_ids)))
|
||||
existing_ids = {row[0] for row in result.all()}
|
||||
missing = set(permission_ids) - existing_ids
|
||||
if missing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Invalid permission IDs: {sorted(missing)}",
|
||||
)
|
||||
|
||||
|
||||
async def _set_role_permissions(
|
||||
db: AsyncSession,
|
||||
role_id: int,
|
||||
permission_ids: List[int],
|
||||
operator_id: int,
|
||||
) -> None:
|
||||
await db.execute(delete(RolePermission).where(RolePermission.role_id == role_id))
|
||||
if permission_ids:
|
||||
for permission_id in permission_ids:
|
||||
db.add(
|
||||
RolePermission(
|
||||
role_id=role_id,
|
||||
permission_id=permission_id,
|
||||
created_by=operator_id,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_roles(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(Role).where(Role.deleted_at.is_(None)).order_by(Role.sort_order, Role.id)
|
||||
)
|
||||
roles = list(result.scalars().all())
|
||||
|
||||
role_ids = [role.id for role in roles]
|
||||
permission_map: Dict[int, List[Permission]] = {role.id: [] for role in roles}
|
||||
user_count_map: Dict[int, int] = {role.id: 0 for role in roles}
|
||||
|
||||
if role_ids:
|
||||
perm_result = await db.execute(
|
||||
select(RolePermission.role_id, Permission)
|
||||
.join(Permission, Permission.id == RolePermission.permission_id)
|
||||
.where(RolePermission.role_id.in_(role_ids))
|
||||
)
|
||||
for role_id, permission in perm_result.all():
|
||||
permission_map.setdefault(role_id, []).append(permission)
|
||||
|
||||
count_result = await db.execute(
|
||||
select(UserRole.role_id, func.count(UserRole.user_id))
|
||||
.where(UserRole.role_id.in_(role_ids))
|
||||
.group_by(UserRole.role_id)
|
||||
)
|
||||
for role_id, count in count_result.all():
|
||||
user_count_map[role_id] = count
|
||||
|
||||
items = [
|
||||
_role_to_dict(role, permission_map.get(role.id, []), user_count_map.get(role.id, 0))
|
||||
for role in roles
|
||||
]
|
||||
return success_response(data=items)
|
||||
|
||||
|
||||
@router.post("/", status_code=status.HTTP_201_CREATED)
|
||||
async def create_role(
|
||||
payload: RoleCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
existing = await db.execute(select(Role).where(Role.role_code == payload.role_code))
|
||||
if existing.scalar_one_or_none():
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Role code already exists")
|
||||
|
||||
existing_name = await db.execute(select(Role).where(Role.role_name == payload.role_name))
|
||||
if existing_name.scalar_one_or_none():
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Role name already exists")
|
||||
|
||||
await _ensure_permissions_exist(db, payload.permission_ids)
|
||||
|
||||
role = Role(
|
||||
role_code=payload.role_code,
|
||||
role_name=payload.role_name,
|
||||
description=payload.description,
|
||||
status="active",
|
||||
created_by=current_user.id,
|
||||
)
|
||||
db.add(role)
|
||||
await db.flush()
|
||||
|
||||
await _set_role_permissions(db, role.id, payload.permission_ids, current_user.id)
|
||||
await db.commit()
|
||||
await db.refresh(role)
|
||||
|
||||
perm_result = await db.execute(
|
||||
select(Permission)
|
||||
.join(RolePermission, RolePermission.permission_id == Permission.id)
|
||||
.where(RolePermission.role_id == role.id)
|
||||
)
|
||||
permissions = list(perm_result.scalars().all())
|
||||
|
||||
return success_response(data=_role_to_dict(role, permissions, 0))
|
||||
|
||||
|
||||
@router.put("/{role_id}")
|
||||
async def update_role(
|
||||
role_id: int,
|
||||
payload: RoleUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
result = await db.execute(select(Role).where(Role.id == role_id).where(Role.deleted_at.is_(None)))
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found")
|
||||
|
||||
update_data = payload.model_dump(exclude_unset=True)
|
||||
|
||||
if "role_name" in update_data:
|
||||
role_name = update_data.pop("role_name")
|
||||
existing = await db.execute(
|
||||
select(Role).where(Role.role_name == role_name).where(Role.id != role_id)
|
||||
)
|
||||
if existing.scalar_one_or_none():
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Role name already exists")
|
||||
role.role_name = role_name
|
||||
|
||||
if "description" in update_data:
|
||||
role.description = update_data.pop("description")
|
||||
|
||||
permission_ids = update_data.pop("permission_ids", None)
|
||||
if permission_ids is not None:
|
||||
await _ensure_permissions_exist(db, permission_ids)
|
||||
await _set_role_permissions(db, role.id, permission_ids, current_user.id)
|
||||
|
||||
role.updated_by = current_user.id
|
||||
db.add(role)
|
||||
await db.commit()
|
||||
await db.refresh(role)
|
||||
|
||||
perm_result = await db.execute(
|
||||
select(Permission)
|
||||
.join(RolePermission, RolePermission.permission_id == Permission.id)
|
||||
.where(RolePermission.role_id == role.id)
|
||||
)
|
||||
permissions = list(perm_result.scalars().all())
|
||||
|
||||
count_result = await db.execute(
|
||||
select(func.count(UserRole.user_id)).where(UserRole.role_id == role.id)
|
||||
)
|
||||
user_count = count_result.scalar() or 0
|
||||
|
||||
return success_response(data=_role_to_dict(role, permissions, user_count))
|
||||
|
||||
|
||||
@router.delete("/{role_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_role(
|
||||
role_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
result = await db.execute(select(Role).where(Role.id == role_id).where(Role.deleted_at.is_(None)))
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found")
|
||||
|
||||
role.deleted_at = func.now()
|
||||
role.deleted_by = current_user.id
|
||||
role.status = "disabled"
|
||||
|
||||
await db.commit()
|
||||
return success_response(message="Deleted")
|
||||
Reference in New Issue
Block a user