Skip to content

Commit bcb874e

Browse files
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 <noreply@anthropic.com>
1 parent afa39c8 commit bcb874e

6 files changed

Lines changed: 191 additions & 1 deletion

File tree

api/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,8 @@ func NewApiServer(config config.Config) *ApiServer {
474474
g.Get("/tracks/search", app.v1TracksSearch)
475475
g.Get("/tracks/unclaimed_id", app.v1TracksUnclaimedId)
476476

477-
g.Get("/tracks/trending", app.v1TracksTrending)
477+
g.Get("/tracks/latest", app.v1TracksLatest)
478+
g.Get("/tracks/trending", app.v1TracksTrending)
478479
g.Get("/tracks/trending/ids", app.v1TracksTrendingIds)
479480
g.Get("/tracks/trending/winners", app.v1TracksTrendingWinners)
480481
g.Get("/tracks/trending/underground", app.v1TracksTrendingUnderground)

api/server_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func testAppWithFixtures(t *testing.T) *ApiServer {
9999
database.SeedTable(app.pool.Replicas[0], "reposts", testdata.RepostFixtures)
100100
database.SeedTable(app.pool.Replicas[0], "saves", testdata.SaveFixtures)
101101
database.SeedTable(app.pool.Replicas[0], "tracks", testdata.TrackFixtures)
102+
database.SeedTable(app.pool.Replicas[0], "tracks", testdata.LatestTrackFixtures)
102103
database.SeedTable(app.pool.Replicas[0], "track_trending_scores", testdata.TrackTrendingScoresFixtures)
103104
database.SeedTable(app.pool.Replicas[0], "trending_results", testdata.TrendingResultsFixtures)
104105
database.SeedTable(app.pool.Replicas[0], "track_routes", testdata.TrackRoutesFixtures)

api/swagger/swagger-v1.yaml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2760,6 +2760,52 @@ paths:
27602760
"500":
27612761
description: Server error
27622762
content: {}
2763+
/tracks/latest:
2764+
get:
2765+
tags:
2766+
- tracks
2767+
description: Gets the most recent tracks on Audius, ordered by creation date
2768+
operationId: Get Latest Tracks
2769+
security:
2770+
- {}
2771+
- OAuth2:
2772+
- read
2773+
parameters:
2774+
- name: offset
2775+
in: query
2776+
description:
2777+
The number of items to skip. Useful for pagination (page number
2778+
* limit)
2779+
schema:
2780+
type: integer
2781+
- name: limit
2782+
in: query
2783+
description: The number of items to fetch
2784+
schema:
2785+
type: integer
2786+
- name: user_id
2787+
in: query
2788+
description: The user ID of the user making the request
2789+
schema:
2790+
type: string
2791+
- name: genre
2792+
in: query
2793+
description: Filter to a specified genre
2794+
schema:
2795+
type: string
2796+
responses:
2797+
"200":
2798+
description: Success
2799+
content:
2800+
application/json:
2801+
schema:
2802+
$ref: "#/components/schemas/tracks_response"
2803+
"400":
2804+
description: Bad request
2805+
content: {}
2806+
"500":
2807+
description: Server error
2808+
content: {}
27632809
/tracks/trending:
27642810
get:
27652811
tags:
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testdata
2+
3+
var LatestTrackIds = []int{802, 801, 800}
4+
5+
var LatestTrackFixtures = []map[string]any{
6+
{"track_id": 800, "genre": "LatestTestGenreA", "owner_id": 1, "title": "Latest Track Old", "is_unlisted": "f", "created_at": "2025-01-01 00:00:00"},
7+
{"track_id": 801, "genre": "LatestTestGenreA", "owner_id": 1, "title": "Latest Track Mid", "is_unlisted": "f", "created_at": "2025-02-01 00:00:00"},
8+
{"track_id": 802, "genre": "LatestTestGenreC", "owner_id": 1, "title": "Latest Track New", "is_unlisted": "f", "created_at": "2025-03-01 00:00:00"},
9+
{"track_id": 803, "genre": "LatestTestGenreB", "owner_id": 1, "title": "Latest Track Visible", "is_unlisted": "f", "created_at": "2025-02-15 00:00:00"},
10+
{"track_id": 804, "genre": "LatestTestGenreB", "owner_id": 1, "title": "Latest Track Hidden", "is_unlisted": "t", "created_at": "2025-02-20 00:00:00"},
11+
}

api/v1_tracks_latest.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package api
2+
3+
import (
4+
"api.audius.co/api/dbv1"
5+
"github.com/gofiber/fiber/v2"
6+
"github.com/jackc/pgx/v5"
7+
)
8+
9+
type GetLatestTracksParams struct {
10+
Limit int `query:"limit" default:"100" validate:"min=1,max=100"`
11+
Offset int `query:"offset" default:"0" validate:"min=0,max=200"`
12+
Genre string `query:"genre" default:""`
13+
}
14+
15+
func (app *ApiServer) v1TracksLatest(c *fiber.Ctx) error {
16+
var params GetLatestTracksParams
17+
if err := app.ParseAndValidateQueryParams(c, &params); err != nil {
18+
return err
19+
}
20+
21+
myId := app.getMyId(c)
22+
23+
trackIds, err := app.getLatestTrackIds(c, params.Genre, params.Limit, params.Offset)
24+
if err != nil {
25+
return err
26+
}
27+
28+
tracks, err := app.queries.Tracks(c.Context(), dbv1.TracksParams{
29+
GetTracksParams: dbv1.GetTracksParams{
30+
Ids: trackIds,
31+
MyID: myId,
32+
AuthedWallet: app.tryGetAuthedWallet(c),
33+
},
34+
})
35+
if err != nil {
36+
return err
37+
}
38+
39+
return v1TracksResponse(c, tracks)
40+
}
41+
42+
func (app *ApiServer) getLatestTrackIds(c *fiber.Ctx, genre string, limit int, offset int) ([]int32, error) {
43+
sql := `
44+
SELECT track_id
45+
FROM tracks
46+
WHERE is_current = true
47+
AND is_delete = false
48+
AND is_unlisted = false
49+
AND is_available = true
50+
AND (@genre = '' OR genre = @genre)
51+
ORDER BY
52+
created_at DESC,
53+
track_id DESC
54+
LIMIT @limit
55+
OFFSET @offset
56+
`
57+
58+
args := pgx.NamedArgs{
59+
"genre": genre,
60+
"limit": limit,
61+
"offset": offset,
62+
}
63+
64+
rows, err := app.pool.Query(c.Context(), sql, args)
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
return pgx.CollectRows(rows, pgx.RowTo[int32])
70+
}

api/v1_tracks_latest_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package api
2+
3+
import (
4+
"testing"
5+
6+
"api.audius.co/api/dbv1"
7+
"api.audius.co/trashid"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestGetLatest(t *testing.T) {
12+
app := testAppWithFixtures(t)
13+
var resp struct {
14+
Data []dbv1.Track
15+
}
16+
status, _ := testGet(t, app, "/v1/tracks/latest?limit=5", &resp)
17+
assert.Equal(t, 200, status)
18+
assert.Equal(t, 5, len(resp.Data))
19+
}
20+
21+
func TestGetLatestWithGenre(t *testing.T) {
22+
app := testAppWithFixtures(t)
23+
var resp struct {
24+
Data []dbv1.Track
25+
}
26+
status, _ := testGet(t, app, "/v1/tracks/latest?genre=LatestTestGenreA", &resp)
27+
assert.Equal(t, 200, status)
28+
assert.Equal(t, 2, len(resp.Data))
29+
assert.Equal(t, trashid.MustEncodeHashID(801), resp.Data[0].ID)
30+
assert.Equal(t, trashid.MustEncodeHashID(800), resp.Data[1].ID)
31+
for _, track := range resp.Data {
32+
assert.Equal(t, "LatestTestGenreA", track.Genre.String)
33+
}
34+
}
35+
36+
func TestGetLatestWithLimitOffset(t *testing.T) {
37+
app := testAppWithFixtures(t)
38+
var resp struct {
39+
Data []dbv1.Track
40+
}
41+
status, _ := testGet(t, app, "/v1/tracks/latest?genre=LatestTestGenreA&limit=1&offset=0", &resp)
42+
assert.Equal(t, 200, status)
43+
assert.Equal(t, 1, len(resp.Data))
44+
assert.Equal(t, trashid.MustEncodeHashID(801), resp.Data[0].ID)
45+
46+
status, _ = testGet(t, app, "/v1/tracks/latest?genre=LatestTestGenreA&limit=1&offset=1", &resp)
47+
assert.Equal(t, 200, status)
48+
assert.Equal(t, 1, len(resp.Data))
49+
assert.Equal(t, trashid.MustEncodeHashID(800), resp.Data[0].ID)
50+
}
51+
52+
func TestGetLatestExcludesUnlisted(t *testing.T) {
53+
app := testAppWithFixtures(t)
54+
var resp struct {
55+
Data []dbv1.Track
56+
}
57+
status, _ := testGet(t, app, "/v1/tracks/latest?genre=LatestTestGenreB", &resp)
58+
assert.Equal(t, 200, status)
59+
assert.Equal(t, 1, len(resp.Data))
60+
assert.Equal(t, trashid.MustEncodeHashID(803), resp.Data[0].ID)
61+
}

0 commit comments

Comments
 (0)