Files
SnapAndAnaly/backend/app/api/todos.py
T
congsh 5c028d7952 Initial commit: snapAna 截图智能整理工具
包含 FastAPI 后端、React 前端、队列/OCR/标签/待办等完整功能。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 15:45:50 +08:00

107 lines
3.0 KiB
Python

"""待办清单接口。"""
from __future__ import annotations
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import and_, func, or_, select
from sqlalchemy.orm import Session
from app.api.deps import db_session
from app.models.todo import Todo, TodoStatus
from app.schemas.common import TodoUpdate
from app.schemas.screenshot import TodoBrief, TodoListResp
router = APIRouter(prefix="/api/todos", tags=["todos"])
def _todo_filters(
status: Optional[str],
kind: Optional[str],
q: Optional[str],
) -> list:
"""构建待办筛选条件。"""
filters = []
if status:
filters.append(Todo.status == status)
if kind:
filters.append(Todo.kind == kind)
if q:
like = f"%{q.strip()}%"
filters.append(or_(Todo.title.ilike(like), Todo.note.ilike(like)))
return filters
@router.get("", response_model=TodoListResp)
def list_todos(
session: Session = Depends(db_session),
status: Optional[str] = Query(None),
kind: Optional[str] = Query(None),
q: Optional[str] = Query(None, description="标题/备注关键词"),
page: int = Query(1, ge=1),
size: int = Query(50, ge=1, le=200),
) -> TodoListResp:
"""按状态/类型/关键词分页查询。"""
filters = _todo_filters(status, kind, q)
base = select(Todo)
if filters:
base = base.where(and_(*filters))
total = session.scalar(select(func.count()).select_from(base.subquery())) or 0
rows = session.scalars(
base.order_by(Todo.created_at.desc()).offset((page - 1) * size).limit(size)
).all()
return TodoListResp(
items=[TodoBrief.model_validate(r) for r in rows],
total=int(total),
page=page,
size=size,
)
@router.get("/summary")
def summary(session: Session = Depends(db_session)) -> dict:
"""各状态待办数量。"""
return {
st.value: session.scalar(select(func.count(Todo.id)).where(Todo.status == st.value)) or 0
for st in TodoStatus
}
@router.patch("/{todo_id}", response_model=TodoBrief)
def update_todo(
todo_id: int,
payload: TodoUpdate,
session: Session = Depends(db_session),
) -> TodoBrief:
"""更新状态/标题/备注。"""
todo = session.get(Todo, todo_id)
if todo is None:
raise HTTPException(404, "Todo not found")
if payload.status is not None:
todo.status = payload.status
if payload.status == TodoStatus.DONE.value:
todo.completed_at = datetime.utcnow()
if payload.title is not None:
todo.title = payload.title
if payload.note is not None:
todo.note = payload.note
session.commit()
session.refresh(todo)
return TodoBrief.model_validate(todo)
@router.delete("/{todo_id}")
def delete_todo(
todo_id: int,
session: Session = Depends(db_session),
) -> dict:
todo = session.get(Todo, todo_id)
if todo is None:
raise HTTPException(404, "Todo not found")
session.delete(todo)
session.commit()
return {"ok": True}