diff --git a/middleware/dependencies.py b/middleware/dependencies.py index 3c97006..70cfebb 100644 --- a/middleware/dependencies.py +++ b/middleware/dependencies.py @@ -1,9 +1,72 @@ -from typing import Annotated +""" +FastAPI 依赖注入 -from fastapi import Depends +包含 HTTP 端点的通用依赖: +- SessionDep: 数据库会话依赖 +- TimeFilterRequestDep: 时间筛选查询依赖(用于 count 等统计接口) +- TableViewRequestDep: 分页排序查询依赖(包含时间筛选 + 分页排序) +""" +from datetime import datetime +from typing import Annotated, Literal, TypeAlias + +from fastapi import Depends, Query from sqlmodel.ext.asyncio.session import AsyncSession from models.database import get_session +from models.mixin import TimeFilterRequest, TableViewRequest -SessionDep = Annotated[AsyncSession, Depends(get_session)] + +# --- 数据库会话依赖 --- + +SessionDep: TypeAlias = Annotated[AsyncSession, Depends(get_session)] """数据库会话依赖,用于路由函数中获取数据库会话""" + + +# --- 时间筛选依赖 --- + +async def _get_time_filter_queries( + created_after_datetime: Annotated[datetime | None, Query()] = None, + created_before_datetime: Annotated[datetime | None, Query()] = None, + updated_after_datetime: Annotated[datetime | None, Query()] = None, + updated_before_datetime: Annotated[datetime | None, Query()] = None, +) -> TimeFilterRequest: + """解析时间筛选查询参数""" + return TimeFilterRequest( + created_after_datetime=created_after_datetime, + created_before_datetime=created_before_datetime, + updated_after_datetime=updated_after_datetime, + updated_before_datetime=updated_before_datetime, + ) + + +TimeFilterRequestDep: TypeAlias = Annotated[TimeFilterRequest, Depends(_get_time_filter_queries)] +"""获取时间筛选参数的依赖(用于 count 等统计接口)""" + + +# --- 分页排序依赖 --- + +async def _get_table_view_queries( + offset: Annotated[int | None, Query(ge=0)] = 0, + limit: Annotated[int | None, Query(ge=1, le=100)] = 20, + desc: bool | None = True, + order: Literal["created_at", "updated_at"] | None = "created_at", + created_after_datetime: Annotated[datetime | None, Query()] = None, + created_before_datetime: Annotated[datetime | None, Query()] = None, + updated_after_datetime: Annotated[datetime | None, Query()] = None, + updated_before_datetime: Annotated[datetime | None, Query()] = None, +) -> TableViewRequest: + """解析分页排序和时间筛选查询参数""" + return TableViewRequest( + offset=offset, + limit=limit, + desc=desc, + order=order, + created_after_datetime=created_after_datetime, + created_before_datetime=created_before_datetime, + updated_after_datetime=updated_after_datetime, + updated_before_datetime=updated_before_datetime, + ) + + +TableViewRequestDep: TypeAlias = Annotated[TableViewRequest, Depends(_get_table_view_queries)] +"""获取分页排序和时间筛选参数的依赖""" diff --git a/models/__init__.py b/models/__init__.py index ee52ac0..e818884 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -67,7 +67,7 @@ from .object import ( ) from .physical_file import PhysicalFile, PhysicalFileBase from .order import Order, OrderStatus, OrderType -from .policy import Policy, PolicyBase, PolicyOptions, PolicyOptionsBase, PolicyType +from .policy import Policy, PolicyBase, PolicyOptions, PolicyOptionsBase, PolicyType, PolicySummary from .redeem import Redeem, RedeemType from .report import Report, ReportReason from .setting import ( @@ -75,11 +75,11 @@ from .setting import ( # 管理员DTO SettingItem, SettingsListResponse, SettingsUpdateRequest, SettingsUpdateResponse, ) -from .share import Share, ShareBase, ShareCreateRequest, ShareResponse +from .share import Share, ShareBase, ShareCreateRequest, ShareResponse, AdminShareListItem from .source_link import SourceLink from .storage_pack import StoragePack from .tag import Tag, TagType -from .task import Task, TaskProps, TaskPropsBase, TaskStatus, TaskType +from .task import Task, TaskProps, TaskPropsBase, TaskStatus, TaskType, TaskSummary from .webdav import WebDAV from .database import engine, get_session @@ -96,4 +96,7 @@ from .model_base import ( VersionInfo, AdminSummaryData, AdminSummaryResponse, -) \ No newline at end of file +) + +# mixin 中的通用分页模型 +from .mixin import ListResponse \ No newline at end of file diff --git a/models/group.py b/models/group.py index c87bd87..92b1a61 100644 --- a/models/group.py +++ b/models/group.py @@ -124,8 +124,8 @@ class GroupUpdateRequest(SQLModelBase): """关联的存储策略UUID列表""" -class GroupDetailResponse(GroupAllOptionsBase): - """用户组详情响应 DTO""" +class GroupCoreBase(SQLModelBase): + """用户组核心字段(从 Group 模型提取)""" id: UUID """用户组UUID""" @@ -148,12 +148,35 @@ class GroupDetailResponse(GroupAllOptionsBase): speed_limit: int = 0 """速度限制 (KB/s)""" + +class GroupDetailResponse(GroupCoreBase, GroupAllOptionsBase): + """用户组详情响应 DTO""" + user_count: int = 0 """用户数量""" policy_ids: list[UUID] = [] """关联的存储策略UUID列表""" + @classmethod + def from_group( + cls, + group: "Group", + user_count: int, + policies: list["Policy"], + ) -> "GroupDetailResponse": + """从 Group ORM 对象构建""" + opts = group.options + return cls( + # GroupCoreBase 字段(从 Group 模型提取) + **GroupCoreBase.model_validate(group, from_attributes=True).model_dump(), + # GroupAllOptionsBase 字段(从 GroupOptions 提取) + **(GroupAllOptionsBase.model_validate(opts, from_attributes=True).model_dump() if opts else {}), + # 计算字段 + user_count=user_count, + policy_ids=[p.id for p in policies], + ) + class GroupListResponse(SQLModelBase): """用户组列表响应 DTO""" diff --git a/models/object.py b/models/object.py index 1a78d3c..aa2067c 100644 --- a/models/object.py +++ b/models/object.py @@ -695,6 +695,32 @@ class AdminFileResponse(ObjectResponse): ban_reason: str | None = None """封禁原因""" + @classmethod + def from_object( + cls, + obj: "Object", + owner: "User | None", + policy: "Policy | None", + ) -> "AdminFileResponse": + """从 Object ORM 对象构建""" + return cls( + # ObjectBase 字段 + **ObjectBase.model_validate(obj, from_attributes=True).model_dump(), + # ObjectResponse 字段 + id=obj.id, + thumb=False, + date=obj.updated_at, + create_date=obj.created_at, + source_enabled=False, + # AdminFileResponse 字段 + owner_id=obj.owner_id, + owner_username=owner.username if owner else "unknown", + policy_name=policy.name if policy else "unknown", + is_banned=obj.is_banned, + banned_at=obj.banned_at, + ban_reason=obj.ban_reason, + ) + class FileBanRequest(SQLModelBase): """文件封禁请求 DTO""" diff --git a/models/policy.py b/models/policy.py index 9731054..9893d4c 100644 --- a/models/policy.py +++ b/models/policy.py @@ -78,6 +78,34 @@ class PolicyBase(SQLModelBase): """是否开启源链接访问""" +# ==================== DTO 模型 ==================== + + +class PolicySummary(SQLModelBase): + """策略摘要,用于列表展示""" + + id: UUID + """策略UUID""" + + name: str + """策略名称""" + + type: PolicyType + """策略类型""" + + server: str | None + """服务器地址""" + + max_size: int + """最大文件尺寸""" + + is_private: bool + """是否私有""" + + +# ==================== 数据库模型 ==================== + + class PolicyOptionsBase(SQLModelBase): """存储策略选项的基础模型""" diff --git a/models/share.py b/models/share.py index 72da92c..b50fba0 100644 --- a/models/share.py +++ b/models/share.py @@ -160,3 +160,61 @@ class ShareResponse(SQLModelBase): has_password: bool """是否有密码""" + + +class ShareListItemBase(SQLModelBase): + """分享列表项基础字段""" + + id: int + """分享ID""" + + code: str + """分享码""" + + views: int + """浏览次数""" + + downloads: int + """下载次数""" + + remain_downloads: int | None + """剩余下载次数""" + + expires: datetime | None + """过期时间""" + + preview_enabled: bool + """是否允许预览""" + + score: int + """积分""" + + user_id: UUID + """用户UUID""" + + created_at: datetime + """创建时间""" + + +class AdminShareListItem(ShareListItemBase): + """管理员分享列表项 DTO,添加关联字段""" + + username: str | None + """用户名""" + + object_name: str | None + """对象名称""" + + @classmethod + def from_share( + cls, + share: "Share", + user: "User | None", + obj: "Object | None", + ) -> "AdminShareListItem": + """从 Share ORM 对象构建""" + return cls( + **ShareListItemBase.model_validate(share, from_attributes=True).model_dump(), + username=user.username if user else None, + object_name=obj.name if obj else None, + ) diff --git a/models/task.py b/models/task.py index 39327b1..b541235 100644 --- a/models/task.py +++ b/models/task.py @@ -9,8 +9,8 @@ from .base import SQLModelBase from .mixin import TableBaseMixin if TYPE_CHECKING: - from .user import User from .download import Download + from .user import User class TaskStatus(StrEnum): @@ -31,6 +31,55 @@ class TaskType(StrEnum): pass +# ==================== DTO 模型 ==================== + + +class TaskSummaryBase(SQLModelBase): + """任务摘要基础字段""" + + id: int + """任务ID""" + + type: int + """任务类型""" + + status: TaskStatus + """任务状态""" + + progress: int + """进度(0-100)""" + + error: str | None + """错误信息""" + + user_id: UUID + """用户UUID""" + + created_at: datetime + """创建时间""" + + updated_at: datetime + """更新时间""" + + +class TaskSummary(TaskSummaryBase): + """任务摘要,用于管理员列表展示""" + + username: str | None + """用户名""" + + @classmethod + def from_task(cls, task: "Task", user: "User | None") -> "TaskSummary": + """从 Task ORM 对象构建""" + return cls( + **TaskSummaryBase.model_validate(task, from_attributes=True).model_dump(), + username=user.username if user else None, + ) + + +# ==================== 数据库模型 ==================== + + class TaskPropsBase(SQLModelBase): """任务属性基础模型""" diff --git a/routers/api/v1/admin/file/__init__.py b/routers/api/v1/admin/file/__init__.py index d0cd054..0296af2 100644 --- a/routers/api/v1/admin/file/__init__.py +++ b/routers/api/v1/admin/file/__init__.py @@ -8,11 +8,10 @@ from loguru import logger as l from sqlalchemy import and_ from middleware.auth import admin_required -from middleware.dependencies import SessionDep +from middleware.dependencies import SessionDep, TableViewRequestDep from models import ( - Policy, PolicyType, User, ResponseBase, - Object, ObjectType, ) -from models.object import AdminFileResponse, FileBanRequest + Policy, PolicyType, User, ResponseBase, ListResponse, + Object, ObjectType, AdminFileResponse, FileBanRequest, ) from service.storage import LocalStorageService admin_file_router = APIRouter( @@ -28,25 +27,21 @@ admin_file_router = APIRouter( ) async def router_admin_get_file_list( session: SessionDep, + table_view: TableViewRequestDep, user_id: UUID | None = None, is_banned: bool | None = None, keyword: str | None = None, - page: int = 1, - page_size: int = 20, -) -> ResponseBase: +) -> ListResponse[AdminFileResponse]: """ 获取系统中的文件列表,支持筛选。 :param session: 数据库会话 + :param table_view: 分页排序参数依赖 :param user_id: 按用户筛选 :param is_banned: 按封禁状态筛选 :param keyword: 按文件名搜索 - :param page: 页码 - :param page_size: 每页数量 - :return: 文件列表 + :return: 分页文件列表 """ - offset = (page - 1) * page_size - # 构建查询条件 conditions = [Object.type == ObjectType.FILE] if user_id: @@ -56,42 +51,17 @@ async def router_admin_get_file_list( if keyword: conditions.append(Object.name.ilike(f"%{keyword}%")) - combined_condition = and_(*conditions) if len(conditions) > 1 else conditions[0] - - files = await Object.get( - session, - combined_condition, - fetch_mode="all", - offset=offset, - limit=page_size, - load=Object.owner, - ) - - total = await Object.count(session, combined_condition) + condition = and_(*conditions) if len(conditions) > 1 else conditions[0] + result = await Object.get_with_count(session, condition, table_view=table_view, load=Object.owner) # 构建响应 - file_list = [] - for f in files: + items: list[AdminFileResponse] = [] + for f in result.items: owner = await f.awaitable_attrs.owner policy = await f.awaitable_attrs.policy - file_list.append(AdminFileResponse( - id=f.id, - name=f.name, - type=f.type, - size=f.size, - thumb=False, - date=f.updated_at, - create_date=f.created_at, - source_enabled=False, - owner_id=f.owner_id, - owner_username=owner.username if owner else "unknown", - policy_name=policy.name if policy else "unknown", - is_banned=f.is_banned, - banned_at=f.banned_at, - ban_reason=f.ban_reason, - ).model_dump()) + items.append(AdminFileResponse.from_object(f, owner, policy)) - return ResponseBase(data={"files": file_list, "total": total}) + return ListResponse(items=items, count=result.count) @admin_file_router.get( diff --git a/routers/api/v1/admin/group/__init__.py b/routers/api/v1/admin/group/__init__.py index 623ab4f..d010f27 100644 --- a/routers/api/v1/admin/group/__init__.py +++ b/routers/api/v1/admin/group/__init__.py @@ -4,9 +4,9 @@ from fastapi import APIRouter, Depends, HTTPException from loguru import logger as l from middleware.auth import admin_required -from middleware.dependencies import SessionDep +from middleware.dependencies import SessionDep, TableViewRequestDep from models import ( - User, ResponseBase, + User, ResponseBase, UserPublic, ListResponse, Group, GroupOptions, ) from models.group import ( GroupCreateRequest, GroupUpdateRequest, GroupDetailResponse, ) @@ -25,61 +25,25 @@ admin_group_router = APIRouter( ) async def router_admin_get_groups( session: SessionDep, - page: int = 1, - page_size: int = 20, -) -> ResponseBase: + table_view: TableViewRequestDep, +) -> ListResponse[GroupDetailResponse]: """ - 获取用户组列表,支持分页。 + 获取用户组列表,支持分页、排序和时间筛选。 :param session: 数据库会话 - :param page: 页码 - :param page_size: 每页数量 - :return: 用户组列表 + :param table_view: 分页排序参数依赖 + :return: 分页用户组列表 """ - offset = (page - 1) * page_size - - groups = await Group.get( - session, - None, - fetch_mode="all", - offset=offset, - limit=page_size, - load=Group.options, - ) - - total = await Group.count(session, None) + result = await Group.get_with_count(session, table_view=table_view, load=Group.options) # 构建响应 - group_list = [] - for g in groups: - opts = g.options + items: list[GroupDetailResponse] = [] + for g in result.items: policies = await g.awaitable_attrs.policies user_count = await User.count(session, User.group_id == g.id) + items.append(GroupDetailResponse.from_group(g, user_count, policies)) - group_list.append(GroupDetailResponse( - id=g.id, - name=g.name, - max_storage=g.max_storage, - share_enabled=g.share_enabled, - web_dav_enabled=g.web_dav_enabled, - admin=g.admin, - speed_limit=g.speed_limit, - user_count=user_count, - policy_ids=[p.id for p in policies], - share_download=opts.share_download if opts else False, - share_free=opts.share_free if opts else False, - relocate=opts.relocate if opts else False, - source_batch=opts.source_batch if opts else 0, - select_node=opts.select_node if opts else False, - advance_delete=opts.advance_delete if opts else False, - archive_download=opts.archive_download if opts else False, - archive_task=opts.archive_task if opts else False, - webdav_proxy=opts.webdav_proxy if opts else False, - aria2=opts.aria2 if opts else False, - redirected_source=opts.redirected_source if opts else False, - ).model_dump()) - - return ResponseBase(data={"groups": group_list, "total": total}) + return ListResponse(items=items, count=result.count) @admin_group_router.get( @@ -104,32 +68,9 @@ async def router_admin_get_group( if not group: raise HTTPException(status_code=404, detail="用户组不存在") - opts = group.options policies = await group.awaitable_attrs.policies user_count = await User.count(session, User.group_id == group_id) - - response = GroupDetailResponse( - id=group.id, - name=group.name, - max_storage=group.max_storage, - share_enabled=group.share_enabled, - web_dav_enabled=group.web_dav_enabled, - admin=group.admin, - speed_limit=group.speed_limit, - user_count=user_count, - policy_ids=[p.id for p in policies], - share_download=opts.share_download if opts else False, - share_free=opts.share_free if opts else False, - relocate=opts.relocate if opts else False, - source_batch=opts.source_batch if opts else 0, - select_node=opts.select_node if opts else False, - advance_delete=opts.advance_delete if opts else False, - archive_download=opts.archive_download if opts else False, - archive_task=opts.archive_task if opts else False, - webdav_proxy=opts.webdav_proxy if opts else False, - aria2=opts.aria2 if opts else False, - redirected_source=opts.redirected_source if opts else False, - ) + response = GroupDetailResponse.from_group(group, user_count, policies) return ResponseBase(data=response.model_dump()) @@ -143,40 +84,28 @@ async def router_admin_get_group( async def router_admin_get_group_members( session: SessionDep, group_id: UUID, - page: int = 1, - page_size: int = 20, -) -> ResponseBase: + table_view: TableViewRequestDep, +) -> ListResponse[UserPublic]: """ 根据用户组ID获取用户组成员列表。 :param session: 数据库会话 :param group_id: 用户组UUID - :param page: 页码 - :param page_size: 每页数量 - :return: 成员列表 + :param table_view: 分页排序参数依赖 + :return: 分页成员列表 """ # 验证组存在 group = await Group.get(session, Group.id == group_id) if not group: raise HTTPException(status_code=404, detail="用户组不存在") - offset = (page - 1) * page_size + result = await User.get_with_count(session, User.group_id == group_id, table_view=table_view) - users = await User.get( - session, - User.group_id == group_id, - fetch_mode="all", - offset=offset, - limit=page_size, + return ListResponse( + items=[u.to_public() for u in result.items], + count=result.count, ) - total = await User.count(session, User.group_id == group_id) - - return ResponseBase(data={ - "members": [u.to_public().model_dump() for u in users], - "total": total, - }) - @admin_group_router.post( path='/', diff --git a/routers/api/v1/admin/policy/__init__.py b/routers/api/v1/admin/policy/__init__.py index 357bcfa..dc1f30a 100644 --- a/routers/api/v1/admin/policy/__init__.py +++ b/routers/api/v1/admin/policy/__init__.py @@ -5,10 +5,10 @@ from loguru import logger as l from sqlmodel import Field from middleware.auth import admin_required -from middleware.dependencies import SessionDep +from middleware.dependencies import SessionDep, TableViewRequestDep from models import ( - Policy, PolicyBase, PolicyType, ResponseBase, - Object, ) + Policy, PolicyBase, PolicyType, PolicySummary, ResponseBase, + ListResponse, Object, ) from models.base import SQLModelBase from service.storage import DirectoryCreationError, LocalStorageService @@ -45,44 +45,22 @@ class PolicyCreateRequest(PolicyBase): ) async def router_policy_list( session: SessionDep, - page: int = 1, - page_size: int = 20, -) -> ResponseBase: + table_view: TableViewRequestDep, +) -> ListResponse[PolicySummary]: """ 获取所有存储策略列表。 :param session: 数据库会话 - :param page: 页码 - :param page_size: 每页数量 - :return: 策略列表 + :param table_view: 分页排序参数依赖 + :return: 分页策略列表 """ - offset = (page - 1) * page_size + result = await Policy.get_with_count(session, table_view=table_view) - policies = await Policy.get( - session, - None, - fetch_mode="all", - offset=offset, - limit=page_size, + return ListResponse( + items=[PolicySummary.model_validate(p, from_attributes=True) for p in result.items], + count=result.count, ) - total = await Policy.count(session, None) - - return ResponseBase(data={ - "policies": [ - { - "id": str(p.id), - "name": p.name, - "type": p.type.value, - "server": p.server, - "max_size": p.max_size, - "is_private": p.is_private, - } - for p in policies - ], - "total": total, - }) - @admin_policy_router.post( path='/test/path', diff --git a/routers/api/v1/admin/share/__init__.py b/routers/api/v1/admin/share/__init__.py index aacb27b..078f5c9 100644 --- a/routers/api/v1/admin/share/__init__.py +++ b/routers/api/v1/admin/share/__init__.py @@ -4,10 +4,10 @@ from fastapi import APIRouter, Depends, HTTPException from loguru import logger as l from middleware.auth import admin_required -from middleware.dependencies import SessionDep +from middleware.dependencies import SessionDep, TableViewRequestDep from models import ( - ResponseBase, - Share, ) + ResponseBase, ListResponse, + Share, AdminShareListItem, ) admin_share_router = APIRouter( prefix='/share', @@ -22,53 +22,27 @@ admin_share_router = APIRouter( ) async def router_admin_get_share_list( session: SessionDep, + table_view: TableViewRequestDep, user_id: UUID | None = None, - page: int = 1, - page_size: int = 20, -) -> ResponseBase: +) -> ListResponse[AdminShareListItem]: """ 获取分享列表。 :param session: 数据库会话 + :param table_view: 分页排序参数依赖 :param user_id: 按用户筛选 - :param page: 页码 - :param page_size: 每页数量 - :return: 分享列表 + :return: 分页分享列表 """ - offset = (page - 1) * page_size condition = Share.user_id == user_id if user_id else None + result = await Share.get_with_count(session, condition, table_view=table_view, load=Share.user) - shares = await Share.get( - session, - condition, - fetch_mode="all", - offset=offset, - limit=page_size, - load=Share.user, - ) - - total = await Share.count(session, condition) - - share_list = [] - for s in shares: + items: list[AdminShareListItem] = [] + for s in result.items: user = await s.awaitable_attrs.user obj = await s.awaitable_attrs.object - share_list.append({ - "id": s.id, - "code": s.code, - "views": s.views, - "downloads": s.downloads, - "remain_downloads": s.remain_downloads, - "expires": s.expires.isoformat() if s.expires else None, - "preview_enabled": s.preview_enabled, - "score": s.score, - "user_id": str(s.user_id), - "username": user.username if user else None, - "object_name": obj.name if obj else None, - "created_at": s.created_at.isoformat(), - }) + items.append(AdminShareListItem.from_share(s, user, obj)) - return ResponseBase(data={"shares": share_list, "total": total}) + return ListResponse(items=items, count=result.count) @admin_share_router.get( diff --git a/routers/api/v1/admin/task/__init__.py b/routers/api/v1/admin/task/__init__.py index 9c6610e..558c1c8 100644 --- a/routers/api/v1/admin/task/__init__.py +++ b/routers/api/v1/admin/task/__init__.py @@ -5,10 +5,10 @@ from loguru import logger as l from sqlalchemy import and_ from middleware.auth import admin_required -from middleware.dependencies import SessionDep +from middleware.dependencies import SessionDep, TableViewRequestDep from models import ( - ResponseBase, - Task, + ResponseBase, ListResponse, + Task, TaskSummary, ) admin_task_router = APIRouter( @@ -24,23 +24,19 @@ admin_task_router = APIRouter( ) async def router_admin_get_task_list( session: SessionDep, + table_view: TableViewRequestDep, user_id: UUID | None = None, status: str | None = None, - page: int = 1, - page_size: int = 20, -) -> ResponseBase: +) -> ListResponse[TaskSummary]: """ 获取任务列表。 :param session: 数据库会话 + :param table_view: 分页排序参数依赖 :param user_id: 按用户筛选 :param status: 按状态筛选 - :param page: 页码 - :param page_size: 每页数量 - :return: 任务列表 + :return: 分页任务列表 """ - offset = (page - 1) * page_size - conditions = [] if user_id: conditions.append(Task.user_id == user_id) @@ -48,34 +44,14 @@ async def router_admin_get_task_list( conditions.append(Task.status == status) condition = and_(*conditions) if conditions else None + result = await Task.get_with_count(session, condition, table_view=table_view, load=Task.user) - tasks = await Task.get( - session, - condition, - fetch_mode="all", - offset=offset, - limit=page_size, - load=Task.user, - ) - - total = await Task.count(session, condition) - - task_list = [] - for t in tasks: + items: list[TaskSummary] = [] + for t in result.items: user = await t.awaitable_attrs.user - task_list.append({ - "id": t.id, - "status": t.status, - "type": t.type, - "progress": t.progress, - "error": t.error, - "user_id": str(t.user_id), - "username": user.username if user else None, - "created_at": t.created_at.isoformat(), - "updated_at": t.updated_at.isoformat(), - }) + items.append(TaskSummary.from_task(t, user)) - return ResponseBase(data={"tasks": task_list, "total": total}) + return ListResponse(items=items, count=result.count) @admin_task_router.get( diff --git a/routers/api/v1/admin/user/__init__.py b/routers/api/v1/admin/user/__init__.py index 895959e..5357799 100644 --- a/routers/api/v1/admin/user/__init__.py +++ b/routers/api/v1/admin/user/__init__.py @@ -5,9 +5,9 @@ from loguru import logger as l from sqlalchemy import func, and_ from middleware.auth import admin_required -from middleware.dependencies import SessionDep +from middleware.dependencies import SessionDep, TableViewRequestDep from models import ( - User, ResponseBase, + User, ResponseBase, UserPublic, ListResponse, Group, Object, ObjectType, ) from models.user import ( UserAdminUpdateRequest, UserCalibrateResponse, @@ -49,30 +49,19 @@ async def router_admin_get_user(session: SessionDep, user_id: int) -> ResponseBa ) async def router_admin_get_users( session: SessionDep, - page: int = 1, - page_size: int = 20 -) -> ResponseBase: + table_view: TableViewRequestDep, +) -> ListResponse[UserPublic]: """ - 获取用户列表,支持分页。 + 获取用户列表,支持分页、排序和时间筛选。 - Args: - session: 数据库会话依赖项。 - page (int): 页码,默认为1。 - page_size (int): 每页显示的用户数量,默认为20。 - - Returns: - ResponseBase: 包含用户列表的响应模型。 + :param session: 数据库会话依赖项 + :param table_view: 分页排序参数依赖 + :return: 分页用户列表 """ - offset = (page - 1) * page_size - users: list[User] = await User.get( - session, - None, - fetch_mode="all", - offset=offset, - limit=page_size - ) - return ResponseBase( - data=[user.to_public().model_dump() for user in users] + result = await User.get_with_count(session, table_view=table_view) + return ListResponse( + items=[user.to_public() for user in result.items], + count=result.count, )