Skip to content

Add role-related interfaces #89

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions backend/app/api/v1/role.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,56 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from typing import Annotated

from fastapi import APIRouter, Query, Request

from backend.app.common.casbin_rbac import DependsRBAC
from backend.app.common.jwt import DependsJwtAuth
from backend.app.common.pagination import PageDepends, paging_data
from backend.app.common.response.response_schema import response_base
from backend.app.database.db_mysql import CurrentSession
from backend.app.schemas.role import GetAllRole, CreateRole, UpdateRole
from backend.app.services.role_service import RoleService
from backend.app.utils.serializers import select_to_json

router = APIRouter()

# TODO: 添加 role 相关接口

@router.get('/{pk}', summary='获取角色详情', dependencies=[DependsJwtAuth])
async def get_role(pk: int):
role = await RoleService.get(pk=pk)
data = GetAllRole(**select_to_json(role))
return response_base.success(data=data)


@router.get('', summary='(模糊条件)分页获取所有角色', dependencies=[DependsJwtAuth, PageDepends])
async def get_all_roles(
db: CurrentSession,
name: Annotated[str | None, Query()] = None,
data_scope: Annotated[int | None, Query()] = None,
):
role_select = await RoleService.get_select(name=name, data_scope=data_scope)
page_data = await paging_data(db, role_select, GetAllRole)
return response_base.success(data=page_data)


@router.post('', summary='创建角色', dependencies=[DependsRBAC])
async def create_role(request: Request, obj: CreateRole):
await RoleService.create(obj=obj, user_id=request.user.id)
return response_base.success()


@router.put('/{pk}', summary='更新角色', dependencies=[DependsRBAC])
async def update_role(request: Request, pk: int, obj: UpdateRole):
count = await RoleService.update(pk=pk, obj=obj, user_id=request.user.id)
if count > 0:
return response_base.success()
return response_base.fail()


@router.delete('', summary='(批量)删除角色', dependencies=[DependsRBAC])
async def delete_role(pk: Annotated[list[int], Query(...)]):
count = await RoleService.delete(pk=pk)
if count > 0:
return response_base.success()
return response_base.fail()
4 changes: 2 additions & 2 deletions backend/app/crud/crud_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@


class CRUDMenu(CRUDBase[Menu, CreateMenu, UpdateMenu]):
# TODO: 添加 menu 相关数据库操作
pass
async def get(self, db, menu_id: int) -> Menu | None:
return await self.get_(db, menu_id)


MenuDao: CRUDMenu = CRUDMenu(Menu)
61 changes: 59 additions & 2 deletions backend/app/crud/crud_role.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,70 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import NoReturn

from sqlalchemy import select, update, delete
from sqlalchemy.orm import selectinload

from backend.app.crud.base import CRUDBase
from backend.app.models import Role
from backend.app.models import Role, Menu
from backend.app.schemas.role import CreateRole, UpdateRole


class CRUDRole(CRUDBase[Role, CreateRole, UpdateRole]):
async def get(self, db, role_id: int):
async def get(self, db, role_id: int) -> Role | None:
return await self.get_(db, role_id)

async def get_with_relation(self, db, role_id: int) -> Role | None:
role = await db.execute(
select(self.model)
.options(selectinload(self.model.menus))
.where(self.model.id == role_id)
)
return role.scalars().first()

async def get_all(self, name: str = None, data_scope: int = None):
se = select(self.model).options(selectinload(self.model.menus)).order_by(self.model.created_time.desc())
where_list = []
if name:
where_list.append(self.model.name.like(f'%{name}%'))
if data_scope:
where_list.append(self.model.data_scope == data_scope)
if where_list:
se = se.where(*where_list)
return se

async def get_by_name(self, db, name: str) -> Role | None:
role = await db.execute(select(self.model).where(self.model.name == name))
return role.scalars().first()

async def create(self, db, obj_in: CreateRole, user_id: int) -> NoReturn:
new_role = self.model(**obj_in.dict(exclude={'menus'}), create_user=user_id)
menu_list = []
for menu_id in obj_in.menus:
menu_list.append(await db.get(Menu, menu_id))
new_role.menus.append(*menu_list)
db.add(new_role)

async def update(self, db, role_id: int, obj_in: UpdateRole, user_id: int) -> int:
role = await db.execute(
update(self.model)
.where(self.model.id == role_id)
.values(**obj_in.dict(exclude={'menus'}), update_user=user_id)
)
current_role = await self.get_with_relation(db, role_id)
# 删除角色所有菜单
for i in list(current_role.menus):
current_role.menus.remove(i)
# 添加角色菜单
menu_list = []
for menu_id in obj_in.menus:
menu_list.append(await db.get(Menu, menu_id))
current_role.menus.append(*menu_list)
return role.rowcount

async def delete(self, db, role_id: list[int]) -> int:
roles = await db.execute(delete(self.model).where(self.model.id.in_(role_id)))
return roles.rowcount


