top songs flow

This commit is contained in:
Connor Johnstone
2026-03-25 15:48:37 -04:00
parent 1a478dea8e
commit 6e156037e6
3 changed files with 255 additions and 1 deletions
+84 -1
View File
@@ -2,7 +2,7 @@ use actix_session::Session;
use actix_web::{HttpResponse, web};
use serde::{Deserialize, Serialize};
use shanty_data::{ArtistBioFetcher, ArtistImageFetcher, MetadataFetcher};
use shanty_data::{ArtistBioFetcher, ArtistImageFetcher, MetadataFetcher, SimilarArtistFetcher};
use shanty_db::entities::wanted_item::WantedStatus;
use shanty_db::queries;
use shanty_search::SearchProvider;
@@ -383,6 +383,87 @@ pub async fn enrich_artist(
.await;
tracing::debug!(mbid = %mbid, has_photo = artist_photo.is_some(), has_bio = artist_bio.is_some(), has_banner = artist_banner.is_some(), "artist enrichment data");
// Fetch top songs from Last.fm (cached 7 days)
let top_songs: Vec<serde_json::Value> = if let Some(ref key) = lastfm_api_key {
let cache_key = format!("lastfm_top_tracks:{mbid}");
let cached: Option<Vec<shanty_data::PopularTrack>> =
if let Ok(Some(json)) = queries::cache::get(state.db.conn(), &cache_key).await {
serde_json::from_str(&json).ok()
} else {
None
};
let tracks = if let Some(cached) = cached {
cached
} else {
match shanty_data::LastFmSimilarFetcher::new(key.clone()) {
Ok(fetcher) => {
match fetcher.get_top_tracks(&artist.name, Some(&mbid)).await {
Ok(tracks) => {
// Cache for 7 days
if let Ok(json) = serde_json::to_string(&tracks) {
let _ = queries::cache::set(
state.db.conn(),
&cache_key,
"lastfm",
&json,
7 * 86400,
)
.await;
// Also persist on artist record
if let Some(local_id) = id {
let _ = queries::artists::update_top_songs(
state.db.conn(),
local_id,
&json,
)
.await;
}
}
tracks
}
Err(e) => {
tracing::warn!(error = %e, "failed to fetch Last.fm top tracks");
vec![]
}
}
}
Err(e) => {
tracing::warn!(error = %e, "failed to create Last.fm fetcher");
vec![]
}
}
};
// Cross-reference with wanted items to add status
let all_wanted = queries::wanted::list(state.db.conn(), None, None).await?;
tracks
.iter()
.map(|t| {
let status = t.mbid.as_deref().and_then(|track_mbid| {
all_wanted
.iter()
.find(|w| w.musicbrainz_id.as_deref() == Some(track_mbid))
.map(|w| match w.status {
WantedStatus::Owned => "owned",
WantedStatus::Downloaded => "downloaded",
WantedStatus::Wanted => "wanted",
WantedStatus::Available => "available",
})
});
serde_json::json!({
"name": t.name,
"playcount": t.playcount,
"mbid": t.mbid,
"status": status,
})
})
.collect()
} else {
vec![]
};
let lastfm_available = lastfm_api_key.is_some();
// Fetch release groups and split into primary vs featured
let all_release_groups = state
.search
@@ -656,6 +737,8 @@ pub async fn enrich_artist(
"artist_photo": artist_photo,
"artist_bio": artist_bio,
"artist_banner": artist_banner,
"top_songs": top_songs,
"lastfm_available": lastfm_available,
}))
}