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)
This commit is contained in:
Automated Action 2025-05-13 04:07:40 +00:00
parent 357c328042
commit 34f86c8479
6 changed files with 83 additions and 5 deletions

View File

@ -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
}
}
```
```
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

View File

@ -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')

View File

@ -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")

View File

@ -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}
}

View File

@ -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())

View File

@ -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