from typing import List, Optional from datetime import datetime, date from fastapi import APIRouter, Depends, HTTPException, status, Response, Query from sqlalchemy.orm import Session from sqlalchemy import desc, asc from app.db.database import get_db from app.models.todo import Todo as TodoModel from app.schemas.todo import Todo, TodoCreate, TodoUpdate router = APIRouter() @router.post("/todos", response_model=Todo, status_code=status.HTTP_201_CREATED, summary="Create a new todo") def create_todo(todo: TodoCreate, db: Session = Depends(get_db)): """ Create a new todo item. - **title**: Required title of the todo - **description**: Optional detailed description - **completed**: Whether the todo is completed (defaults to False) Returns the created todo item. """ db_todo = TodoModel(**todo.model_dump()) db.add(db_todo) db.commit() db.refresh(db_todo) return db_todo @router.get("/todos", response_model=List[Todo], summary="Get all todos") def read_todos( skip: int = 0, limit: int = 100, completed: Optional[bool] = Query(None, description="Filter by completion status"), created_after: Optional[date] = Query(None, description="Filter todos created after this date (YYYY-MM-DD)"), created_before: Optional[date] = Query(None, description="Filter todos created before this date (YYYY-MM-DD)"), sort_by: str = Query("id", description="Field to sort by (id, title, created_at, updated_at)"), sort_order: str = Query("asc", description="Sort order (asc or desc)"), db: Session = Depends(get_db) ): """ Get all todo items with optional filtering, sorting, and pagination. - **skip**: Number of items to skip (pagination) - **limit**: Maximum number of items to return (pagination) - **completed**: Optional filter by completion status (true/false) - **created_after**: Filter todos created on or after this date (format: YYYY-MM-DD) - **created_before**: Filter todos created on or before this date (format: YYYY-MM-DD) - **sort_by**: Field to sort by (options: id, title, created_at, updated_at) - **sort_order**: Sort order (options: asc, desc) Returns a list of todo items. """ query = db.query(TodoModel) # Apply filters if completed is not None: query = query.filter(TodoModel.completed == completed) if created_after: # Convert date to datetime with time at start of day (00:00:00) start_datetime = datetime.combine(created_after, datetime.min.time()) query = query.filter(TodoModel.created_at >= start_datetime) if created_before: # Convert date to datetime with time at end of day (23:59:59) end_datetime = datetime.combine(created_before, datetime.max.time()) query = query.filter(TodoModel.created_at <= end_datetime) # Apply sorting if sort_by not in ["id", "title", "created_at", "updated_at"]: sort_by = "id" # Default to id if invalid field provided if sort_order.lower() not in ["asc", "desc"]: sort_order = "asc" # Default to ascending if invalid order provided # Get the attribute to sort by sort_attr = getattr(TodoModel, sort_by) # Apply sorting direction if sort_order.lower() == "desc": query = query.order_by(desc(sort_attr)) else: query = query.order_by(asc(sort_attr)) # Apply pagination todos = query.offset(skip).limit(limit).all() return todos @router.get("/todos/{todo_id}", response_model=Todo, summary="Get a specific todo") def read_todo(todo_id: int, db: Session = Depends(get_db)): """ Get a specific todo item by ID. - **todo_id**: The ID of the todo to retrieve Returns the todo item or raises a 404 if not found. """ db_todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first() if db_todo is None: raise HTTPException(status_code=404, detail="Todo not found") return db_todo @router.put("/todos/{todo_id}", response_model=Todo, summary="Update a todo") def update_todo(todo_id: int, todo: TodoUpdate, db: Session = Depends(get_db)): """ Update a todo item. - **todo_id**: The ID of the todo to update - **todo**: The updated todo data Returns the updated todo item or raises a 404 if not found. """ db_todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first() if db_todo is None: raise HTTPException(status_code=404, detail="Todo not found") # Update fields if provided in the request todo_data = todo.model_dump(exclude_unset=True) for key, value in todo_data.items(): setattr(db_todo, key, value) db.commit() db.refresh(db_todo) return db_todo @router.delete("/todos/{todo_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, summary="Delete a todo") def delete_todo(todo_id: int, db: Session = Depends(get_db)): """ Delete a todo item. - **todo_id**: The ID of the todo to delete Returns no content on success or raises a 404 if not found. """ db_todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first() if db_todo is None: raise HTTPException(status_code=404, detail="Todo not found") db.delete(db_todo) db.commit() return Response(status_code=status.HTTP_204_NO_CONTENT)