From bcb874eb735ba215dad7613bdd4df1a654319103 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Tue, 31 Mar 2026 18:21:37 -0700 Subject: [PATCH 1/2] Add v1/tracks/latest endpoint for reverse chronological track listing Queries tracks ordered by created_at DESC with genre filtering, limit/offset pagination, and hidden/unlisted track exclusion. Co-Authored-By: Claude Opus 4.6 --- api/server.go | 3 +- api/server_test.go | 1 + api/swagger/swagger-v1.yaml | 46 ++++++++++++++++++ api/testdata/latest_track_fixtures.go | 11 +++++ api/v1_tracks_latest.go | 70 +++++++++++++++++++++++++++ api/v1_tracks_latest_test.go | 61 +++++++++++++++++++++++ 6 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 api/testdata/latest_track_fixtures.go create mode 100644 api/v1_tracks_latest.go create mode 100644 api/v1_tracks_latest_test.go diff --git a/api/server.go b/api/server.go index 0757338f..9eaccff5 100644 --- a/api/server.go +++ b/api/server.go @@ -474,7 +474,8 @@ func NewApiServer(config config.Config) *ApiServer { g.Get("/tracks/search", app.v1TracksSearch) g.Get("/tracks/unclaimed_id", app.v1TracksUnclaimedId) - g.Get("/tracks/trending", app.v1TracksTrending) + g.Get("/tracks/latest", app.v1TracksLatest) + g.Get("/tracks/trending", app.v1TracksTrending) g.Get("/tracks/trending/ids", app.v1TracksTrendingIds) g.Get("/tracks/trending/winners", app.v1TracksTrendingWinners) g.Get("/tracks/trending/underground", app.v1TracksTrendingUnderground) diff --git a/api/server_test.go b/api/server_test.go index c763362c..e2e3c3a6 100644 --- a/api/server_test.go +++ b/api/server_test.go @@ -99,6 +99,7 @@ func testAppWithFixtures(t *testing.T) *ApiServer { database.SeedTable(app.pool.Replicas[0], "reposts", testdata.RepostFixtures) database.SeedTable(app.pool.Replicas[0], "saves", testdata.SaveFixtures) database.SeedTable(app.pool.Replicas[0], "tracks", testdata.TrackFixtures) + database.SeedTable(app.pool.Replicas[0], "tracks", testdata.LatestTrackFixtures) database.SeedTable(app.pool.Replicas[0], "track_trending_scores", testdata.TrackTrendingScoresFixtures) database.SeedTable(app.pool.Replicas[0], "trending_results", testdata.TrendingResultsFixtures) database.SeedTable(app.pool.Replicas[0], "track_routes", testdata.TrackRoutesFixtures) diff --git a/api/swagger/swagger-v1.yaml b/api/swagger/swagger-v1.yaml index 55160766..4ea16c90 100644 --- a/api/swagger/swagger-v1.yaml +++ b/api/swagger/swagger-v1.yaml @@ -2760,6 +2760,52 @@ paths: "500": description: Server error content: {} + /tracks/latest: + get: + tags: + - tracks + description: Gets the most recent tracks on Audius, ordered by creation date + operationId: Get Latest Tracks + security: + - {} + - OAuth2: + - read + parameters: + - name: offset + in: query + description: + The number of items to skip. Useful for pagination (page number + * limit) + schema: + type: integer + - name: limit + in: query + description: The number of items to fetch + schema: + type: integer + - name: user_id + in: query + description: The user ID of the user making the request + schema: + type: string + - name: genre + in: query + description: Filter to a specified genre + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/tracks_response" + "400": + description: Bad request + content: {} + "500": + description: Server error + content: {} /tracks/trending: get: tags: diff --git a/api/testdata/latest_track_fixtures.go b/api/testdata/latest_track_fixtures.go new file mode 100644 index 00000000..20a1f563 --- /dev/null +++ b/api/testdata/latest_track_fixtures.go @@ -0,0 +1,11 @@ +package testdata + +var LatestTrackIds = []int{802, 801, 800} + +var LatestTrackFixtures = []map[string]any{ + {"track_id": 800, "genre": "LatestTestGenreA", "owner_id": 1, "title": "Latest Track Old", "is_unlisted": "f", "created_at": "2025-01-01 00:00:00"}, + {"track_id": 801, "genre": "LatestTestGenreA", "owner_id": 1, "title": "Latest Track Mid", "is_unlisted": "f", "created_at": "2025-02-01 00:00:00"}, + {"track_id": 802, "genre": "LatestTestGenreC", "owner_id": 1, "title": "Latest Track New", "is_unlisted": "f", "created_at": "2025-03-01 00:00:00"}, + {"track_id": 803, "genre": "LatestTestGenreB", "owner_id": 1, "title": "Latest Track Visible", "is_unlisted": "f", "created_at": "2025-02-15 00:00:00"}, + {"track_id": 804, "genre": "LatestTestGenreB", "owner_id": 1, "title": "Latest Track Hidden", "is_unlisted": "t", "created_at": "2025-02-20 00:00:00"}, +} diff --git a/api/v1_tracks_latest.go b/api/v1_tracks_latest.go new file mode 100644 index 00000000..95b5d37a --- /dev/null +++ b/api/v1_tracks_latest.go @@ -0,0 +1,70 @@ +package api + +import ( + "api.audius.co/api/dbv1" + "github.com/gofiber/fiber/v2" + "github.com/jackc/pgx/v5" +) + +type GetLatestTracksParams struct { + Limit int `query:"limit" default:"100" validate:"min=1,max=100"` + Offset int `query:"offset" default:"0" validate:"min=0,max=200"` + Genre string `query:"genre" default:""` +} + +func (app *ApiServer) v1TracksLatest(c *fiber.Ctx) error { + var params GetLatestTracksParams + if err := app.ParseAndValidateQueryParams(c, ¶ms); err != nil { + return err + } + + myId := app.getMyId(c) + + trackIds, err := app.getLatestTrackIds(c, params.Genre, params.Limit, params.Offset) + if err != nil { + return err + } + + tracks, err := app.queries.Tracks(c.Context(), dbv1.TracksParams{ + GetTracksParams: dbv1.GetTracksParams{ + Ids: trackIds, + MyID: myId, + AuthedWallet: app.tryGetAuthedWallet(c), + }, + }) + if err != nil { + return err + } + + return v1TracksResponse(c, tracks) +} + +func (app *ApiServer) getLatestTrackIds(c *fiber.Ctx, genre string, limit int, offset int) ([]int32, error) { + sql := ` + SELECT track_id + FROM tracks + WHERE is_current = true + AND is_delete = false + AND is_unlisted = false + AND is_available = true + AND (@genre = '' OR genre = @genre) + ORDER BY + created_at DESC, + track_id DESC + LIMIT @limit + OFFSET @offset + ` + + args := pgx.NamedArgs{ + "genre": genre, + "limit": limit, + "offset": offset, + } + + rows, err := app.pool.Query(c.Context(), sql, args) + if err != nil { + return nil, err + } + + return pgx.CollectRows(rows, pgx.RowTo[int32]) +} diff --git a/api/v1_tracks_latest_test.go b/api/v1_tracks_latest_test.go new file mode 100644 index 00000000..c6342679 --- /dev/null +++ b/api/v1_tracks_latest_test.go @@ -0,0 +1,61 @@ +package api + +import ( + "testing" + + "api.audius.co/api/dbv1" + "api.audius.co/trashid" + "github.com/stretchr/testify/assert" +) + +func TestGetLatest(t *testing.T) { + app := testAppWithFixtures(t) + var resp struct { + Data []dbv1.Track + } + status, _ := testGet(t, app, "/v1/tracks/latest?limit=5", &resp) + assert.Equal(t, 200, status) + assert.Equal(t, 5, len(resp.Data)) +} + +func TestGetLatestWithGenre(t *testing.T) { + app := testAppWithFixtures(t) + var resp struct { + Data []dbv1.Track + } + status, _ := testGet(t, app, "/v1/tracks/latest?genre=LatestTestGenreA", &resp) + assert.Equal(t, 200, status) + assert.Equal(t, 2, len(resp.Data)) + assert.Equal(t, trashid.MustEncodeHashID(801), resp.Data[0].ID) + assert.Equal(t, trashid.MustEncodeHashID(800), resp.Data[1].ID) + for _, track := range resp.Data { + assert.Equal(t, "LatestTestGenreA", track.Genre.String) + } +} + +func TestGetLatestWithLimitOffset(t *testing.T) { + app := testAppWithFixtures(t) + var resp struct { + Data []dbv1.Track + } + status, _ := testGet(t, app, "/v1/tracks/latest?genre=LatestTestGenreA&limit=1&offset=0", &resp) + assert.Equal(t, 200, status) + assert.Equal(t, 1, len(resp.Data)) + assert.Equal(t, trashid.MustEncodeHashID(801), resp.Data[0].ID) + + status, _ = testGet(t, app, "/v1/tracks/latest?genre=LatestTestGenreA&limit=1&offset=1", &resp) + assert.Equal(t, 200, status) + assert.Equal(t, 1, len(resp.Data)) + assert.Equal(t, trashid.MustEncodeHashID(800), resp.Data[0].ID) +} + +func TestGetLatestExcludesUnlisted(t *testing.T) { + app := testAppWithFixtures(t) + var resp struct { + Data []dbv1.Track + } + status, _ := testGet(t, app, "/v1/tracks/latest?genre=LatestTestGenreB", &resp) + assert.Equal(t, 200, status) + assert.Equal(t, 1, len(resp.Data)) + assert.Equal(t, trashid.MustEncodeHashID(803), resp.Data[0].ID) +} From aa8277096e9b9fc331a653035890d783ee88675b Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Tue, 31 Mar 2026 18:23:44 -0700 Subject: [PATCH 2/2] Move latest track fixtures inline into test file Co-Authored-By: Claude Opus 4.6 --- api/server_test.go | 1 - api/testdata/latest_track_fixtures.go | 11 ----------- api/v1_tracks_latest_test.go | 21 +++++++++++++++++---- 3 files changed, 17 insertions(+), 16 deletions(-) delete mode 100644 api/testdata/latest_track_fixtures.go diff --git a/api/server_test.go b/api/server_test.go index e2e3c3a6..c763362c 100644 --- a/api/server_test.go +++ b/api/server_test.go @@ -99,7 +99,6 @@ func testAppWithFixtures(t *testing.T) *ApiServer { database.SeedTable(app.pool.Replicas[0], "reposts", testdata.RepostFixtures) database.SeedTable(app.pool.Replicas[0], "saves", testdata.SaveFixtures) database.SeedTable(app.pool.Replicas[0], "tracks", testdata.TrackFixtures) - database.SeedTable(app.pool.Replicas[0], "tracks", testdata.LatestTrackFixtures) database.SeedTable(app.pool.Replicas[0], "track_trending_scores", testdata.TrackTrendingScoresFixtures) database.SeedTable(app.pool.Replicas[0], "trending_results", testdata.TrendingResultsFixtures) database.SeedTable(app.pool.Replicas[0], "track_routes", testdata.TrackRoutesFixtures) diff --git a/api/testdata/latest_track_fixtures.go b/api/testdata/latest_track_fixtures.go deleted file mode 100644 index 20a1f563..00000000 --- a/api/testdata/latest_track_fixtures.go +++ /dev/null @@ -1,11 +0,0 @@ -package testdata - -var LatestTrackIds = []int{802, 801, 800} - -var LatestTrackFixtures = []map[string]any{ - {"track_id": 800, "genre": "LatestTestGenreA", "owner_id": 1, "title": "Latest Track Old", "is_unlisted": "f", "created_at": "2025-01-01 00:00:00"}, - {"track_id": 801, "genre": "LatestTestGenreA", "owner_id": 1, "title": "Latest Track Mid", "is_unlisted": "f", "created_at": "2025-02-01 00:00:00"}, - {"track_id": 802, "genre": "LatestTestGenreC", "owner_id": 1, "title": "Latest Track New", "is_unlisted": "f", "created_at": "2025-03-01 00:00:00"}, - {"track_id": 803, "genre": "LatestTestGenreB", "owner_id": 1, "title": "Latest Track Visible", "is_unlisted": "f", "created_at": "2025-02-15 00:00:00"}, - {"track_id": 804, "genre": "LatestTestGenreB", "owner_id": 1, "title": "Latest Track Hidden", "is_unlisted": "t", "created_at": "2025-02-20 00:00:00"}, -} diff --git a/api/v1_tracks_latest_test.go b/api/v1_tracks_latest_test.go index c6342679..a3aa6712 100644 --- a/api/v1_tracks_latest_test.go +++ b/api/v1_tracks_latest_test.go @@ -4,12 +4,25 @@ import ( "testing" "api.audius.co/api/dbv1" + "api.audius.co/database" "api.audius.co/trashid" "github.com/stretchr/testify/assert" ) -func TestGetLatest(t *testing.T) { +func latestTestApp(t *testing.T) *ApiServer { app := testAppWithFixtures(t) + database.SeedTable(app.pool.Replicas[0], "tracks", []map[string]any{ + {"track_id": 800, "genre": "LatestTestGenreA", "owner_id": 1, "title": "Latest Track Old", "is_unlisted": "f", "created_at": "2025-01-01 00:00:00"}, + {"track_id": 801, "genre": "LatestTestGenreA", "owner_id": 1, "title": "Latest Track Mid", "is_unlisted": "f", "created_at": "2025-02-01 00:00:00"}, + {"track_id": 802, "genre": "LatestTestGenreC", "owner_id": 1, "title": "Latest Track New", "is_unlisted": "f", "created_at": "2025-03-01 00:00:00"}, + {"track_id": 803, "genre": "LatestTestGenreB", "owner_id": 1, "title": "Latest Track Visible", "is_unlisted": "f", "created_at": "2025-02-15 00:00:00"}, + {"track_id": 804, "genre": "LatestTestGenreB", "owner_id": 1, "title": "Latest Track Hidden", "is_unlisted": "t", "created_at": "2025-02-20 00:00:00"}, + }) + return app +} + +func TestGetLatest(t *testing.T) { + app := latestTestApp(t) var resp struct { Data []dbv1.Track } @@ -19,7 +32,7 @@ func TestGetLatest(t *testing.T) { } func TestGetLatestWithGenre(t *testing.T) { - app := testAppWithFixtures(t) + app := latestTestApp(t) var resp struct { Data []dbv1.Track } @@ -34,7 +47,7 @@ func TestGetLatestWithGenre(t *testing.T) { } func TestGetLatestWithLimitOffset(t *testing.T) { - app := testAppWithFixtures(t) + app := latestTestApp(t) var resp struct { Data []dbv1.Track } @@ -50,7 +63,7 @@ func TestGetLatestWithLimitOffset(t *testing.T) { } func TestGetLatestExcludesUnlisted(t *testing.T) { - app := testAppWithFixtures(t) + app := latestTestApp(t) var resp struct { Data []dbv1.Track }