Nobody has started this yet — be first.
Business impact
The seven built-in default categories (Food, Transport, Housing, Utilities, Entertainment, Health, Other) are what every new user starts with -- nobody has to create a category before they can log an expense. But now nobody can set a budget limit on any of them either: POST /budgets rejects every attempt to set a limit on a global category with "category does not exist or is not accessible," even though that same category works perfectly fine for logging expenses against. This defeats the budget feature for the overwhelming majority of users, who never get around to creating a custom category.
Problem
The visibility check in Handler.Upsert -- which is supposed to allow a budget on any category "global OR owned by the caller," exactly like every other category-visible check in this codebase -- now only accepts categories the caller privately owns. It dropped the "or it's a global category" branch, so user_id IS NULL (a global category) never satisfies it.
Current behavior
Setting a budget on any of the seven seeded global categories (e.g. id 1, "Food") fails with a 400 "category does not exist or is not accessible" error, even though that category is perfectly usable for expenses.
Expected behavior
A budget can be set on a category that is either global or owned by the caller -- the same rule expenses.Repo.CategoryVisible and categories.Repo.IsVisible already correctly implement elsewhere in this codebase.
Steps to reproduce
As any authenticated user, POST /budgets with category_id: 1 (the seeded "Food" global category) and a valid monthly_limit.
Why this matters
This is the same "global-or-owned" visibility rule implemented correctly in at least two other places in this codebase (expenses.Repo.CategoryVisible, categories.Repo.IsVisible) -- the copy of this check inline in budgets.Handler.Upsert lost the OR user_id IS NULL clause, so it silently narrowed from "visible to you" to "privately owned by you," a real, easy-to-make inconsistency between several near-duplicate authorization checks scattered across a codebase.
Suggested approach
Compare the inline visibility query in Handler.Upsert against expenses.Repo.CategoryVisible's query (same shape, same intent) a few files over -- they should be checking the exact same condition.
Acceptance criteria
Verification
go test ./practicetickets/... -run TestTicket05 -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 fix/budget-blocks-global-categoriesFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/budget-blocks-global-categoriesSubmit 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 1
Fix a bug
Read existing behaviour, correct it.