from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from typing import Optional, List from datetime import datetime, timezone from app.database import get_db from app.models.todo import Todo from app.schemas.todo import TodoCreate, TodoUpdate, TodoResponse from app.routers.auth import get_current_session from app.models.settings import Settings router = APIRouter() @router.get("/", response_model=List[TodoResponse]) async def get_todos( completed: Optional[bool] = Query(None), priority: Optional[str] = Query(None), category: Optional[str] = Query(None), search: Optional[str] = Query(None), db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Get all todos with optional filters.""" query = select(Todo) if completed is not None: query = query.where(Todo.completed == completed) if priority: query = query.where(Todo.priority == priority) if category: query = query.where(Todo.category == category) if search: query = query.where(Todo.title.ilike(f"%{search}%")) query = query.order_by(Todo.created_at.desc()) result = await db.execute(query) todos = result.scalars().all() return todos @router.post("/", response_model=TodoResponse, status_code=201) async def create_todo( todo: TodoCreate, db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Create a new todo.""" new_todo = Todo(**todo.model_dump()) db.add(new_todo) await db.commit() await db.refresh(new_todo) return new_todo @router.get("/{todo_id}", response_model=TodoResponse) async def get_todo( todo_id: int, db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Get a specific todo by ID.""" result = await db.execute(select(Todo).where(Todo.id == todo_id)) todo = result.scalar_one_or_none() if not todo: raise HTTPException(status_code=404, detail="Todo not found") return todo @router.put("/{todo_id}", response_model=TodoResponse) async def update_todo( todo_id: int, todo_update: TodoUpdate, db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Update a todo.""" result = await db.execute(select(Todo).where(Todo.id == todo_id)) todo = result.scalar_one_or_none() if not todo: raise HTTPException(status_code=404, detail="Todo not found") update_data = todo_update.model_dump(exclude_unset=True) # Handle completion timestamp if "completed" in update_data: if update_data["completed"] and not todo.completed: update_data["completed_at"] = datetime.now(timezone.utc) elif not update_data["completed"]: update_data["completed_at"] = None for key, value in update_data.items(): setattr(todo, key, value) await db.commit() await db.refresh(todo) return todo @router.delete("/{todo_id}", status_code=204) async def delete_todo( todo_id: int, db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Delete a todo.""" result = await db.execute(select(Todo).where(Todo.id == todo_id)) todo = result.scalar_one_or_none() if not todo: raise HTTPException(status_code=404, detail="Todo not found") await db.delete(todo) await db.commit() return None @router.patch("/{todo_id}/toggle", response_model=TodoResponse) async def toggle_todo( todo_id: int, db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Toggle todo completion status.""" result = await db.execute(select(Todo).where(Todo.id == todo_id)) todo = result.scalar_one_or_none() if not todo: raise HTTPException(status_code=404, detail="Todo not found") todo.completed = not todo.completed todo.completed_at = datetime.now(timezone.utc) if todo.completed else None await db.commit() await db.refresh(todo) return todo