-
Recommendation playlists created for {user.name}
-
- These playlists are ephemeral and will only be available for a month.
- Be sure to save the ones you like to your own playlists !
-
+
+
Created for {user.name}
+
-
{}}
+ {!playlists.length ? (
+
+
+
+ Oh no. Either something’s gone wrong, or you need to submit more
+ listens before we can prepare delicious fresh produce just for
+ you.
+
+
+ ) : (
+
+
+
+ {playlists.map((playlist, index) => {
+ const extension = getPlaylistExtension(playlist);
+ const sourcePatch =
+ extension?.additional_metadata?.algorithm_metadata
+ .source_patch;
+ const isFirstOfType =
+ playlists.findIndex((pl) => {
+ const extension2 = getPlaylistExtension(pl);
+ const sourcePatch2 =
+ extension2?.additional_metadata?.algorithm_metadata
+ .source_patch;
+ return sourcePatch === sourcePatch2;
+ }) === index;
+
+ const info = RecommendationsPage.getPlaylistInfo(
+ playlist,
+ !isFirstOfType
+ );
+ return this.getPlaylistCard(playlist, info);
+ })}
+
+
+
+ )}
+ {selectedPlaylist && (
+
+
+ {selectedPlaylist.track.length > 0 ? (
+
+ this.setState((prevState) => ({
+ selectedPlaylist: {
+ ...prevState.selectedPlaylist!,
+ track: newState,
+ },
+ }))
+ }
+ >
+ {selectedPlaylist.track.map((track: JSPFTrack, index) => {
+ return (
+
+ );
+ })}
+
+ ) : (
+
+
Nothing in this playlist yet
+
+ )}
+
+
+
+ )}
+
);
@@ -108,13 +570,7 @@ document.addEventListener("DOMContentLoaded", () => {
tracesSampleRate: sentry_traces_sample_rate,
});
}
- const {
- playlists,
- user,
- playlist_count: playlistCount,
- pagination_offset: paginationOffset,
- playlists_per_page: playlistsPerPage,
- } = reactProps;
+ const { playlists, user } = reactProps;
const RecommendationsPageWithAlertNotifications = withAlertNotifications(
RecommendationsPage
@@ -123,12 +579,15 @@ document.addEventListener("DOMContentLoaded", () => {
const renderRoot = createRoot(domContainer!);
renderRoot.render(
+
diff --git a/frontend/js/src/utils/types.d.ts b/frontend/js/src/utils/types.d.ts
index e80003c31d..8f59343cf9 100644
--- a/frontend/js/src/utils/types.d.ts
+++ b/frontend/js/src/utils/types.d.ts
@@ -471,12 +471,21 @@ declare type JSPFObject = {
playlist: JSPFPlaylist;
};
+declare type JSPFPlaylistMetadata = {
+ external_urls?: { [key: string]: any };
+ algorithm_metadata: {
+ source_patch: string;
+ };
+ expires_at?: string; // ISO date string
+};
+
declare type JSPFPlaylistExtension = {
- collaborators: string[];
+ collaborators?: string[];
public: boolean;
created_for?: string;
copied_from?: string; // Full ListenBrainz playlist URI
last_modified_at?: string; // ISO date string
+ additional_metadata?: JSPFPlaylistMetadata;
};
declare type JSPFTrackExtension = {
diff --git a/listenbrainz/db/playlist.py b/listenbrainz/db/playlist.py
index 330966b979..0d5daa06cf 100644
--- a/listenbrainz/db/playlist.py
+++ b/listenbrainz/db/playlist.py
@@ -16,6 +16,10 @@
TROI_BOT_USER_ID = 12939
TROI_BOT_DEBUG_USER_ID = 19055
+LISTENBRAINZ_USER_ID = 23944
+
+# These are the recommendation troi patches that we showcase on the recommendations page for each user
+RECOMMENDATION_PATCHES = ('daily-jams', 'weekly-jams', 'weekly-exploration', 'top-discoveries-for-year', 'top-missed-recordings-for-year')
def get_by_mbid(playlist_id: str, load_recordings: bool = True) -> Optional[model_playlist.Playlist]:
@@ -146,6 +150,44 @@ def get_playlists_for_user(user_id: int,
return playlists, count
+def get_recommendation_playlists_for_user(user_id: int):
+ """Get all recommendation playlists that have been created for the user
+
+ Arguments:
+ user_id: The user to find playlists for
+
+ Returns:
+ A list of playlists
+
+ """
+
+ params = {"creator_id": (LISTENBRAINZ_USER_ID, TROI_BOT_USER_ID), "created_for_id": user_id, "patches": RECOMMENDATION_PATCHES}
+ query = sqlalchemy.text(f"""
+ SELECT pl.id
+ , pl.mbid
+ , pl.name
+ , pl.description
+ , pl.creator_id
+ , pl.created
+ , pl.public
+ , pl.created
+ , pl.last_updated
+ , pl.copied_from_id
+ , pl.created_for_id
+ , pl.additional_metadata
+ FROM playlist.playlist pl
+ WHERE additional_metadata->'algorithm_metadata'->>'source_patch' IN :patches
+ AND created_for_id = :created_for_id
+ AND creator_id IN :creator_id
+ ORDER BY pl.created DESC""")
+
+ with ts.engine.connect() as connection:
+ result = connection.execute(query, params)
+ playlists = _playlist_resultset_to_model(connection, result, False)
+
+ return playlists
+
+
def _playlist_resultset_to_model(connection, result, load_recordings):
"""Parse the result of an sql query to get playlists
diff --git a/listenbrainz/tests/integration/test_api.py b/listenbrainz/tests/integration/test_api.py
index 4280f38875..f942e90b8b 100644
--- a/listenbrainz/tests/integration/test_api.py
+++ b/listenbrainz/tests/integration/test_api.py
@@ -1064,3 +1064,10 @@ def test_get_user_services(self):
content_type="application/json"
)
self.assert403(response)
+
+ def test_user_recommendations(self):
+ r = self.client.get(url_for("api_v1.user_recommendations", playlist_user_name=self.followed_user["musicbrainz_id"]))
+ self.assert200(r)
+
+ r = self.client.get(url_for("api_v1.user_recommendations", playlist_user_name="does not exist"))
+ self.assert404(r)
diff --git a/listenbrainz/webserver/templates/playlists/recommendations.html b/listenbrainz/webserver/templates/playlists/recommendations.html
index ba9c28f90d..8e07e6d151 100644
--- a/listenbrainz/webserver/templates/playlists/recommendations.html
+++ b/listenbrainz/webserver/templates/playlists/recommendations.html
@@ -1,11 +1,12 @@
{% extends 'user/base.html' %}
{%- block title -%}
+ Created for
{% if current_user.musicbrainz_id == user.musicbrainz_id %}
- Your
+ you
{% else %}
- {{ user.musicbrainz_id }}'s
+ {{ user.musicbrainz_id }}
{%- endif -%}
- Recommendations - ListenBrainz
+ - ListenBrainz
{%- endblock-%}
diff --git a/listenbrainz/webserver/templates/user/base.html b/listenbrainz/webserver/templates/user/base.html
index ab4c54ec87..6f0baa3e81 100644
--- a/listenbrainz/webserver/templates/user/base.html
+++ b/listenbrainz/webserver/templates/user/base.html
@@ -26,7 +26,7 @@
Stats
Taste
Playlists
- Recommendations
+ Created for you
diff --git a/listenbrainz/webserver/views/api.py b/listenbrainz/webserver/views/api.py
index f247cdd4e0..4a433aa8e8 100644
--- a/listenbrainz/webserver/views/api.py
+++ b/listenbrainz/webserver/views/api.py
@@ -641,6 +641,29 @@ def get_playlists_collaborated_on_for_user(playlist_user_name):
return jsonify(serialize_playlists(playlists, playlist_count, count, offset))
+@api_bp.route("/user/