Nobody has started this yet — be first.
Business impact
Product wants a simple stats summary for a dashboard/onboarding email ("you're 40% through your watchlist!"). Right now the only way to build that is for every client to page through the entire list and compute it themselves -- wasteful for a user with a large watchlist, and it means every client re-implements the same aggregation logic instead of the server doing it once, correctly.
Problem
There is no way to get a summary of a user's watchlist. A client that wants to show "you've watched 42 of 108 movies, averaging a 7.3 rating" has to fetch every single page of GET /api/movies and compute it client-side.
Current behavior
GET /api/movies/stats doesn't exist -- it returns a plain 404 -- and a user with zero movies has no way to get a 200 with all-zero stats either, since the route isn't there at all.
Expected behavior
GET /api/movies/stats, guarded by @jwt_required(), returns a JSON object scoped to the current user only: {"total": 3, "watched": 2, "average_rating": 7.0}. total is a count of all of the current user's movie entries; watched is a count of the current user's entries with watched=true; average_rating is the mean of rating across the current user's entries that have a rating set (unrated entries must not be treated as 0, and must not be included in the denominator), and null if the user has no rated entries at all (including a user with zero movies).
Steps to reproduce
cd flask/movie_watchlist .venv/bin/python -m pytest practicetickets/test_ticket06_stats_endpoint.py -v
Why this matters
This is the same load-bearing rule the rest of this project exists to enforce, applied to a new kind of endpoint: an aggregate query, not a single-resource lookup. movies/service.py's get_owned_movie_or_404() filters by user_id at the query level for a single row -- it's easy to reach for MovieEntry.query.filter_by(user_id=...) there and get it right, because the existing helper does it for you. A new aggregate endpoint has no existing helper to lean on, and it's correspondingly easy to write a query that aggregates over all users' movies by forgetting the user_id filter -- which would silently leak one user's rating/watched counts into another user's stats. Getting this ticket right means applying the same ownership discipline the rest of the codebase already has, to a query shape (COUNT/AVG) that doesn't have a ready-made helper for it yet.
Suggested approach
Look at how list_movies() builds base_query = MovieEntry.query.filter_by(user_id=current_user_id) and how it resolves current_user_id from the JWT. Your new endpoint needs the same starting filter, then an aggregation over it instead of a .paginate(...)/.dump(...). Think about what SQL AVG() already does with NULL values (unrated entries) versus what a naive sum(ratings) / len(ratings) in Python would do with an empty list.
Acceptance criteria
Verification
cd flask/movie_watchlist && .venv/bin/python -m pytest practicetickets/test_ticket06_stats_endpoint.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
$git checkout -b feat/stats-endpointFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/stats-endpointSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inRun the existing test suite (uses an in-memory SQLite DB, no setup needed):
.venv/bin/python -m pytest -v # 33 passed, 0 failed on a clean checkout
cp .env.example .env
# edit .env: set SECRET_KEY, JWT_SECRET_KEY, POSTGRES_PASSWORD to real values
docker compose up --build
This builds the web image, starts Postgres (db), waits for its healthcheck, then runs flask db upgrade and starts gunicorn -- all with one command, no manual migration step. The API is then available at http://localhost:5000.
Work the tickets in practicetickets/ (TICKET_01 through TICKET_07); each names one pytest test file in the same directory. This directory is outside pytest.ini's testpaths = tests, so a bare pytest run from the project root never picks these up -- they only run when pointed at directly:
cd flask/movie_watchlist
.venv/bin/python -m pytest practicetickets/test_ticket01_movie_list_ordering.py -v # a single ticket
./practicetickets/run_tickets.sh # all 7, clean pass/fail summary
run_tickets.sh also unsets TEST_DATABASE_URL/DATABASE_URL for its own run, so a leftover Postgres URL from a different project in your shell doesn't get picked up instead of the in-memory SQLite DB these tests are written against.
Level 2
Implement a feature
Extend the system within its own patterns.