import sqlite3 from typing import List from fastapi import APIRouter, HTTPException, status from app.database import Database from app.schemas import Todo, TodoCreate, TodoUpdate router = APIRouter(prefix="/todos", tags=["todos"]) def row_to_todo(row: sqlite3.Row) -> Todo: return Todo(id=row["id"], title=row["title"], done=bool(row["done"])) @router.get("", response_model=List[Todo]) def list_todos(db: Database) -> List[Todo]: rows = db.execute("SELECT id, title, done FROM todos ORDER BY id").fetchall() return [row_to_todo(row) for row in rows] @router.post("", response_model=Todo, status_code=status.HTTP_201_CREATED) def create_todo(payload: TodoCreate, db: Database) -> Todo: cursor = db.execute( "INSERT INTO todos (title, done) VALUES (?, ?)", (payload.title, 0), ) db.commit() return Todo(id=cursor.lastrowid, title=payload.title, done=False) @router.get("/{todo_id}", response_model=Todo) def get_todo(todo_id: int, db: Database) -> Todo: row = db.execute( "SELECT id, title, done FROM todos WHERE id = ?", (todo_id,), ).fetchone() if row is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Todo not found") return row_to_todo(row) @router.patch("/{todo_id}", response_model=Todo) def update_todo(todo_id: int, payload: TodoUpdate, db: Database) -> Todo: todo = get_todo(todo_id, db) updates = payload.model_dump(exclude_unset=True) if not updates: return todo title = updates.get("title", todo.title) done = updates.get("done", todo.done) db.execute( "UPDATE todos SET title = ?, done = ? WHERE id = ?", (title, int(done), todo_id), ) db.commit() return get_todo(todo_id, db) @router.delete("/{todo_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_todo(todo_id: int, db: Database) -> None: cursor = db.execute("DELETE FROM todos WHERE id = ?", (todo_id,)) db.commit() if cursor.rowcount == 0: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Todo not found")