RoleDao: CRUDRole = CRUDRole(Role)
2 changes: 1 addition & 1 deletion backend/app/models/sys_dept.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ class Dept(Base):
phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机')
email: Mapped[str | None] = mapped_column(String(50), default=None, comment='邮箱')
status: Mapped[bool] = mapped_column(default=True, comment='部门状态(0停用 1正常)')
del_flag: Mapped[bool] = mapped_column(default=True, comment='删除标志(0删除 1存在)')
del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)')
# 用户部门一对多
users: Mapped['User'] = relationship(init=False, back_populates='dept') # noqa: F821
2 changes: 1 addition & 1 deletion backend/app/models/sys_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Menu(Base):
menu_type: Mapped[int] = mapped_column(default=0, comment='菜单类型(0目录 1菜单 2按钮)')
icon: Mapped[str | None] = mapped_column(String(100), default='#', comment='菜单图标')
remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注')
del_flag: Mapped[bool] = mapped_column(default=True, comment='删除标志(0删除 1存在)')
del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)')
# 菜单角色多对多
roles: Mapped[list['Role']] = relationship( # noqa: F821
init=False, secondary=sys_role_menu, back_populates='menus'
Expand Down
3 changes: 1 addition & 2 deletions backend/app/models/sys_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ class Role(Base):

id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(20), unique=True, comment='角色名称')
sort: Mapped[int] = mapped_column(default=0, comment='显示顺序')
data_scope: Mapped[int | None] = mapped_column(default=2, comment='数据范围(1:全部数据权限 2:自定数据权限)')
del_flag: Mapped[bool] = mapped_column(default=True, comment='删除标志(0删除 1存在)')
del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)')
# 角色用户多对多
users: Mapped[list['User']] = relationship( # noqa: F821
init=False, secondary=sys_user_role, back_populates='roles'
Expand Down
5 changes: 2 additions & 3 deletions backend/app/schemas/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

class RoleBase(BaseModel):
name: str
sort: int = Field(default=0, ge=0, description='排序')
data_scope: int | None = Field(default=RoleDataScope.custom, description='数据范围(1:全部数据权限 2:自定数据权限)') # noqa: E501
del_flag: bool

Expand All @@ -22,11 +21,11 @@ def check_data_scope(cls, v):


class CreateRole(RoleBase):
menu_ids: list[int]
menus: list[int]


class UpdateRole(RoleBase):
menu_ids: list[int]
menus: list[int]


class GetAllRole(RoleBase):
Expand Down
2 changes: 1 addition & 1 deletion backend/app/services/api_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
class ApiService:
@staticmethod
async def get(*, pk: int) -> Api:
async with async_db_session.begin() as db:
async with async_db_session() as db:
api = await ApiDao.get(db, pk)
if not api:
raise errors.NotFoundError(msg='接口不存在')
Expand Down
56 changes: 54 additions & 2 deletions backend/app/services/role_service.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,59 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sqlalchemy import Select

from backend.app.common.exception import errors
from backend.app.crud.crud_menu import MenuDao
from backend.app.crud.crud_role import RoleDao
from backend.app.database.db_mysql import async_db_session
from backend.app.models import Role
from backend.app.schemas.role import CreateRole, UpdateRole


class RoleService:
# TODO: 添加 role 相关服务
pass
@staticmethod
async def get(pk: int) -> Role:
async with async_db_session() as db:
role = await RoleDao.get_with_relation(db, pk)
if not role:
raise errors.NotFoundError(msg='角色不存在')
return role

@staticmethod
async def get_select(*, name: str = None, data_scope: int = None) -> Select:
return await RoleDao.get_all(name=name, data_scope=data_scope)

@staticmethod
async def create(*, obj: CreateRole, user_id: int) -> None:
async with async_db_session.begin() as db:
role = await RoleDao.get_by_name(db, obj.name)
if role:
raise errors.ForbiddenError(msg='角色已存在')
for menu_id in obj.menus:
menu = await MenuDao.get(db, menu_id)
if not menu:
raise errors.ForbiddenError(msg='菜单不存在')
await RoleDao.create(db, obj, user_id)

@staticmethod
async def update(*, pk: int, obj: UpdateRole, user_id: int) -> int:
async with async_db_session.begin() as db:
role = await RoleDao.get(db, pk)
if not role:
raise errors.NotFoundError(msg='角色不存在')
if role.name != obj.name:
role = await RoleDao.get_by_name(db, obj.name)
if role:
raise errors.ForbiddenError(msg='角色已存在')
for menu_id in obj.menus:
menu = await MenuDao.get(db, menu_id)
if not menu:
raise errors.ForbiddenError(msg='菜单不存在')
count = await RoleDao.update(db, pk, obj, user_id)
return count

@staticmethod
async def delete(*, pk: list[int]) -> int:
async with async_db_session.begin() as db:
count = await RoleDao.delete(db, pk)
return count