From 2f3a3e8080f80fd52b47047bccf93c037d9f5b9e Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Wed, 23 Apr 2025 17:00:59 -0700 Subject: [PATCH 1/3] Add missing playlist fields --- api/dbv1/full_playlists.go | 90 +++++++++++++++++++----------- api/dbv1/get_playlists.sql.go | 13 ++++- api/dbv1/queries/get_playlists.sql | 7 ++- api/response_helpers.go | 6 +- 4 files changed, 76 insertions(+), 40 deletions(-) diff --git a/api/dbv1/full_playlists.go b/api/dbv1/full_playlists.go index c35f073c..06f4459c 100644 --- a/api/dbv1/full_playlists.go +++ b/api/dbv1/full_playlists.go @@ -2,6 +2,7 @@ package dbv1 import ( "context" + "fmt" "bridgerton.audius.co/trashid" "github.com/jackc/pgx/v5/pgtype" @@ -10,12 +11,13 @@ import ( type FullPlaylist struct { GetPlaylistsRow - ID string `json:"id"` - Artwork *SquareImage `json:"artwork"` - UserID trashid.HashId `json:"user_id"` - User FullUser `json:"user"` - Tracks []FullTrack `json:"tracks"` - Access Access `json:"access"` + ID string `json:"id"` + Artwork *SquareImage `json:"artwork"` + UserID trashid.HashId `json:"user_id"` + User FullUser `json:"user"` + Tracks []FullTrack `json:"tracks"` + Access Access `json:"access"` + Permalink string `json:"permalink"` FolloweeReposts []*FolloweeRepost `json:"followee_reposts"` FolloweeFavorites []*FolloweeFavorite `json:"followee_favorites"` @@ -89,6 +91,13 @@ func (q *Queries) FullPlaylistsKeyed(ctx context.Context, arg GetPlaylistsParams streamAccess := q.GetPlaylistAccess(ctx, arg.MyID.(int32), playlist.StreamConditions, &playlist, &user) downloadAccess := streamAccess + var playlistType string + if playlist.IsAlbum { + playlistType = "album" + } else { + playlistType = "playlist" + } + playlistMap[playlist.PlaylistID] = FullPlaylist{ GetPlaylistsRow: playlist, ID: id, @@ -99,6 +108,7 @@ func (q *Queries) FullPlaylistsKeyed(ctx context.Context, arg GetPlaylistsParams FolloweeFavorites: fullFolloweeFavorites(playlist.FolloweeFavorites), FolloweeReposts: fullFolloweeReposts(playlist.FolloweeReposts), PlaylistContents: fullPlaylistContents, + Permalink: fmt.Sprintf("/%s/%s/%s", user.Handle.String, playlistType, playlist.Slug.String), AddedTimestamps: fullPlaylistContents, Access: Access{ Stream: streamAccess, @@ -129,20 +139,22 @@ func (q *Queries) FullPlaylists(ctx context.Context, arg GetPlaylistsParams) ([] } type MinPlaylist struct { - ID string `json:"id"` - PlaylistName pgtype.Text `json:"playlist_name"` - PlaylistOwnerID int32 `json:"playlist_owner_id"` - PlaylistID int32 `json:"playlist_id"` - Artwork *SquareImage `json:"artwork"` - Description *string `json:"description"` - PlaylistContents interface{} `json:"playlist_contents"` - IsAlbum bool `json:"is_album"` - IsPrivate bool `json:"is_private"` - FavoriteCount int32 `json:"favorite_count"` - RepostCount int32 `json:"repost_count"` - UserID trashid.HashId `json:"user_id"` - User MinUser `json:"user"` - Tracks []MinTrack `json:"tracks"` + ID string `json:"id"` + PlaylistName pgtype.Text `json:"playlist_name"` + Artwork *SquareImage `json:"artwork"` + Access Access `json:"access"` + Description string `json:"description"` + IsImageAutogenerated bool `json:"is_image_autogenerated"` + Upc string `json:"upc"` + DdexApp string `json:"ddex_app"` + PlaylistContents interface{} `json:"playlist_contents"` + TrackCount int32 `json:"track_count"` + TotalPlayCount int64 `json:"total_play_count"` + IsAlbum bool `json:"is_album"` + FavoriteCount int32 `json:"favorite_count"` + RepostCount int32 `json:"repost_count"` + User MinUser `json:"user"` + Permalink string `json:"permalink"` } func ToMinPlaylist(fullPlaylist FullPlaylist) MinPlaylist { @@ -152,20 +164,30 @@ func ToMinPlaylist(fullPlaylist FullPlaylist) MinPlaylist { } return MinPlaylist{ - ID: fullPlaylist.ID, - PlaylistName: fullPlaylist.PlaylistName, - PlaylistOwnerID: fullPlaylist.PlaylistOwnerID, - PlaylistID: fullPlaylist.PlaylistID, - Artwork: fullPlaylist.Artwork, - PlaylistContents: fullPlaylist.PlaylistContents, - Description: nil, - IsAlbum: false, - IsPrivate: false, - FavoriteCount: 0, - RepostCount: 0, - UserID: fullPlaylist.UserID, - User: ToMinUser(fullPlaylist.User), - Tracks: minTracks, + ID: fullPlaylist.ID, + PlaylistName: fullPlaylist.PlaylistName, + Artwork: fullPlaylist.Artwork, + Access: fullPlaylist.Access, + Upc: fullPlaylist.Upc.String, + DdexApp: fullPlaylist.DdexApp.String, + PlaylistContents: fullPlaylist.PlaylistContents, + Description: fullPlaylist.Description.String, + IsImageAutogenerated: fullPlaylist.IsImageAutogenerated, + IsAlbum: fullPlaylist.IsAlbum, + TrackCount: int32(len(fullPlaylist.Tracks)), + TotalPlayCount: func() int64 { + var total int64 + for _, track := range fullPlaylist.Tracks { + if track.PlayCount.Valid { + total += track.PlayCount.Int64 + } + } + return total + }(), + FavoriteCount: int32(fullPlaylist.FavoriteCount.Int32), + RepostCount: int32(fullPlaylist.RepostCount.Int32), + User: ToMinUser(fullPlaylist.User), + Permalink: fullPlaylist.Permalink, } } diff --git a/api/dbv1/get_playlists.sql.go b/api/dbv1/get_playlists.sql.go index bbba9af5..c694116c 100644 --- a/api/dbv1/get_playlists.sql.go +++ b/api/dbv1/get_playlists.sql.go @@ -17,7 +17,6 @@ const getPlaylists = `-- name: GetPlaylists :many SELECT -- artwork p.description, - -- permalink -- id p.is_album, p.is_delete, @@ -26,6 +25,8 @@ SELECT p.is_scheduled_release, p.is_stream_gated, p.stream_conditions, + p.upc, + p.ddex_app, -- is_streamable, coalesce(playlist_image_sizes_multihash, playlist_image_multihash) as artwork, @@ -35,6 +36,7 @@ SELECT p.playlist_id, p.playlist_owner_id, p.playlist_contents, + playlist_routes.slug as slug, p.blocknumber, @@ -111,8 +113,9 @@ SELECT FROM playlists p JOIN aggregate_playlist using (playlist_id) +LEFT JOIN playlist_routes on p.playlist_id = playlist_routes.playlist_id and playlist_routes.is_current = true WHERE is_delete = false - and playlist_id = ANY($2::int[]) + and p.playlist_id = ANY($2::int[]) ` type GetPlaylistsParams struct { @@ -129,11 +132,14 @@ type GetPlaylistsRow struct { IsScheduledRelease bool `json:"is_scheduled_release"` IsStreamGated pgtype.Bool `json:"is_stream_gated"` StreamConditions *AccessGate `json:"stream_conditions"` + Upc pgtype.Text `json:"upc"` + DdexApp pgtype.Text `json:"ddex_app"` Artwork pgtype.Text `json:"artwork"` PlaylistName pgtype.Text `json:"playlist_name"` PlaylistID int32 `json:"playlist_id"` PlaylistOwnerID int32 `json:"playlist_owner_id"` PlaylistContents PlaylistContents `json:"playlist_contents"` + Slug pgtype.Text `json:"slug"` Blocknumber pgtype.Int4 `json:"blocknumber"` RepostCount pgtype.Int4 `json:"repost_count"` FavoriteCount pgtype.Int4 `json:"favorite_count"` @@ -164,11 +170,14 @@ func (q *Queries) GetPlaylists(ctx context.Context, arg GetPlaylistsParams) ([]G &i.IsScheduledRelease, &i.IsStreamGated, &i.StreamConditions, + &i.Upc, + &i.DdexApp, &i.Artwork, &i.PlaylistName, &i.PlaylistID, &i.PlaylistOwnerID, &i.PlaylistContents, + &i.Slug, &i.Blocknumber, &i.RepostCount, &i.FavoriteCount, diff --git a/api/dbv1/queries/get_playlists.sql b/api/dbv1/queries/get_playlists.sql index f5b9ce53..7dfe8400 100644 --- a/api/dbv1/queries/get_playlists.sql +++ b/api/dbv1/queries/get_playlists.sql @@ -2,7 +2,6 @@ SELECT -- artwork p.description, - -- permalink -- id p.is_album, p.is_delete, @@ -11,6 +10,8 @@ SELECT p.is_scheduled_release, p.is_stream_gated, p.stream_conditions, + p.upc, + p.ddex_app, -- is_streamable, coalesce(playlist_image_sizes_multihash, playlist_image_multihash) as artwork, @@ -20,6 +21,7 @@ SELECT p.playlist_id, p.playlist_owner_id, p.playlist_contents, + playlist_routes.slug as slug, p.blocknumber, @@ -96,6 +98,7 @@ SELECT FROM playlists p JOIN aggregate_playlist using (playlist_id) +LEFT JOIN playlist_routes on p.playlist_id = playlist_routes.playlist_id and playlist_routes.is_current = true WHERE is_delete = false - and playlist_id = ANY(@ids::int[]) + and p.playlist_id = ANY(@ids::int[]) ; diff --git a/api/response_helpers.go b/api/response_helpers.go index 1a98ef42..62e6db51 100644 --- a/api/response_helpers.go +++ b/api/response_helpers.go @@ -27,14 +27,16 @@ func v1UsersResponse(c *fiber.Ctx, users []dbv1.FullUser) error { }) } +// Note: playlist response returned an array even though it's a single playlist +// Done for backwards compatibility. Would be nice to get rid of this. func v1PlaylistResponse(c *fiber.Ctx, playlist dbv1.FullPlaylist) error { if c.Locals("isFull").(bool) { return c.JSON(fiber.Map{ - "data": playlist, + "data": []dbv1.FullPlaylist{playlist}, }) } return c.JSON(fiber.Map{ - "data": dbv1.ToMinPlaylist(playlist), + "data": []dbv1.MinPlaylist{dbv1.ToMinPlaylist(playlist)}, }) } From d37082bbab6ef04057d158c57782280d0128eb4d Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Wed, 23 Apr 2025 17:17:21 -0700 Subject: [PATCH 2/3] Fix test --- api/v1_playlist_test.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/api/v1_playlist_test.go b/api/v1_playlist_test.go index 4e779a78..a5977a23 100644 --- a/api/v1_playlist_test.go +++ b/api/v1_playlist_test.go @@ -9,33 +9,34 @@ import ( func TestGetPlaylist(t *testing.T) { var playlistResponse struct { - Data dbv1.FullPlaylist + Data []dbv1.FullPlaylist } status, body := testGet(t, "/v1/full/playlists/7eP5n", &playlistResponse) assert.Equal(t, 200, status) + // Also check with jsonAssert for debugging jsonAssert(t, body, map[string]string{ - "data.id": "7eP5n", - "data.playlist_name": "First", + "data.0.id": "7eP5n", + "data.0.playlist_name": "First", }) } func TestGetPlaylistFollowDownloadAccess(t *testing.T) { var playlistResponse struct { - Data dbv1.FullPlaylist + Data []dbv1.FullPlaylist } // No access _, body1 := testGet(t, "/v1/full/playlists/ML51L", &playlistResponse) jsonAssert(t, body1, map[string]string{ - "data.playlist_name": "Follow Gated Stream", - "data.access": `{"stream":false,"download":false}`, + "data.0.playlist_name": "Follow Gated Stream", + "data.0.access": `{"stream":false,"download":false}`, }) // With access _, body2 := testGet(t, "/v1/full/playlists/ML51L?user_id=ELKzn", &playlistResponse) jsonAssert(t, body2, map[string]string{ - "data.playlist_name": "Follow Gated Stream", - "data.access": `{"stream":true,"download":true}`, + "data.0.playlist_name": "Follow Gated Stream", + "data.0.access": `{"stream":true,"download":true}`, }) } From 31663c09d35f90db4616509838905fdd1c5ee2ea Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Wed, 23 Apr 2025 17:19:37 -0700 Subject: [PATCH 3/3] Remove comment --- api/v1_playlist_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/v1_playlist_test.go b/api/v1_playlist_test.go index a5977a23..5fc3ed82 100644 --- a/api/v1_playlist_test.go +++ b/api/v1_playlist_test.go @@ -15,7 +15,6 @@ func TestGetPlaylist(t *testing.T) { status, body := testGet(t, "/v1/full/playlists/7eP5n", &playlistResponse) assert.Equal(t, 200, status) - // Also check with jsonAssert for debugging jsonAssert(t, body, map[string]string{ "data.0.id": "7eP5n", "data.0.playlist_name": "First",