From 91fc9dd3860be02bb59996c9bc5b3f1991fd9c38 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Tue, 21 Apr 2026 10:22:55 -0700 Subject: [PATCH] Update new releases playlist query --- api/v1_playlists_new_releases.go | 44 ++++++++++++++------ api/v1_playlists_new_releases_test.go | 58 ++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 13 deletions(-) diff --git a/api/v1_playlists_new_releases.go b/api/v1_playlists_new_releases.go index 6fff6801..a05f05e6 100644 --- a/api/v1_playlists_new_releases.go +++ b/api/v1_playlists_new_releases.go @@ -6,6 +6,8 @@ import ( "github.com/jackc/pgx/v5" ) +const minNewAlbumValidationScore = 5 + type GetPlaylistsNewReleasesParams struct { Limit int `query:"limit" default:"10" validate:"min=1,max=100"` Offset int `query:"offset" default:"0" validate:"min=0"` @@ -19,25 +21,43 @@ func (app *ApiServer) v1PlaylistsNewReleases(c *fiber.Ctx) error { } isAlbum := params.Type == "album" + joinClause := "" + filterClause := "" + orderClause := "COALESCE(p.release_date, p.created_at) DESC, p.playlist_id DESC" + + if isAlbum { + joinClause = "LEFT JOIN aggregate_playlist ap ON p.playlist_id = ap.playlist_id" + filterClause = ` + AND COALESCE(ap.save_count, 0) + COALESCE(ap.repost_count, 0) >= @min_validation_score + ` + orderClause = ` + COALESCE(ap.save_count, 0) + COALESCE(ap.repost_count, 0) DESC, + COALESCE(p.release_date, p.created_at) DESC, + p.playlist_id DESC + ` + } sql := ` - SELECT playlist_id - FROM playlists - WHERE is_delete = false - AND is_current = true - AND is_private = false - AND is_album = @is_album - AND COALESCE(release_date, created_at) <= NOW() - AND COALESCE(release_date, created_at) > NOW() - INTERVAL '90 days' - ORDER BY COALESCE(release_date, created_at) DESC, playlist_id DESC + SELECT p.playlist_id + FROM playlists p + ` + joinClause + ` + WHERE p.is_delete = false + AND p.is_current = true + AND p.is_private = false + AND p.is_album = @is_album + AND COALESCE(p.release_date, p.created_at) <= NOW() + AND COALESCE(p.release_date, p.created_at) > NOW() - INTERVAL '90 days' + ` + filterClause + ` + ORDER BY ` + orderClause + ` LIMIT @limit OFFSET @offset ` rows, err := app.pool.Query(c.Context(), sql, pgx.NamedArgs{ - "is_album": isAlbum, - "limit": params.Limit, - "offset": params.Offset, + "is_album": isAlbum, + "limit": params.Limit, + "offset": params.Offset, + "min_validation_score": minNewAlbumValidationScore, }) if err != nil { return err diff --git a/api/v1_playlists_new_releases_test.go b/api/v1_playlists_new_releases_test.go index 2051c6d7..dc64c401 100644 --- a/api/v1_playlists_new_releases_test.go +++ b/api/v1_playlists_new_releases_test.go @@ -26,7 +26,7 @@ func TestGetPlaylistsNewReleases_Albums(t *testing.T) { "created_at": now.AddDate(0, 0, -1), "playlist_name": "one", }, - // older album + // older album with stronger engagement { "playlist_id": 2, "playlist_owner_id": 1, @@ -36,6 +36,16 @@ func TestGetPlaylistsNewReleases_Albums(t *testing.T) { "created_at": now.AddDate(0, 0, -10), "playlist_name": "two", }, + // recent album without enough social proof (excluded) + { + "playlist_id": 7, + "playlist_owner_id": 1, + "is_album": true, + "is_private": false, + "release_date": now.AddDate(0, 0, -2), + "created_at": now.AddDate(0, 0, -2), + "playlist_name": "seven", + }, // non-album playlist (excluded when type=album) { "playlist_id": 3, @@ -77,6 +87,11 @@ func TestGetPlaylistsNewReleases_Albums(t *testing.T) { "playlist_name": "six", }, }, + "aggregate_playlist": { + {"playlist_id": 1, "is_album": true, "save_count": 3, "repost_count": 2}, + {"playlist_id": 2, "is_album": true, "save_count": 6, "repost_count": 2}, + {"playlist_id": 7, "is_album": true, "save_count": 2, "repost_count": 2}, + }, } database.Seed(app.pool.Replicas[0], fixtures) @@ -86,6 +101,47 @@ func TestGetPlaylistsNewReleases_Albums(t *testing.T) { status, body := testGet(t, app, "/v1/playlists/new-releases?type=album", &resp) assert.Equal(t, 200, status) + jsonAssert(t, body, map[string]any{ + "data.0.id": trashid.MustEncodeHashID(2), + "data.1.id": trashid.MustEncodeHashID(1), + }) + assert.Len(t, resp.Data, 2) +} + +func TestGetPlaylistsNewReleases_PlaylistsRemainChronological(t *testing.T) { + app := emptyTestApp(t) + now := time.Now() + fixtures := database.FixtureMap{ + "users": {{"user_id": 1, "handle_lc": "one"}}, + "playlists": { + { + "playlist_id": 1, + "playlist_owner_id": 1, + "is_album": false, + "is_private": false, + "release_date": now.AddDate(0, 0, -1), + "created_at": now.AddDate(0, 0, -1), + "playlist_name": "newer playlist", + }, + { + "playlist_id": 2, + "playlist_owner_id": 1, + "is_album": false, + "is_private": false, + "release_date": now.AddDate(0, 0, -10), + "created_at": now.AddDate(0, 0, -10), + "playlist_name": "older playlist", + }, + }, + } + database.Seed(app.pool.Replicas[0], fixtures) + + var resp struct { + Data []dbv1.Playlist + } + + status, body := testGet(t, app, "/v1/playlists/new-releases?type=playlist", &resp) + assert.Equal(t, 200, status) jsonAssert(t, body, map[string]any{ "data.0.id": trashid.MustEncodeHashID(1), "data.1.id": trashid.MustEncodeHashID(2),