diff --git a/README.md b/README.md index 70831a8..e1ea154 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,17 @@ Agent/人类协同任务管理平台 - FastAPI 后端 - `GET /auth/me` - 获取当前用户信息 ### Issues + +> Issues 和 Search 列表接口返回分页格式:`{items, total, page, page_size, total_pages}` +> Issues 支持排序参数:`sort_by` (created_at/priority/title/due_date/status), `sort_order` (asc/desc) +> Issues 支持额外过滤:`assignee_id`, `tag` + + +> Issues 和 Search 列表接口返回分页格式: +> Issues 支持排序参数: (created_at/priority/title/due_date/status), (asc/desc) +> Issues 支持额外过滤:, - `POST /issues` - 创建 issue(支持 resolution 决议案类型) -- `GET /issues` - 列表(支持按 project/status/type 过滤) +- `GET /issues` - 列表(分页、排序、按 assignee/tag 过滤)(支持按 project/status/type 过滤) - `GET /issues/{id}` - 详情 - `PATCH /issues/{id}` - 更新 - `DELETE /issues/{id}` - 删除 diff --git a/app/main.py b/app/main.py index 3f0464e..2e6f11a 100644 --- a/app/main.py +++ b/app/main.py @@ -110,26 +110,64 @@ def create_issue(issue: schemas.IssueCreate, bg: BackgroundTasks, db: Session = return db_issue -@app.get("/issues", response_model=List[schemas.IssueResponse]) +import math + +@app.get("/issues") def list_issues( project_id: int = None, issue_status: str = None, issue_type: str = None, - skip: int = 0, - limit: int = 100, + assignee_id: int = None, + tag: str = None, + sort_by: str = "created_at", + sort_order: str = "desc", + page: int = 1, + page_size: int = 50, db: Session = Depends(get_db) ): + """List issues with filtering, sorting, and pagination metadata.""" query = db.query(models.Issue) - + if project_id: query = query.filter(models.Issue.project_id == project_id) if issue_status: query = query.filter(models.Issue.status == issue_status) if issue_type: query = query.filter(models.Issue.issue_type == issue_type) - - issues = query.offset(skip).limit(limit).all() - return issues + if assignee_id: + query = query.filter(models.Issue.assignee_id == assignee_id) + if tag: + query = query.filter(models.Issue.tags.contains(tag)) + + # Sorting + sort_fields = { + "created_at": models.Issue.created_at, + "updated_at": models.Issue.updated_at, + "priority": models.Issue.priority, + "title": models.Issue.title, + "due_date": models.Issue.due_date, + "status": models.Issue.status, + } + sort_col = sort_fields.get(sort_by, models.Issue.created_at) + if sort_order == "asc": + query = query.order_by(sort_col.asc()) + else: + query = query.order_by(sort_col.desc()) + + total = query.count() + page = max(1, page) + page_size = min(max(1, page_size), 200) + total_pages = math.ceil(total / page_size) if total else 1 + skip = (page - 1) * page_size + items = query.offset(skip).limit(page_size).all() + + return { + "items": [schemas.IssueResponse.model_validate(i) for i in items], + "total": total, + "page": page, + "page_size": page_size, + "total_pages": total_pages, + } @app.get("/issues/overdue", response_model=List[schemas.IssueResponse]) @@ -462,21 +500,35 @@ def transition_issue(issue_id: int, new_status: str, bg: BackgroundTasks, db: Se # ============ Search API ============ -@app.get("/search/issues", response_model=List[schemas.IssueResponse]) +@app.get("/search/issues") def search_issues( q: str, project_id: int = None, - skip: int = 0, - limit: int = 50, + page: int = 1, + page_size: int = 50, db: Session = Depends(get_db) ): - """Search issues by title or description keyword.""" + """Search issues by title or description keyword with pagination.""" query = db.query(models.Issue).filter( (models.Issue.title.contains(q)) | (models.Issue.description.contains(q)) ) if project_id: query = query.filter(models.Issue.project_id == project_id) - return query.offset(skip).limit(limit).all() + + total = query.count() + page = max(1, page) + page_size = min(max(1, page_size), 200) + total_pages = math.ceil(total / page_size) if total else 1 + skip = (page - 1) * page_size + items = query.offset(skip).limit(page_size).all() + + return { + "items": [schemas.IssueResponse.model_validate(i) for i in items], + "total": total, + "page": page, + "page_size": page_size, + "total_pages": total_pages, + } diff --git a/app/schemas/schemas.py b/app/schemas/schemas.py index 88bbf2f..77e2619 100644 --- a/app/schemas/schemas.py +++ b/app/schemas/schemas.py @@ -198,3 +198,15 @@ class MilestoneResponse(MilestoneBase): class Config: from_attributes = True + + +# Paginated response +from typing import Generic, TypeVar +T = TypeVar("T") + +class PaginatedResponse(BaseModel, Generic[T]): + items: List[T] + total: int + page: int + page_size: int + total_pages: int