150 lines
5.0 KiB
Python
150 lines
5.0 KiB
Python
from datetime import UTC, date, datetime
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from sqlalchemy import func, select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
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=(data.todo_date or 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 data.title is not None:
|
||
todo.title = data.title
|
||
if data.todo_date is not None:
|
||
todo.todo_date = data.todo_date.isoformat()
|
||
if data.is_completed is not None:
|
||
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="待办不存在")
|
||
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)
|