feat: add task list pagination
ci / docker (push) Successful in 3m40s

This commit is contained in:
2026-09-05 16:20:13 +08:00
parent 3bf8c7c3d9
commit 9e378b1fbf
5 changed files with 126 additions and 17 deletions
+26 -4
View File
@@ -466,7 +466,11 @@ async def list_tasks(
q: str | None = None,
list_id: UUID | None = None,
completed: bool | None = None,
due_from: datetime | None = None,
due_to: datetime | None = None,
cursor: str | None = None,
page: int | None = Query(default=None, ge=1),
page_size: int | None = Query(default=None, ge=1, le=100),
limit: int = Query(default=50, ge=1, le=100),
user: User = Depends(current_user),
db: AsyncSession = Depends(get_db),
@@ -479,6 +483,10 @@ async def list_tasks(
query = query.where(Task.list_id == list_id)
if completed is not None:
query = query.where(Task.completed.is_(completed))
if due_from is not None:
query = query.where(Task.due_at >= due_from)
if due_to is not None:
query = query.where(Task.due_at < due_to)
if q:
pattern = f"%{q}%"
tag_match = exists(
@@ -496,16 +504,22 @@ async def list_tasks(
query = query.where(
or_(Task.title.ilike(pattern), Task.description.ilike(pattern), tag_match, list_match)
)
total = await db.scalar(select(func.count()).select_from(query.order_by(None).subquery())) or 0
ordering = (Task.created_at, Task.id)
if page is not None:
size = page_size or limit
items = list((await db.scalars(query.order_by(*ordering).offset((page - 1) * size).limit(size))).all())
return TaskPage(items=items, total=total, page=page, page_size=size)
if cursor:
created_at, task_id = _decode_cursor(cursor)
query = query.where(
or_(Task.created_at > created_at, (Task.created_at == created_at) & (Task.id > task_id))
)
rows = list((await db.scalars(query.order_by(Task.created_at, Task.id).limit(limit + 1))).all())
rows = list((await db.scalars(query.order_by(*ordering).limit(limit + 1))).all())
has_more = len(rows) > limit
items = rows[:limit]
next_cursor = _encode_cursor(items[-1].created_at, items[-1].id) if has_more else None
return TaskPage(items=items, next_cursor=next_cursor)
return TaskPage(items=items, next_cursor=next_cursor, total=total, page=1, page_size=limit)
async def _task_detail(db: AsyncSession, task: Task) -> TaskDetailOut:
@@ -629,6 +643,8 @@ async def delete_task(
@app.get("/api/v1/trash", response_model=TaskPage)
async def list_trash(
cursor: str | None = None,
page: int | None = Query(default=None, ge=1),
page_size: int | None = Query(default=None, ge=1, le=100),
limit: int = Query(default=50, ge=1, le=100),
user: User = Depends(current_user),
db: AsyncSession = Depends(get_db),
@@ -636,16 +652,22 @@ async def list_trash(
query = select(Task).where(
Task.user_id == user.id, Task.deleted_at.is_not(None), Task.parent_id.is_(None)
)
total = await db.scalar(select(func.count()).select_from(query.order_by(None).subquery())) or 0
ordering = (Task.created_at, Task.id)
if page is not None:
size = page_size or limit
items = list((await db.scalars(query.order_by(*ordering).offset((page - 1) * size).limit(size))).all())
return TaskPage(items=items, total=total, page=page, page_size=size)
if cursor:
created_at, task_id = _decode_cursor(cursor)
query = query.where(
or_(Task.created_at > created_at, (Task.created_at == created_at) & (Task.id > task_id))
)
rows = list((await db.scalars(query.order_by(Task.created_at, Task.id).limit(limit + 1))).all())
rows = list((await db.scalars(query.order_by(*ordering).limit(limit + 1))).all())
has_more = len(rows) > limit
items = rows[:limit]
next_cursor = _encode_cursor(items[-1].created_at, items[-1].id) if has_more else None
return TaskPage(items=items, next_cursor=next_cursor)
return TaskPage(items=items, next_cursor=next_cursor, total=total, page=1, page_size=limit)
@app.post("/api/v1/tasks/{task_id}/restore", response_model=TaskDetailOut)