155 lines
5.2 KiB
Python
155 lines
5.2 KiB
Python
|
|
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 datetime
|
|||
|
|
todo.is_completed = data.is_completed
|
|||
|
|
todo.completed_at = datetime.utcnow() 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)
|