Nobody has started this yet — be first.
Business impact
GET /expenses always returns every one of the caller's expenses. A user who wants to review just their "Entertainment" spending, or a client building a per-category expense view, has to fetch everything and filter client-side -- wasted transfer and work that grows without bound as a user's expense history grows.
Problem
Repo.List(ctx, userID) takes only a user id and always returns every expense owned by that user; there's no way to narrow by category. Handler.List never parses a category_id query parameter at all.
Current behavior
GET /expenses?category_id=N returns the same full, unfiltered list as GET /expenses with no query param at all -- the filter is a complete no-op, and a non-numeric category_id is silently ignored rather than rejected.
Expected behavior
GET /expenses?category_id=N returns only the caller's expenses in that category. Omitting the query param keeps today's "all of the caller's expenses" behavior exactly as-is.
Steps to reproduce
As an authenticated user with expenses in more than one category, GET /expenses?category_id=1.
Why this matters
This is the exact same lesson as filtering elsewhere in this codebase (and in the bookmark_manager project) -- add the new filter as an optional, additional AND condition in the WHERE clause, parameterized, without breaking the mandatory user_id = $1 ownership scoping that's the actual point of every query in this file. The category filter must be applied in addition to, never instead of, the existing user-scoping -- this is the same file where Ticket 09 lives, so pay close attention to keeping that scoping intact.
Suggested approach
Extend Repo.List to accept an optional category id (e.g. categoryID *int64, nil meaning "no filter") and add a conditional AND e.category_id = $N clause, mirroring how bookmark_manager's Store.List conditionally adds clauses. Parse ?category_id= in Handler.List and pass it through.
Acceptance criteria
Verification
go test ./practicetickets/... -run TestTicket07 -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/expense-category-filterFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/expense-category-filterSubmit 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 indocker-compose.ymlmigrations/0001_init.sqldocker-entrypoint-initdb.dexpense_trackerdbappcurl http://localhost:8082/healthz
To run locally against your own Postgres instead: cp .env.example .env (edit if your Postgres isn't on localhost:5434), then make migrate (applies migrations/*.sql via psql) and make run (go run ./cmd/server).
Work the tickets in PRACTICE_TICKETS.md (TICKET-01 through TICKET-10); each names one Go test in practicetickets/:
go test ./practicetickets/... -run TestTicket01 -v # a single ticket
./practice_tickets_run.sh # all 10, clean pass/fail summary
This project's own integration suite (cmd/server/*_test.go) needs a real, reachable Postgres -- point TEST_DATABASE_URL at any empty/disposable database (migrations are applied automatically, and every test truncates+reseeds before it runs) and run make test. The practicetickets/ suite defaults to postgres://postgres:postgres@localhost:5434/expense_tracker_test if TEST_DATABASE_URL is unset.
Level 2
Implement a feature
Extend the system within its own patterns.