From 34f86c8479e3bb8f509fdae8a6ad3e678e11b155 Mon Sep 17 00:00:00 2001 From: Automated Action Date: Tue, 13 May 2025 04:07:40 +0000 Subject: [PATCH] Add task completion tracking with timestamps - Added completion_date field to Todo model - Modified CRUD to auto-set completion_date when task is marked complete - Added completion date stats (last 24h, last week metrics) - Created Alembic migration for the new field - Updated API documentation and README generated with BackendIM... (backend.im) --- README.md | 14 +++++++-- .../6f42ebcf5b2f_add_completion_date.py | 29 +++++++++++++++++++ app/api/routes/todos.py | 14 +++++++-- app/crud/todo.py | 28 +++++++++++++++++- app/models/todo.py | 1 + app/schemas/todo.py | 2 ++ 6 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 alembic/versions/6f42ebcf5b2f_add_completion_date.py diff --git a/README.md b/README.md index 948db20..7d04d80 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,14 @@ A simple Todo API application built with FastAPI and SQLite. This RESTful API pr - Todo categorization with categories - Priority levels (low, medium, high) - Due dates for task deadlines +- Task completion tracking with completion timestamps - Advanced filtering capabilities: - Filter by category - Filter by priority - Filter by completion status - Filter by due date range - Customizable sorting options -- Todo statistics and analytics +- Todo statistics and analytics including completion metrics - SQLAlchemy ORM with SQLite database - Alembic for database migrations - FastAPI's automatic OpenAPI documentation @@ -110,11 +111,14 @@ The GET /api/v1/todos/ endpoint supports the following query parameters: "category": "work", "priority": "high", "due_date": "2025-05-20T12:00:00", + "completion_date": null, "created_at": "2025-05-13T12:00:00", "updated_at": null } ``` +When a todo is marked as completed, the `completion_date` is automatically set to the current time. If a todo is marked as not completed, the `completion_date` is cleared. + ## Statistics The /api/v1/todos/stats endpoint returns the following information: @@ -125,6 +129,8 @@ The /api/v1/todos/stats endpoint returns the following information: "completed": 3, "incomplete": 7, "overdue": 2, + "completed_last_24h": 1, + "completed_last_week": 3, "by_category": { "work": 4, "personal": 3, @@ -137,4 +143,8 @@ The /api/v1/todos/stats endpoint returns the following information: "low": 2 } } -``` \ No newline at end of file +``` + +The statistics include: +- `completed_last_24h`: Number of tasks completed within the last 24 hours +- `completed_last_week`: Number of tasks completed within the last 7 days \ No newline at end of file diff --git a/alembic/versions/6f42ebcf5b2f_add_completion_date.py b/alembic/versions/6f42ebcf5b2f_add_completion_date.py new file mode 100644 index 0000000..c017774 --- /dev/null +++ b/alembic/versions/6f42ebcf5b2f_add_completion_date.py @@ -0,0 +1,29 @@ +"""add completion date + +Revision ID: 6f42ebcf5b2f +Revises: 5f42ebcf5b2e +Create Date: 2025-05-13 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '6f42ebcf5b2f' +down_revision = '5f42ebcf5b2e' +branch_labels = None +depends_on = None + + +def upgrade(): + # Add completion_date column + op.add_column('todos', sa.Column('completion_date', sa.DateTime(timezone=True), nullable=True)) + + # Set completion_date for already completed todos to their updated_at value + op.execute("UPDATE todos SET completion_date = updated_at WHERE completed = TRUE") + + +def downgrade(): + # Drop column + op.drop_column('todos', 'completion_date') \ No newline at end of file diff --git a/app/api/routes/todos.py b/app/api/routes/todos.py index 380e844..9f0896c 100644 --- a/app/api/routes/todos.py +++ b/app/api/routes/todos.py @@ -56,7 +56,13 @@ def create_todo(todo: TodoCreate, db: Session = Depends(get_db)): @router.get("/stats", response_model=Dict[str, Any]) def get_todo_stats(db: Session = Depends(get_db)): - """Get statistics about todos""" + """Get statistics about todos including: + - Total, completed, and incomplete counts + - Number of overdue tasks + - Number of tasks completed in the last 24 hours + - Number of tasks completed in the last 7 days + - Breakdown by category and priority + """ return crud.get_todo_stats(db=db) @@ -71,7 +77,11 @@ def read_todo(todo_id: int, db: Session = Depends(get_db)): @router.put("/{todo_id}", response_model=Todo) def update_todo(todo_id: int, todo: TodoUpdate, db: Session = Depends(get_db)): - """Update a todo with new values including category, priority and due date""" + """Update a todo with new values including category, priority, due date and completion status. + + When a todo is marked as completed, the completion_date will be automatically set to the current time + if not explicitly provided. If a todo is marked as not completed, the completion_date will be cleared. + """ db_todo = crud.update_todo(db=db, todo_id=todo_id, todo=todo) if db_todo is None: raise HTTPException(status_code=404, detail="Todo not found") diff --git a/app/crud/todo.py b/app/crud/todo.py index cb7f7dd..181a399 100644 --- a/app/crud/todo.py +++ b/app/crud/todo.py @@ -1,7 +1,7 @@ from sqlalchemy.orm import Session from sqlalchemy import asc, desc from typing import List, Optional, Dict, Any -from datetime import datetime +from datetime import datetime, timedelta from app.models.todo import Todo, PriorityEnum from app.schemas.todo import TodoCreate, TodoUpdate @@ -75,6 +75,16 @@ def update_todo(db: Session, todo_id: int, todo: TodoUpdate) -> Optional[Todo]: return None update_data = todo.model_dump(exclude_unset=True) + + # If completed status is being set to True and there's no completion_date provided, + # set completion_date to current datetime + if update_data.get('completed') is True and 'completion_date' not in update_data: + update_data['completion_date'] = datetime.now() + + # If completed status is being set to False, clear the completion_date + if update_data.get('completed') is False and db_todo.completed: + update_data['completion_date'] = None + for field, value in update_data.items(): setattr(db_todo, field, value) @@ -122,11 +132,27 @@ def get_todo_stats(db: Session) -> Dict[str, Any]: Todo.completed == False ).count() + # Get completed todos in last 24 hours + yesterday = now - datetime.timedelta(days=1) + completed_last_24h = db.query(Todo).filter( + Todo.completion_date > yesterday, + Todo.completed == True + ).count() + + # Get completed todos in last 7 days + last_week = now - datetime.timedelta(days=7) + completed_last_week = db.query(Todo).filter( + Todo.completion_date > last_week, + Todo.completed == True + ).count() + return { "total": total, "completed": completed, "incomplete": incomplete, "overdue": overdue, + "completed_last_24h": completed_last_24h, + "completed_last_week": completed_last_week, "by_category": {cat or "uncategorized": count for cat, count in categories}, "by_priority": {str(pri.name).lower() if pri else "none": count for pri, count in priorities} } \ No newline at end of file diff --git a/app/models/todo.py b/app/models/todo.py index 23b67d5..94abc85 100644 --- a/app/models/todo.py +++ b/app/models/todo.py @@ -19,5 +19,6 @@ class Todo(Base): category = Column(String, nullable=True, index=True) priority = Column(Enum(PriorityEnum), default=PriorityEnum.MEDIUM, nullable=True) due_date = Column(DateTime(timezone=True), nullable=True) + completion_date = Column(DateTime(timezone=True), nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) \ No newline at end of file diff --git a/app/schemas/todo.py b/app/schemas/todo.py index b0b182b..1b91b0e 100644 --- a/app/schemas/todo.py +++ b/app/schemas/todo.py @@ -26,9 +26,11 @@ class TodoUpdate(BaseModel): category: Optional[str] = None priority: Optional[PriorityEnum] = None due_date: Optional[datetime] = None + completion_date: Optional[datetime] = None class TodoInDB(TodoBase): id: int + completion_date: Optional[datetime] = None created_at: datetime updated_at: Optional[datetime] = None