Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,9 @@ func NewApiServer(config config.Config) *ApiServer {

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)
g.Get("/tracks/trending/underground/winners", app.v1TracksTrendingUndergroundWinners)
g.Get("/tracks/recommended", app.v1TracksTrending)
g.Get("/tracks/recent-premium", app.v1TracksRecentPremium)
g.Get("/tracks/usdc-purchase", app.v1TracksUsdcPurchase)
Expand Down
1 change: 1 addition & 0 deletions api/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func testAppWithFixtures(t *testing.T) *ApiServer {
database.SeedTable(app.pool.Replicas[0], "saves", testdata.SaveFixtures)
database.SeedTable(app.pool.Replicas[0], "tracks", testdata.TrackFixtures)
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)
database.SeedTable(app.pool.Replicas[0], "usdc_purchases", testdata.UsdcPurchasesFixtures)
database.SeedTable(app.pool.Replicas[0], "usdc_transactions_history", testdata.UsdcTransactionsHistoryFixtures)
Expand Down
62 changes: 62 additions & 0 deletions api/swagger/swagger-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2420,6 +2420,37 @@ paths:
"500":
description: Server error
content: {}
/tracks/trending/winners:
get:
tags:
- tracks
description: Gets weekly trending winners from the trending_results table. Returns full track objects for the specified week. Defaults to the most recent week with data when no week is provided.
operationId: Get Trending Winners
parameters:
- name: week
in: query
description: Target week in YYYY-MM-DD format. Defaults to the most recent week with data.
schema:
type: string
format: date
- name: user_id
in: query
description: The user ID of the user making the request
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/{version}:
get:
tags:
Expand Down Expand Up @@ -2579,6 +2610,37 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/tracks_response"
/tracks/trending/underground/winners:
get:
tags:
- tracks
description: Gets weekly trending underground winners from the trending_results table. Returns full track objects for the specified week. Defaults to the most recent week with data when no week is provided.
operationId: Get Trending Underground Winners
parameters:
- name: week
in: query
description: Target week in YYYY-MM-DD format. Defaults to the most recent week with data.
schema:
type: string
format: date
- name: user_id
in: query
description: The user ID of the user making the request
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/underground/{version}:
get:
tags:
Expand Down
12 changes: 12 additions & 0 deletions api/testdata/trending_results_fixtures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package testdata

var TrendingResultsFixtures = []map[string]any{
// Week 2022-01-21 - tracks 300, 202, 200 (rank 1, 2, 3) - matches trending order
{"user_id": 3, "id": "300", "rank": 1, "type": "TrendingType.TRACKS", "version": "TrendingVersion.ML51L", "week": "2022-01-21"},
{"user_id": 2, "id": "202", "rank": 2, "type": "TrendingType.TRACKS", "version": "TrendingVersion.ML51L", "week": "2022-01-21"},
{"user_id": 2, "id": "200", "rank": 3, "type": "TrendingType.TRACKS", "version": "TrendingVersion.ML51L", "week": "2022-01-21"},
// Week 2022-01-21 - underground tracks 519, 520, 521 (rank 1, 2, 3)
{"user_id": 8, "id": "519", "rank": 1, "type": "TrendingType.UNDERGROUND_TRACKS", "version": "TrendingVersion.ML51L", "week": "2022-01-21"},
{"user_id": 11, "id": "520", "rank": 2, "type": "TrendingType.UNDERGROUND_TRACKS", "version": "TrendingVersion.ML51L", "week": "2022-01-21"},
{"user_id": 1, "id": "521", "rank": 3, "type": "TrendingType.UNDERGROUND_TRACKS", "version": "TrendingVersion.ML51L", "week": "2022-01-21"},
}
93 changes: 93 additions & 0 deletions api/v1_tracks_trending_underground_winners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package api

import (
"api.audius.co/api/dbv1"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5"
)

type GetTrendingUndergroundWinnersTracksParams struct {
Week string `query:"week" default:""`
}

