A small, real bookmark-saving API whose entire reason for existing is teaching the habit of building tag/search SQL filters with parameterized queries -- clause list and argument slice built in lockstep -- instead of string-concatenated SQL, even in an app "nobody" is attacking.
Nobody has started this yet — be first.
Saving any bookmark silently strips all of its tags
Every bookmark a user saves loses its tags on the way in -- tags are the entire organizing mechanism of this product, so this bug makes tag-based filtering and search-by-tag completely non-functional for every user, silently. Nobody gets an error; their bookmarks just save with no tags, and they won't notice until they go looking for something and it isn't where they expected.
About this project
A standalone Go bookmark manager (net/http with Go 1.22+ pattern routing, no framework; database/sql against Postgres via lib/pq, no ORM): save a URL with a title and tags, then list/filter/search them. Tags live on the bookmark itself as a native text[] column backed by a GIN index, rather than a normalized bookmark_tags join table -- a deliberate schema trade-off the codebase documents and leans into.
The central engineering problem the project is built around is 's WHERE-clause construction: the and query-string filters are untrusted input exactly like a form field would be, so the clause text and its bound-argument slice are built up together, one filter at a time, so the Nth placeholder always lines up with the Nth argument however many filters are present -- never by pasting a value into the query string. The same lockstep pattern is what the pagination and future PATCH tickets extend to / and a dynamic clause. Two smaller but equally real problems live alongside it: an absolute-URL scheme guard (/ only, everything else and every malformed URL rejected with a clean 400, never a panic or a 500) and a database-level unique constraint on , enforced and detected via the correct Postgres SQLSTATE rather than a race-prone check-then-insert, so concurrent duplicate saves genuinely can't create two rows.
Tag + search filter combines with OR instead of AND
Combining a tag filter with a search query (e.g. "my go-tagged bookmarks about concurrency") returns a flood of irrelevant results -- anything tagged go or merely matching the query -- instead of the narrow intersection the user asked for. Filtering is the core value of a bookmark manager once someone has more than a couple dozen links; a filter that gets less precise the more criteria you add teaches users not to trust it.
Store.Listtagq$NLIMITOFFSETSEThttphttpsurlTen tickets are deliberately injected into an otherwise working, fully-tested Go codebase: some are one-character or one-line mistakes (an inverted boolean, a swapped OR/AND join, a dropped scheme branch, a copy-pasted wrong SQLSTATE constant, a comparison that is always true), and the rest are enhancements -- pagination, case-insensitive tag matching, a distinct-tags endpoint backed by a Postgres unnest() aggregate, and a PATCH partial-update endpoint that has to distinguish "field omitted" from "field present but empty" -- each with the smallest scaffolding needed for its dedicated test to compile. Every ticket has a dedicated Go test, isolated from the others under practicetickets/, that goes green when the fix is correct.
bookmarks_test