Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 12 additions & 47 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,16 @@
from __future__ import annotations
from fastapi import FastAPI
from app import service, schemas

from fastapi import FastAPI, HTTPException, Query
app = FastAPI()

from .schemas import Task, TaskCreate, TaskStatus
from .service import complete_task, create_task, get_task, list_tasks
@app.get("/tasks")
def read_tasks(status: str | None = None, q: str | None = None):
return service.get_tasks(status=status, q=q)

@app.get("/tasks/{task_id}")
def read_task(task_id: int):
return service.get_task(task_id)

app = FastAPI(
title="Codex Task Tracker API",
description="A small task manager API used for Codex App lab exercises.",
version="0.1.0",
)


@app.get("/health")
def health_check() -> dict[str, str]:
return {"status": "ok"}


@app.get("/tasks", response_model=list[Task])
def read_tasks(
status: TaskStatus | None = Query(default=None),
q: str | None = Query(
default=None,
min_length=1,
description="Case-insensitive text search across title and description.",
),
) -> list[Task]:
return list_tasks(status=status, q=q)


@app.get("/tasks/{task_id}", response_model=Task)
def read_task(task_id: int) -> Task:
task = get_task(task_id)
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
return task


@app.post("/tasks", response_model=Task, status_code=201)
def add_task(payload: TaskCreate) -> Task:
return create_task(payload.model_dump())


@app.post("/tasks/{task_id}/complete", response_model=Task)
def complete_existing_task(task_id: int) -> Task:
task = complete_task(task_id)
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
return task
@app.post("/tasks/{task_id}/complete")
def complete_task(task_id: int):
return service.complete_task(task_id)
81 changes: 26 additions & 55 deletions app/service.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,35 @@
from __future__ import annotations

from datetime import datetime, timezone
from typing import Any

from .store import load_tasks, next_task_id, save_tasks


def list_tasks(status: str | None = None, q: str | None = None) -> list[dict[str, Any]]:
"""Return task records, optionally filtered by status and search text."""
tasks = load_tasks()
filtered: list[dict[str, Any]] = []

for task in tasks:
# Instructor note: intentional bug for the lab.
# This uses the literal string "status" instead of the query parameter value.
if status and task["status"] != "status":
continue

# Instructor note: partial feature for the lab.
# The route already accepts `q`, but search is not implemented yet.
filtered.append(task)
from app import store

return filtered
def get_tasks(status: str | None = None, q: str | None = None):
tasks = store.load_tasks()

if status:
tasks = [t for t in tasks if t.get("status") == status]

def get_task(task_id: int) -> dict[str, Any] | None:
"""Find a single task by ID."""
tasks = load_tasks()
return next((task for task in tasks if task["id"] == task_id), None)
if q:
query = q.lower()
tasks = [
t for t in tasks
if query in t.get("title", "").lower()
or query in t.get("description", "").lower()
]

return tasks

def create_task(payload: dict[str, Any]) -> dict[str, Any]:
"""Create a new task and persist it."""
tasks = load_tasks()
task = {
"id": next_task_id(tasks),
"title": payload["title"],
"description": payload["description"],
"status": "open",
"priority": payload["priority"],
"created_at": datetime.now(timezone.utc).isoformat(),
"completed_at": None,
}
tasks.append(task)
save_tasks(tasks)
return task


def complete_task(task_id: int) -> dict[str, Any] | None:
"""Mark a task as completed."""
tasks = load_tasks()

def get_task(task_id: int):
tasks = store.load_tasks()
for task in tasks:
if task["id"] == task_id:
updated_task = dict(task)
updated_task["status"] = "done"
updated_task["completed_at"] = datetime.now(timezone.utc).isoformat()

# Instructor note: intentional bug for the lab.
# The updated task is returned, but the stored list is never updated or saved.
return updated_task
if task.get("id") == task_id:
return task
return None

def complete_task(task_id: int):
tasks = store.load_tasks()
for task in tasks:
if task.get("id") == task_id:
task["status"] = "done"
task["completed_at"] = datetime.now(timezone.utc).isoformat()
store.save_tasks(tasks)
return task
return None