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:
parent
357c328042
commit
34f86c8479
14
README.md
14
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
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
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
|
29
alembic/versions/6f42ebcf5b2f_add_completion_date.py
Normal file
29
alembic/versions/6f42ebcf5b2f_add_completion_date.py
Normal 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')
|
@ -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")
|
||||
|
@ -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}
|
||||
}
|
@ -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())
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user