func (app *ApiServer) v1TracksTrendingUndergroundWinners(c *fiber.Ctx) error {
var params = GetTrendingUndergroundWinnersTracksParams{}
if err := app.ParseAndValidateQueryParams(c, &params); err != nil {
return err
}

myId := app.getMyId(c)

trackIds, err := app.getTrendingUndergroundWinnersIds(c, params.Week)
if err != nil {
return err
}

if len(trackIds) == 0 {
return v1TracksResponse(c, []dbv1.Track{})
}

tracks, err := app.queries.Tracks(c.Context(), dbv1.TracksParams{
GetTracksParams: dbv1.GetTracksParams{
Ids: trackIds,
MyID: myId,
},
})
if err != nil {
return err
}

return v1TracksResponse(c, tracks)
}

func (app *ApiServer) getTrendingUndergroundWinnersIds(c *fiber.Ctx, weekParam string) ([]int32, error) {
args := pgx.NamedArgs{
"type": "TrendingType.UNDERGROUND_TRACKS",
}

var weekFilter string
if weekParam != "" {
args["week"] = weekParam
// Nearest row on or after the requested week; if none (e.g. future date), use most recent
weekFilter = `AND tr.week = COALESCE(
(SELECT MIN(tr2.week) FROM trending_results tr2
WHERE tr2.type = @type AND tr2.week >= @week::date),
(SELECT MAX(tr2.week) FROM trending_results tr2
WHERE tr2.type = @type)
)`
} else {
// Default: use the most recent week in the table that is before today
weekFilter = `AND tr.week = (
SELECT MAX(tr2.week)
FROM trending_results tr2
WHERE tr2.type = @type
AND tr2.week < CURRENT_DATE
)`
}

sql := `
SELECT tr.id::int
FROM trending_results tr
JOIN tracks t ON t.track_id = tr.id::int
AND t.is_current = true
AND t.is_delete = false
AND t.is_unlisted = false
AND t.is_available = true
WHERE tr.type = @type
` + weekFilter + `
ORDER BY tr.rank ASC
`

rows, err := app.pool.Query(c.Context(), sql, args)
if err != nil {
return nil, err
}
defer rows.Close()

trackIds, err := pgx.CollectRows(rows, pgx.RowTo[int32])
if err != nil {
return nil, err
}

return trackIds, nil
}
93 changes: 93 additions & 0 deletions api/v1_tracks_trending_underground_winners_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package api

import (
"testing"

"api.audius.co/api/dbv1"
"api.audius.co/trashid"
"github.com/stretchr/testify/assert"
)

func TestGetTrendingUndergroundWinnersTracks(t *testing.T) {
app := testAppWithFixtures(t)

var resp struct {
Data []dbv1.Track
}

// Test with explicit week - uses trending_results for 2022-01-21
status, body := testGet(t, app, "/v1/tracks/trending/underground/winners?week=2022-01-21", &resp)
assert.Equal(t, 200, status)

jsonAssert(t, body, map[string]any{
"data.#": 3,
})

// Verify order: 519 (rank 1), 520 (rank 2), 521 (rank 3)
assert.Equal(t, trashid.MustEncodeHashID(519), resp.Data[0].ID)
assert.Equal(t, "Jazz", resp.Data[0].Genre.String)

assert.Equal(t, trashid.MustEncodeHashID(520), resp.Data[1].ID)
assert.Equal(t, "Classical", resp.Data[1].Genre.String)

assert.Equal(t, trashid.MustEncodeHashID(521), resp.Data[2].ID)
assert.Equal(t, "Electronic", resp.Data[2].Genre.String)
}

func TestGetTrendingUndergroundWinnersTracksDefaultWeek(t *testing.T) {
app := testAppWithFixtures(t)

var resp struct {
Data []dbv1.Track
}

// Without week param - defaults to most recent week in table before today (2022-01-21)
status, body := testGet(t, app, "/v1/tracks/trending/underground/winners", &resp)
assert.Equal(t, 200, status)

jsonAssert(t, body, map[string]any{
"data.#": 3,
})

assert.Equal(t, trashid.MustEncodeHashID(519), resp.Data[0].ID)
assert.Equal(t, trashid.MustEncodeHashID(520), resp.Data[1].ID)
assert.Equal(t, trashid.MustEncodeHashID(521), resp.Data[2].ID)
}

