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)