Nobody has started this yet — be first.
Business impact
This is the worst possible category of bug for a financial-records product: silent, irreversible data loss across account boundaries. Any authenticated user who deletes any one of their own expenses will, in the same request, also delete every other expense they own -- and far worse, if they know or guess another user's expense id (sequential integer ids, trivially guessable), a single DELETE call against that id deletes that stranger's expense too, with no ownership check actually stopping it. A user reports "all my expenses vanished after I deleted one" -- that's this bug, and there is no undo.
Problem
Repo.Delete's SQL is DELETE FROM expenses WHERE id = $1 OR user_id = $2 -- the AND that's supposed to scope deletion to "this specific row, and only if it's mine" has become an OR, so the WHERE clause matches (a) the row with that exact id, regardless of owner, unioned with (b) every single row owned by the caller, regardless of which id was requested.
Current behavior
Deleting one of your own expenses deletes every expense you own, and deleting an expense id that belongs to a different user succeeds anyway, wiping out that stranger's row too.
Expected behavior
WHERE id = $1 AND user_id = $2 -- deletes at most one row, and only if it is both the requested id and owned by the caller.
Steps to reproduce
As user A, create two expenses (ids e1, e2). As user A, DELETE /expenses/{e1} (intending to delete just e1). Check: does e2 still exist? It shouldn't have been touched -- but it's gone too. Separately: as user B (with no expenses of their own), DELETE /expenses/{e1's id} using an id you know belongs to user A.
Why this matters
This is the exact "operator flip" class of bug this whole project exists to catch -- the SQL WHERE clause is the single enforcement point for ownership (by this project's own design), so getting the boolean operator between "which row" and "who owns it" wrong doesn't just fail to protect the boundary, it makes a single-row delete potentially multi-row and cross-account, silently, with no error of any kind (a matched-many-rows DELETE isn't itself an error in Postgres).
Suggested approach
Compare Repo.Delete's WHERE clause against Repo.Get and Repo.Update in the very same file -- both correctly use the identical id = $.. AND user_id = $.. shape a few lines above and below this function.
Acceptance criteria
Verification
go test ./practicetickets/... -run TestTicket09 -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/expense-delete-or-vs-andFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/expense-delete-or-vs-andSubmit 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.