Files
JARVIS/backend/app/routers/todo.py
DESKTOP-72TV0V4\caoxiaozhu 3ee825aa90 Add MinerU document ingestion support
Normalize uploaded documents into structured markdown, add clearer parser
errors for missing dependencies, and cover the ingestion flow with
backend tests. This also replaces deprecated UTC timestamp helpers in
the touched backend paths so the knowledge pipeline stays warning-free.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 13:42:16 +08:00

155 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from datetime import date
from app.database import get_db
from app.models.todo import DailyTodo, TodoSource
from app.models.user import User
from app.routers.auth import get_current_user
from app.schemas.todo import (
TodoCreate, TodoUpdate, TodoOut, TodoListOut, TodoSummaryOut
)
from app.services.todo_service import generate_daily_todos
router = APIRouter(prefix="/api/todos", tags=["待办"])
@router.get("", response_model=TodoListOut)
async def list_todos(
date_str: str = Query(default=None), # YYYY-MM-DD默认当天
page: int = Query(default=1, ge=1),
page_size: int = Query(default=50, ge=1, le=100),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
target_date = date_str or date.today().isoformat()
offset = (page - 1) * page_size
# 查询总数
count_q = select(func.count()).select_from(DailyTodo).where(
DailyTodo.user_id == current_user.id,
DailyTodo.todo_date == target_date,
)
total = (await db.execute(count_q)).scalar()
# 查询列表
q = select(DailyTodo).where(
DailyTodo.user_id == current_user.id,
DailyTodo.todo_date == target_date,
).order_by(DailyTodo.created_at.desc()).offset(offset).limit(page_size)
items = (await db.execute(q)).scalars().all()
return TodoListOut(items=items, total=total, page=page, page_size=page_size)
@router.post("", response_model=TodoOut, status_code=201)
async def create_todo(
data: TodoCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
todo = DailyTodo(
user_id=current_user.id,
title=data.title,
source=TodoSource.MANUAL,
todo_date=date.today().isoformat(),
)
db.add(todo)
await db.commit()
await db.refresh(todo)
return todo
@router.patch("/{todo_id}", response_model=TodoOut)
async def update_todo(
todo_id: str,
data: TodoUpdate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(
select(DailyTodo).where(DailyTodo.id == todo_id, DailyTodo.user_id == current_user.id)
)
todo = result.scalar_one_or_none()
if not todo:
raise HTTPException(status_code=404, detail="待办不存在")
# 历史日期不允许修改
if todo.todo_date != date.today().isoformat():
raise HTTPException(status_code=403, detail="历史待办不可修改")
if data.title is not None:
todo.title = data.title
if data.is_completed is not None:
from datetime import UTC, datetime
todo.is_completed = data.is_completed
todo.completed_at = datetime.now(UTC) if data.is_completed else None
await db.commit()
await db.refresh(todo)
return todo
@router.delete("/{todo_id}", status_code=204)
async def delete_todo(
todo_id: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(
select(DailyTodo).where(DailyTodo.id == todo_id, DailyTodo.user_id == current_user.id)
)
todo = result.scalar_one_or_none()
if not todo:
raise HTTPException(status_code=404, detail="待办不存在")
if todo.todo_date != date.today().isoformat():
raise HTTPException(status_code=403, detail="历史待办不可删除")
await db.delete(todo)
await db.commit()
@router.post("/ai-generate", response_model=TodoListOut)
async def ai_generate_todos(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
target_date = date.today().isoformat()
# 幂等检查是否已有AI生成记录
check_q = select(func.count()).select_from(DailyTodo).where(
DailyTodo.user_id == current_user.id,
DailyTodo.todo_date == target_date,
DailyTodo.source.in_([TodoSource.AI_KANBAN, TodoSource.AI_CHAT]),
)
count = (await db.execute(check_q)).scalar()
if count > 0:
# 已生成,返回现有记录
q = select(DailyTodo).where(
DailyTodo.user_id == current_user.id,
DailyTodo.todo_date == target_date,
).order_by(DailyTodo.created_at.desc())
items = (await db.execute(q)).scalars().all()
return TodoListOut(items=items, total=len(items), page=1, page_size=50)
# 执行AI生成
todos = await generate_daily_todos(current_user.id, db)
return TodoListOut(items=todos, total=len(todos), page=1, page_size=50)
@router.get("/summary", response_model=TodoSummaryOut)
async def get_summary(
date_str: str = Query(default=None),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
target_date = date_str or date.today().isoformat()
q = select(DailyTodo).where(
DailyTodo.user_id == current_user.id,
DailyTodo.todo_date == target_date,
)
todos = (await db.execute(q)).scalars().all()
completed = sum(1 for t in todos if t.is_completed)
return TodoSummaryOut(date=target_date, total=len(todos), completed=completed, pending=len(todos) - completed)