feat(backend): 无界列表端点加分页——projects/templates 支持 limit/offset
GET /projects 与 GET /templates 原一次返回全部 owner 行,行数随数据线性增长。 新增可复用分页依赖 ww_api.pagination.get_page(limit∈[1,200] 默认 50、offset≥0, 越界 → 422),两端点接入,Repository.list_for_owner 加 keyword-only limit/offset 把 LIMIT/OFFSET 下推 DB。向后兼容:不传参返回第一页。补 projects 分页 + 边界 422 测试。
This commit is contained in:
37
apps/api/ww_api/pagination.py
Normal file
37
apps/api/ww_api/pagination.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""列表端点的分页参数(`limit`/`offset`,边界校验 + 合理默认)。
|
||||
|
||||
无界列表端点(owner 维度:projects / templates)原来一次返回全部行,行数随用户
|
||||
数据线性增长。这里提供一个可复用的 FastAPI 依赖 `get_page`,把 `limit`/`offset`
|
||||
在边界处校验(越界 → 422)后交端点。默认返回第一页(`offset=0` + `DEFAULT_PAGE_LIMIT`),
|
||||
不传参的老客户端行为不破坏;`limit` 有上限(`MAX_PAGE_LIMIT`)防一次性拉爆。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, Query
|
||||
|
||||
# 合理默认 + 上限(单用户原型:默认页足够覆盖常见数据量;上限防一次拉全表)。
|
||||
DEFAULT_PAGE_LIMIT = 50
|
||||
MAX_PAGE_LIMIT = 200
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Page:
|
||||
"""一页的边界(不可变):`limit` 条数上限、`offset` 起始偏移。"""
|
||||
|
||||
limit: int
|
||||
offset: int
|
||||
|
||||
|
||||
def get_page(
|
||||
limit: Annotated[int, Query(ge=1, le=MAX_PAGE_LIMIT)] = DEFAULT_PAGE_LIMIT,
|
||||
offset: Annotated[int, Query(ge=0)] = 0,
|
||||
) -> Page:
|
||||
"""校验并封装分页参数(`limit∈[1,MAX]`、`offset≥0`;越界 FastAPI 返 422)。"""
|
||||
return Page(limit=limit, offset=offset)
|
||||
|
||||
|
||||
PageDep = Annotated[Page, Depends(get_page)]
|
||||
@@ -46,6 +46,7 @@ from ww_llm_gateway import Gateway
|
||||
from ww_shared import AppError, ErrorCode, ErrorEnvelope
|
||||
|
||||
from ww_api.logging_config import get_logger
|
||||
from ww_api.pagination import PageDep
|
||||
from ww_api.schemas.injection import (
|
||||
InjectionEntity,
|
||||
InjectionEntityRef,
|
||||
@@ -144,8 +145,9 @@ async def create_project(body: ProjectCreateRequest, repo: ProjectRepoDep) -> Pr
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_projects(repo: ProjectRepoDep) -> ProjectListResponse:
|
||||
views = await repo.list_for_owner(STUB_OWNER_ID)
|
||||
async def list_projects(repo: ProjectRepoDep, page: PageDep) -> ProjectListResponse:
|
||||
"""作品列表(按 created_at 升序,分页)。`?limit=&offset=`,默认第一页。"""
|
||||
views = await repo.list_for_owner(STUB_OWNER_ID, limit=page.limit, offset=page.offset)
|
||||
return ProjectListResponse(projects=[_to_response(v) for v in views])
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ from ww_db import get_session
|
||||
from ww_shared import AppError, ErrorCode
|
||||
|
||||
from ww_api.logging_config import get_logger
|
||||
from ww_api.pagination import PageDep
|
||||
from ww_api.schemas.templates import TemplateCreateRequest, TemplateResponse
|
||||
from ww_api.services.credentials import STUB_OWNER_ID
|
||||
from ww_api.services.project_deps import get_template_repo
|
||||
@@ -36,9 +37,9 @@ SessionDep = Annotated[AsyncSession, Depends(get_session)]
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_templates(repo: TemplateRepoDep) -> list[TemplateResponse]:
|
||||
"""列出当前用户(stub)的模板。"""
|
||||
views = await repo.list_for_owner(STUB_OWNER_ID)
|
||||
async def list_templates(repo: TemplateRepoDep, page: PageDep) -> list[TemplateResponse]:
|
||||
"""列出当前用户(stub)的模板(分页;`?limit=&offset=`,默认第一页)。"""
|
||||
views = await repo.list_for_owner(STUB_OWNER_ID, limit=page.limit, offset=page.offset)
|
||||
return [
|
||||
TemplateResponse(
|
||||
id=v.id, title=v.title, body=v.body, category=v.category, tool_key=v.tool_key
|
||||
|
||||
Reference in New Issue
Block a user