feat: popular genres endpoint#961
Merged
Merged
Conversation
dylanjeffers
added a commit
that referenced
this pull request
Jun 18, 2026
## What Collapses genre spelling variants — e.g. `Hip Hop` / `hip-hop` / `hiphop`, `r&b` / `rnb` — to a single canonical form, building on the `/v1/genres/popular` endpoint added in #961. ## Design decisions **Normalize on read (display only), not on write, and not on filter params.** The Go bridge service does **not** write the `tracks.genre` column — tracks (including genre) are populated upstream by the Python discovery provider during chain indexing. So there is no write path in this repo to hook. **`NormalizeGenre()`** (`api/genre_normalize.go`): - trims surrounding whitespace, collapses internal whitespace runs - title-cases by default, preserving internal separators (`hip-hop/rap` → `Hip-Hop/Rap`) - maps known special cases via a collapsed lookup key (lowercase, alphanumeric-only) so every punctuation/spacing variant routes through one entry: `R&B`, `EDM`, `DJ`, `Hip Hop`, `Drum & Bass`, `Lo-Fi`, `K-Pop`, `J-Pop` - already-canonical values (`Electronic`, `R&B`, `Hip Hop`) pass through unchanged **`/v1/genres/popular`** — normalize each name, then **merge and sum counts** for variants that collapse together, re-sort by total desc, and apply `min_count` to the merged total. Caveat (in code): the SQL still `GROUP BY`s and paginates on the raw genre, so merging only catches variants within the same page; full at-rest aggregation requires normalizing on write upstream. **Genre filter params were intentionally NOT normalized.** An earlier revision normalized the `genre` param on the trending / underground / latest / users-genre-top endpoints, but that is **unsound**: because the stored `genre` column is not normalized at rest, canonicalizing the param stops it matching the raw stored value. CI caught this (`TestGetLatestWithGenre`: a query for `LatestTestGenreA` was title-cased to `Latesttestgenrea` and matched zero rows). Reverted in 01ba2ba — filtering is left untouched. Collapsing variants for *filtering* requires normalizing genre on write upstream. ## Tests `TestGenreNormalize` covers trim, internal-whitespace collapse, casing, `hip-hop`/`hiphop` → `Hip Hop`, `r&b`/`rnb` → `R&B`, special cases, and already-canonical pass-through. ``` go test ./api/ -run TestGenreNormal # PASS (24 subtests) ``` `go build` and `go vet ./api/` are clean. DB-backed tests run in CI (Docker is broken in the local dev environment). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
GET /v1/genres/popularreturning genres ranked by track usage. Powers freeform genre autocomplete on upload.What it does
g.Get("/genres/popular", app.v1GenresPopular)inapi/server.go.api/v1_genres_popular.gowraps the existingGetGenressqlc query (GROUP BY genre ORDER BY count DESC, filteringis_current, non-empty genre,created_at > start_time, andaccess_authorities IS NULL).start_time(unix, default 0),limit(1–100, default 100),offset(≥0, default 0),min_count(1–1e6, default 1).genrestag withpopular_genres_response/popular_genreschemas.Tests
api/v1_genres_popular_test.gocovers ordering + fixture presence,min_countfiltering, andaccess_authoritiesexclusion.TestGenresPopularcould not be executed here. CI will run the full suite on this PR.Review note (non-blocking)
min_countis filtered in Go after the SQLLIMIT/OFFSET, so a page can be trimmed below the requestedlimitand low-count genres still consume a page slot. Harmless for the autocomplete use case (defaultlimit=100,min_count=1), but moving it to aHAVING count >= @min_countclause would make pagination exact. Worth a follow-up ifmin_count+ pagination get used together.🤖 Generated with Claude Code