Nobody has started this yet — be first.
Business impact
The stretch-goal duplicate-URL feature is the whole reason the DB has a unique constraint on url -- right now, saving a URL that already exists returns a raw 500 Internal Server Error (the generic "failed to create bookmark" body) instead of the clean 409 Conflict with the existing record that the API is documented to return. Every duplicate save looks like a server crash to the client instead of an expected, recoverable case.
Problem
isUniqueViolation checks the Postgres error code against the wrong SQLSTATE -- it's checking for foreign_key_violation (23503) instead of unique_violation (23505), so it never actually recognizes a duplicate-URL insert failure for what it is; that error falls through to the generic 500 path.
Current behavior
POST /bookmarks a second time with the same url returns 500, even though the database constraint correctly rejected the insert as a duplicate.
Expected behavior
A duplicate URL insert is recognized and handled: DB error code 23505 → 409 with the existing bookmark in the response body (Create's existing logic for this path is already correct -- it just never gets to run).
Steps to reproduce
curl -s -X POST http://localhost:8081/bookmarks -H 'Content-Type: application/json'
-d '{"url":"https://go.dev","title":"The Go Programming Language"}'
curl -s -X POST http://localhost:8081/bookmarks -H 'Content-Type: application/json'
-d '{"url":"https://go.dev","title":"Duplicate attempt"}'
Why this matters
23503 and 23505 are both common Postgres constraint-violation codes and easy to confuse when copying from memory or another snippet -- this is a realistic "wrong constant" mistake that silently compiles (both are just string literals) and only shows up when the specific constraint it's supposed to detect actually fires.
Suggested approach
Look at the SQLSTATE code isUniqueViolation compares against, and check it against what a Postgres unique_violation actually is (the bookmarks migration's own comment above the unique index explains why this constraint exists in the first place).
Acceptance criteria
Verification
go test ./practicetickets/... -run TestTicket04 -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/duplicate-url-returns-500Fix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/duplicate-url-returns-500Submit 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 inmigrations/0001_init.sqlcurl http://localhost:8081/healthz
# {"status":"ok"}
To run without Docker, point the DB_* env vars from .env.example at a reachable Postgres and run go run ./cmd/server -- it applies the same migrations on startup.
The project's own test suite (go test ./...) is an integration suite against a real Postgres; it looks for one via BM_TEST_DB_HOST/BM_TEST_DB_PORT/BM_TEST_DB_USER/BM_TEST_DB_PASSWORD/BM_TEST_DB_NAME (defaults point at localhost:15433, database bookmarks_test) and skips cleanly rather than failing if that database is unreachable:
docker run -d --name bm_test_pg -e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=bookmarks_test -p 15433:5432 postgres:16-alpine
Work the tickets in PRACTICE_TICKETS.md (TICKET-01 through TICKET-10, all in one file, ordered easy -> hard); each names one Go test under practicetickets/:
go test ./practicetickets/... -run TestTicket01 -v # a single ticket
./practice_tickets_run.sh # all 10, clean pass/fail summary table
Level 1
Fix a bug
Read existing behaviour, correct it.