func TestGetTrendingUndergroundWinnersTracksOldDateUsesEarliestWeek(t *testing.T) {
app := testAppWithFixtures(t)

var resp struct {
Data []dbv1.Track
}

// Query before any data (2020-01-01): nearest row on or after is 2022-01-21 (earliest we have)
status, _ := testGet(t, app, "/v1/tracks/trending/underground/winners?week=2020-01-01", &resp)
assert.Equal(t, 200, status)
assert.Equal(t, 3, len(resp.Data))
}

func TestGetTrendingUndergroundWinnersTracksNearestWeek(t *testing.T) {
app := testAppWithFixtures(t)

var resp struct {
Data []dbv1.Track
}

// Query 2022-01-20: nearest row on or after is 2022-01-21
status, _ := testGet(t, app, "/v1/tracks/trending/underground/winners?week=2022-01-20", &resp)
assert.Equal(t, 200, status)
assert.Equal(t, 3, len(resp.Data))
assert.Equal(t, trashid.MustEncodeHashID(519), resp.Data[0].ID)

// Query 2022-01-21: exact match
status, _ = testGet(t, app, "/v1/tracks/trending/underground/winners?week=2022-01-21", &resp)
assert.Equal(t, 200, status)
assert.Equal(t, 3, len(resp.Data))

// Query 2022-01-25: no row after 2022-01-21, use most recent (2022-01-21)
status, _ = testGet(t, app, "/v1/tracks/trending/underground/winners?week=2022-01-25", &resp)
assert.Equal(t, 200, status)
assert.Equal(t, 3, len(resp.Data))
assert.Equal(t, trashid.MustEncodeHashID(519), resp.Data[0].ID)
}
93 changes: 93 additions & 0 deletions api/v1_tracks_trending_winners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package api

import (
"api.audius.co/api/dbv1"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5"
)

type GetTrendingWinnersTracksParams struct {
Week string `query:"week" default:""`
}

func (app *ApiServer) v1TracksTrendingWinners(c *fiber.Ctx) error {
var params = GetTrendingWinnersTracksParams{}
if err := app.ParseAndValidateQueryParams(c, &params); err != nil {
return err
}

myId := app.getMyId(c)

trackIds, err := app.getTrendingWinnersIds(c, params.Week)
if err != nil {
return err
}

if len(trackIds) == 0 {
return v1TracksResponse(c, []dbv1.Track{})
}

tracks, err := app.queries.Tracks(c.Context(), dbv1.TracksParams{
GetTracksParams: dbv1.GetTracksParams{
Ids: trackIds,
MyID: myId,
},
})
if err != nil {
return err
}

return v1TracksResponse(c, tracks)
}

func (app *ApiServer) getTrendingWinnersIds(c *fiber.Ctx, weekParam string) ([]int32, error) {
args := pgx.NamedArgs{
"type": "TrendingType.TRACKS",
}

var weekFilter string
if weekParam != "" {
args["week"] = weekParam
// Nearest row on or after the requested week; if none (e.g. future date), use most recent
weekFilter = `AND tr.week = COALESCE(
(SELECT MIN(tr2.week) FROM trending_results tr2
WHERE tr2.type = @type AND tr2.week >= @week::date),
(SELECT MAX(tr2.week) FROM trending_results tr2
WHERE tr2.type = @type)
)`
} else {
// Default: use the most recent week in the table that is before today
weekFilter = `AND tr.week = (
SELECT MAX(tr2.week)
FROM trending_results tr2
WHERE tr2.type = @type
AND tr2.week < CURRENT_DATE
)`
}

sql := `
SELECT tr.id::int
FROM trending_results tr
JOIN tracks t ON t.track_id = tr.id::int
AND t.is_current = true
AND t.is_delete = false
AND t.is_unlisted = false
AND t.is_available = true
WHERE tr.type = @type
` + weekFilter + `
ORDER BY tr.rank ASC
`

rows, err := app.pool.Query(c.Context(), sql, args)
if err != nil {
return nil, err
}
defer rows.Close()

trackIds, err := pgx.CollectRows(rows, pgx.RowTo[int32])
if err != nil {
return nil, err
}

return trackIds, nil
}
Loading