Skip to content

Commit eb63032

Browse files
perf: User and workspace api adapt new ui
1 parent 6d39a77 commit eb63032

File tree

9 files changed

+215
-63
lines changed

9 files changed

+215
-63
lines changed

backend/apps/settings/api/terminology.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async def pager(
1818
paginator = Paginator(session)
1919
filters = {}
2020
return await paginator.get_paginated_response(
21-
model=term_model,
21+
stmt=term_model,
2222
pagination=pagination,
2323
**filters)
2424

backend/apps/system/api/user.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from fastapi import APIRouter
2-
from apps.system.crud.user import get_db_user
2+
from apps.system.crud.user import get_db_user, user_ws_options
33
from apps.system.models.user import UserModel
44
from apps.system.schemas.auth import CacheName, CacheNamespace
5-
from apps.system.schemas.system_schema import PwdEditor, UserCreator, UserEditor, UserGrid, UserLanguage
6-
from common.core.deps import CurrentUser, SessionDep
5+
from apps.system.schemas.system_schema import PwdEditor, UserCreator, UserEditor, UserGrid, UserLanguage, UserWs
6+
from common.core.deps import CurrentUser, SessionDep, Trans
77
from common.core.pagination import Paginator
88
from common.core.schemas import PaginatedResponse, PaginationParams
99
from common.core.security import md5pwd, verify_md5pwd
@@ -14,7 +14,6 @@
1414
async def user_info(current_user: CurrentUser):
1515
return current_user
1616

17-
1817
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[UserGrid])
1918
async def pager(
2019
session: SessionDep,
@@ -25,10 +24,25 @@ async def pager(
2524
paginator = Paginator(session)
2625
filters = {}
2726
return await paginator.get_paginated_response(
28-
model=UserModel,
27+
stmt=UserModel,
2928
pagination=pagination,
3029
**filters)
3130

31+
@router.get("/ws")
32+
async def ws_options(session: SessionDep, current_user: CurrentUser, trans: Trans) -> list[UserWs]:
33+
return await user_ws_options(session, current_user.id, trans)
34+
35+
@router.put("/ws/{oid}")
36+
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
37+
async def ws_change(session: SessionDep, current_user: CurrentUser, oid: int):
38+
ws_list: list[UserWs] = await user_ws_options(session, current_user.id)
39+
if not any(x.id == oid for x in ws_list):
40+
raise RuntimeError(f"oid [{oid}] is invalid!")
41+
user_model: UserModel = get_db_user(session = session, user_id = current_user.id)
42+
user_model.oid = oid
43+
session.add(user_model)
44+
session.commit()
45+
3246
@router.get("/{id}", response_model=UserEditor)
3347
async def query(session: SessionDep, id: int) -> UserEditor:
3448
db_user: UserModel = get_db_user(session = session, user_id = id)
@@ -58,6 +72,11 @@ async def delete(session: SessionDep, id: int):
5872
user_model: UserModel = get_db_user(session = session, user_id = id)
5973
session.delete(user_model)
6074
session.commit()
75+
76+
@router.delete("")
77+
async def batch_del(session: SessionDep, id_list: list[int]):
78+
for id in id_list:
79+
delete(session, id)
6180

6281
@router.put("/language")
6382
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
@@ -69,7 +88,6 @@ async def langChange(session: SessionDep, current_user: CurrentUser, language: U
6988
db_user.language = lang
7089
session.add(db_user)
7190
session.commit()
72-
return {"message": "Language changed successfully", "language": lang}
7391

7492
@router.put("/pwd")
7593
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")

backend/apps/system/api/workspace.py

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,78 @@
1-
from fastapi import APIRouter
2-
from sqlmodel import select
1+
from typing import Optional
2+
from fastapi import APIRouter, Query
3+
from sqlmodel import or_, select
34
from apps.system.models.system_model import UserWsModel, WorkspaceBase, WorkspaceEditor, WorkspaceModel
4-
from apps.system.schemas.system_schema import UserWsBase, UserWsDTO
5-
from common.core.deps import SessionDep, Trans
5+
from apps.system.models.user import UserModel
6+
from apps.system.schemas.system_schema import UserWsBase, UserWsDTO, WorkspaceUser
7+
from common.core.deps import CurrentUser, SessionDep, Trans
8+
from common.core.pagination import Paginator
9+
from common.core.schemas import PaginatedResponse, PaginationParams
610
from common.utils.time import get_timestamp
711

812
router = APIRouter(tags=["system/workspace"], prefix="/system/workspace")
913

14+
15+
@router.get("/uws/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[WorkspaceUser])
16+
async def pager(
17+
session: SessionDep,
18+
current_user: CurrentUser,
19+
pageNum: int,
20+
pageSize: int,
21+
keyword: Optional[str] = Query(None, description="搜索关键字(可选)"),
22+
oid: Optional[int] = Query(None, description="空间ID(仅admin用户生效)"),
23+
):
24+
if current_user.isAdmin:
25+
if not oid:
26+
raise RuntimeError('oid miss error')
27+
workspace_id = oid
28+
else:
29+
workspace_id = current_user.oid
30+
pagination = PaginationParams(page=pageNum, size=pageSize)
31+
paginator = Paginator(session)
32+
stmt = select(UserModel.id, UserModel.account, UserModel.name, UserModel.email, UserModel.status, UserModel.create_time, UserModel.oid, UserWsModel.weight).join(
33+
UserWsModel, UserModel.id == UserWsModel.uid
34+
).where(
35+
UserWsModel.oid == workspace_id,
36+
).order_by(UserModel.create_time)
37+
38+
if keyword:
39+
keyword_pattern = f"%{keyword}%"
40+
stmt = stmt.where(
41+
or_(
42+
UserModel.account.ilike(keyword_pattern),
43+
UserModel.name.ilike(keyword_pattern),
44+
UserModel.email.ilike(keyword_pattern)
45+
)
46+
)
47+
return await paginator.get_paginated_response(
48+
stmt=stmt,
49+
pagination=pagination,
50+
)
51+
52+
53+
@router.post("/uws")
54+
async def create(session: SessionDep, creator: UserWsDTO):
55+
# 判断uid_list以及oid合法性
56+
db_model_list = [
57+
UserWsModel.model_validate({
58+
"oid": creator.oid,
59+
"uid": uid,
60+
"weight": creator.weight
61+
})
62+
for uid in creator.uid_list
63+
]
64+
session.add_all(db_model_list)
65+
session.commit()
66+
67+
@router.delete("/uws")
68+
async def delete(session: SessionDep, dto: UserWsBase):
69+
db_model_list: list[UserWsModel] = session.exec(select(UserWsModel).where(UserWsModel.uid.in_(dto.uid_list), UserWsModel.oid == dto.oid)).all()
70+
if not db_model_list:
71+
raise ValueError(f"UserWsModel not found")
72+
for db_model in db_model_list:
73+
session.delete(db_model)
74+
session.commit()
75+
1076
@router.get("", response_model=list[WorkspaceModel])
1177
async def query(session: SessionDep, trans: Trans):
1278
list_result = session.exec(select(WorkspaceModel).order_by(WorkspaceModel.create_time)).all()
@@ -50,16 +116,4 @@ async def delete(session: SessionDep, id: int):
50116
session.delete(db_model)
51117
session.commit()
52118

53-
@router.post("/uws")
54-
async def create(session: SessionDep, creator: UserWsDTO):
55-
db_model = UserWsModel.model_validate(creator)
56-
session.add(db_model)
57-
session.commit()
58119

59-
@router.delete("/uws")
60-
async def delete(session: SessionDep, dto: UserWsBase):
61-
db_model: UserWsModel = session.exec(select(UserWsModel).where(UserWsModel.uid == dto.uid, UserWsModel.oid == dto.oid)).first()
62-
if not db_model:
63-
raise ValueError(f"UserWsModel not found")
64-
session.delete(db_model)
65-
session.commit()

backend/apps/system/crud/user.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11

2+
from typing import Optional
23
from sqlmodel import Session, select
34

4-
from apps.system.models.system_model import UserWsModel
5+
from apps.system.models.system_model import UserWsModel, WorkspaceModel
56
from apps.system.schemas.auth import CacheName, CacheNamespace
6-
from apps.system.schemas.system_schema import BaseUserDTO, UserInfoDTO
7+
from apps.system.schemas.system_schema import BaseUserDTO, UserInfoDTO, UserWs
78
from common.core.sqlbot_cache import cache
9+
from common.utils.locale import I18n
810
from ..models.user import UserModel
911
from common.core.security import verify_md5pwd
1012

@@ -36,4 +38,21 @@ def authenticate(*, session: Session, account: str, password: str) -> BaseUserDT
3638
return None
3739
if not verify_md5pwd(password, db_user.password):
3840
return None
39-
return db_user
41+
return db_user
42+
43+
async def user_ws_options(session: Session, uid: int, trans: Optional[I18n] = None) -> list[UserWs]:
44+
if uid == 1:
45+
stmt = select(WorkspaceModel.id, WorkspaceModel.name).order_by(WorkspaceModel.create_time)
46+
else:
47+
stmt = select(UserWsModel.oid, WorkspaceModel.name).join(
48+
WorkspaceModel, UserWsModel.oid == WorkspaceModel.id
49+
).where(
50+
UserWsModel.uid == uid,
51+
).order_by(WorkspaceModel.create_time)
52+
result = session.exec(stmt)
53+
if not trans:
54+
return result.all()
55+
return [
56+
UserWs(id = id, name = trans(name) if name.startswith('i18n') else name)
57+
for id, name in result.all()
58+
]

backend/apps/system/schemas/system_schema.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class PwdEditor(BaseModel):
4343
new_pwd: str
4444

4545
class UserWsBase(BaseModel):
46-
uid: int
46+
uid_list: list[int]
4747
oid: int
4848
class UserWsDTO(UserWsBase):
4949
weight: int = 0
@@ -83,4 +83,11 @@ def __init__(
8383
domain_match=domain_match,
8484
token=token,
8585
**kwargs
86-
)
86+
)
87+
88+
class WorkspaceUser(UserEditor):
89+
weight: int
90+
create_time: int
91+
92+
class UserWs(BaseCreatorDTO):
93+
name: str

backend/common/core/pagination.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,86 @@
1-
from sqlmodel.ext.asyncio.session import AsyncSession
2-
from sqlmodel import select, func, SQLModel
3-
from typing import Type, TypeVar, Sequence, Optional
1+
from sqlalchemy import Row, Select
2+
from sqlmodel import Session, select, func, SQLModel
3+
from typing import Dict, Type, TypeVar, Sequence, Optional
44
from common.core.schemas import PaginationParams, PaginatedResponse
5+
from sqlmodel.sql.expression import SelectOfScalar
6+
from typing import Union, Any
57

68
ModelT = TypeVar('ModelT', bound=SQLModel)
79

810
class Paginator:
9-
def __init__(self, session: AsyncSession):
11+
def __init__(self, session: Session):
1012
self.session = session
11-
13+
def _process_result_row(self, row: Row) -> Dict[str, Any]:
14+
result_dict = {}
15+
for item, key in zip(row, row._fields):
16+
if isinstance(item, SQLModel):
17+
result_dict.update(item.dict())
18+
else:
19+
result_dict[key] = item
20+
21+
return result_dict
1222
async def paginate(
1323
self,
14-
model: Type[ModelT],
24+
stmt: Union[Select, SelectOfScalar, Type[ModelT]],
1525
page: int = 1,
1626
size: int = 20,
1727
order_by: Optional[str] = None,
1828
desc: bool = False,
1929
**filters
20-
) -> tuple[Sequence[ModelT], int]:
30+
) -> tuple[Sequence[Any], int]:
2131
offset = (page - 1) * size
22-
stmt = select(model)
32+
single_model: bool = False
33+
if isinstance(stmt, type) and issubclass(stmt, SQLModel):
34+
stmt = select(stmt)
35+
single_model = True
2336

37+
# 应用过滤条件
2438
for field, value in filters.items():
2539
if value is not None:
26-
stmt = stmt.where(getattr(model, field) == value)
40+
# 处理关联模型的字段 (如 user.name)
41+
if '.' in field:
42+
related_model, related_field = field.split('.')
43+
# 这里需要根据实际关联关系调整
44+
stmt = stmt.where(getattr(getattr(stmt.selected_columns, related_model), related_field) == value)
45+
else:
46+
stmt = stmt.where(getattr(stmt.selected_columns, field) == value)
2747

48+
# 应用排序
2849
if order_by:
29-
column = getattr(model, order_by)
50+
if '.' in order_by:
51+
related_model, related_field = order_by.split('.')
52+
column = getattr(getattr(stmt.selected_columns, related_model), related_field)
53+
else:
54+
column = getattr(stmt.selected_columns, order_by)
3055
stmt = stmt.order_by(column.desc() if desc else column.asc())
3156

32-
count_stmt = select(func.count()).select_from(model)
33-
for field, value in filters.items():
34-
if value is not None:
35-
count_stmt = count_stmt.where(getattr(model, field) == value)
36-
57+
# 计算总数
58+
""" count_stmt = stmt.with_only_columns(func.count(), maintain_column_froms=True)
3759
result = self.session.exec(count_stmt)
38-
total = result.first()
60+
total: int = result.first() """
61+
count_stmt = select(func.count()).select_from(stmt.subquery())
62+
total_result = self.session.exec(count_stmt)
63+
total: int = total_result.first()
3964

65+
# 应用分页
4066
stmt = stmt.offset(offset).limit(size)
4167

68+
# 执行查询
4269
result = self.session.exec(stmt)
43-
items = result.all()
44-
70+
if not single_model:
71+
items = [self._process_result_row(row) for row in result]
72+
else:
73+
items = result.all()
4574
return items, total
4675

4776
async def get_paginated_response(
4877
self,
49-
model: Type[ModelT],
78+
stmt: Union[Select, SelectOfScalar, Type[ModelT]],
5079
pagination: PaginationParams,
5180
**filters
52-
) -> PaginatedResponse[ModelT]:
81+
) -> PaginatedResponse[Any]:
5382
items, total = await self.paginate(
54-
model=model,
83+
stmt=stmt,
5584
page=pagination.page,
5685
size=pagination.size,
5786
order_by=pagination.order_by,
@@ -61,7 +90,7 @@ async def get_paginated_response(
6190

6291
total_pages = (total + pagination.size - 1) // pagination.size
6392

64-
return PaginatedResponse[ModelT](
93+
return PaginatedResponse[Any](
6594
items=items,
6695
total=total,
6796
page=pagination.page,

frontend/src/api/auth.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ export const userApi = {
99
query: (id: number) => request.get(`/settings/terminology/${id}`),
1010
language: (data: any) => request.put('/user/language', data),
1111
pwd: (data: any) => request.put('/user/pwd', data),
12+
ws_options: () => request.get('/user/ws'),
13+
ws_change: (oid: number) => request.put(`/user/ws/${oid}`),
1214
}

0 commit comments

Comments
 (0)