feat(award): backend V2 commands for /award/page and /award/getAlbums
Backend plumbing for Phase 3 AwardView. New endpoints:
- v2_get_award_page(awardId) → hero + categorized releases
- v2_get_award_albums(awardId, …) → paginated album list
Mirrors Android's AwardDto shape (id, name, image, awarded_at,
magazine, releases, playlists). Follows the label/page pattern for
signing and error handling. Deserializer accepts the V2 generic
{has_more, items} envelope as well as the legacy search-style
{albums: {items, total}} in case Qobuz swaps shapes per account.
cc
This commit is contained in:
7 files changed
+174
-3
No files matched your search
@@ -851,6 +851,34 @@ impl<A: FrontendAdapter + Send + Sync + 'static> QbzCore<A> {
|
|||||||
.map_err(CoreError::Api)
|
.map_err(CoreError::Api)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get award page — hero info + award-winning releases.
|
||||||
|
pub async fn get_award_page(
|
||||||
|
&self,
|
||||||
|
award_id: &str,
|
||||||
|
) -> Result<qbz_models::AwardPageData, CoreError> {
|
||||||
|
let client = self.client.read().await;
|
||||||
|
let client = client.as_ref().ok_or(CoreError::NotInitialized)?;
|
||||||
|
client
|
||||||
|
.get_award_page(award_id)
|
||||||
|
.await
|
||||||
|
.map_err(CoreError::Api)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get paginated albums for an award (/award/getAlbums).
|
||||||
|
pub async fn get_award_albums(
|
||||||
|
&self,
|
||||||
|
award_id: &str,
|
||||||
|
limit: u32,
|
||||||
|
offset: u32,
|
||||||
|
) -> Result<SearchResultsPage<Album>, CoreError> {
|
||||||
|
let client = self.client.read().await;
|
||||||
|
let client = client.as_ref().ok_or(CoreError::NotInitialized)?;
|
||||||
|
client
|
||||||
|
.get_award_albums(award_id, limit, offset)
|
||||||
|
.await
|
||||||
|
.map_err(CoreError::Api)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get label explore (discover more labels)
|
/// Get label explore (discover more labels)
|
||||||
pub async fn get_label_explore(
|
pub async fn get_label_explore(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -71,6 +71,11 @@ pub use types::{
|
|||||||
LabelPageContainer,
|
LabelPageContainer,
|
||||||
LabelPageData,
|
LabelPageData,
|
||||||
LabelPageGenericList,
|
LabelPageGenericList,
|
||||||
|
// Award page types
|
||||||
|
AwardMagazine,
|
||||||
|
AwardPageContainer,
|
||||||
|
AwardPageData,
|
||||||
|
AwardPageGenericList,
|
||||||
// Artist page types
|
// Artist page types
|
||||||
PageArtistAward,
|
PageArtistAward,
|
||||||
PageArtistBiography,
|
PageArtistBiography,
|
||||||
|
|||||||
@@ -1477,6 +1477,66 @@ impl QobuzClient {
|
|||||||
Ok(serde_json::from_value(response)?)
|
Ok(serde_json::from_value(response)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get award page — hero info + categorized award-winning releases.
|
||||||
|
/// Mirrors Android's AwardDto shape from /award/page.
|
||||||
|
pub async fn get_award_page(&self, award_id: &str) -> Result<qbz_models::AwardPageData> {
|
||||||
|
let url = endpoints::build_url(paths::AWARD_PAGE);
|
||||||
|
log::debug!("[API] get_award_page({})", award_id);
|
||||||
|
let response: serde_json::Value = self
|
||||||
|
.signed_get(&url, "awardpage", &[("award_id", award_id.to_string())])
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
Ok(serde_json::from_value(response)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get paginated albums for a single award (/award/getAlbums).
|
||||||
|
pub async fn get_award_albums(
|
||||||
|
&self,
|
||||||
|
award_id: &str,
|
||||||
|
limit: u32,
|
||||||
|
offset: u32,
|
||||||
|
) -> Result<SearchResultsPage<Album>> {
|
||||||
|
let url = endpoints::build_url(paths::AWARD_GET_ALBUMS);
|
||||||
|
log::debug!("[API] get_award_albums({}, limit={}, offset={})", award_id, limit, offset);
|
||||||
|
let response: serde_json::Value = self
|
||||||
|
.signed_get(
|
||||||
|
&url,
|
||||||
|
"awardgetAlbums",
|
||||||
|
&[
|
||||||
|
("award_id", award_id.to_string()),
|
||||||
|
("limit", limit.to_string()),
|
||||||
|
("offset", offset.to_string()),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Response envelope may be V2GenericListDto {has_more,items} or the
|
||||||
|
// search-style {albums: {items,total,offset,limit}}. Try both.
|
||||||
|
if let Some(albums_obj) = response.get("albums") {
|
||||||
|
return Ok(serde_json::from_value(albums_obj.clone())?);
|
||||||
|
}
|
||||||
|
let items_value = response.get("items").cloned().unwrap_or(serde_json::Value::Null);
|
||||||
|
let items: Vec<Album> = serde_json::from_value(items_value).unwrap_or_default();
|
||||||
|
let has_more = response
|
||||||
|
.get("has_more")
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
|
let total_hint = if has_more {
|
||||||
|
offset + items.len() as u32 + 1
|
||||||
|
} else {
|
||||||
|
offset + items.len() as u32
|
||||||
|
};
|
||||||
|
Ok(SearchResultsPage::<Album> {
|
||||||
|
items,
|
||||||
|
total: total_hint,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Get label explore (discover more labels)
|
/// Get label explore (discover more labels)
|
||||||
pub async fn get_label_explore(&self, limit: u32, offset: u32) -> Result<LabelExploreResponse> {
|
pub async fn get_label_explore(&self, limit: u32, offset: u32) -> Result<LabelExploreResponse> {
|
||||||
let url = endpoints::build_url(paths::LABEL_EXPLORE);
|
let url = endpoints::build_url(paths::LABEL_EXPLORE);
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ pub mod paths {
|
|||||||
pub const LABEL_PAGE: &str = "/label/page";
|
pub const LABEL_PAGE: &str = "/label/page";
|
||||||
pub const LABEL_EXPLORE: &str = "/label/explore";
|
pub const LABEL_EXPLORE: &str = "/label/explore";
|
||||||
|
|
||||||
|
// Award
|
||||||
|
pub const AWARD_PAGE: &str = "/award/page";
|
||||||
|
pub const AWARD_GET_ALBUMS: &str = "/award/getAlbums";
|
||||||
|
|
||||||
// Genre
|
// Genre
|
||||||
pub const GENRE_LIST: &str = "/genre/list";
|
pub const GENRE_LIST: &str = "/genre/list";
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use tauri::State;
|
use tauri::State;
|
||||||
|
|
||||||
use qbz_models::{
|
use qbz_models::{
|
||||||
Album, Artist, DiscoverAlbum, DiscoverData, DiscoverPlaylistsResponse, DiscoverResponse,
|
Album, Artist, AwardPageData, DiscoverAlbum, DiscoverData, DiscoverPlaylistsResponse,
|
||||||
GenreInfo, LabelDetail, LabelExploreResponse, LabelPageData, PageArtistResponse,
|
DiscoverResponse, GenreInfo, LabelDetail, LabelExploreResponse, LabelPageData,
|
||||||
PlaylistTag, SearchResultsPage, Track,
|
PageArtistResponse, PlaylistTag, SearchResultsPage, Track,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::artist_blacklist::BlacklistState;
|
use crate::artist_blacklist::BlacklistState;
|
||||||
@@ -442,6 +442,57 @@ pub async fn v2_get_label_page(
|
|||||||
.map_err(RuntimeError::Internal)
|
.map_err(RuntimeError::Internal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get award page (hero info + award-winning releases).
|
||||||
|
#[tauri::command]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub async fn v2_get_award_page(
|
||||||
|
awardId: String,
|
||||||
|
bridge: State<'_, CoreBridgeState>,
|
||||||
|
runtime: State<'_, RuntimeManagerState>,
|
||||||
|
) -> Result<AwardPageData, RuntimeError> {
|
||||||
|
runtime
|
||||||
|
.manager()
|
||||||
|
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
log::info!("[V2] get_award_page: {}", awardId);
|
||||||
|
let bridge = bridge.get().await;
|
||||||
|
bridge
|
||||||
|
.get_award_page(&awardId)
|
||||||
|
.await
|
||||||
|
.map_err(RuntimeError::Internal)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Paginated albums for a single award.
|
||||||
|
#[tauri::command]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub async fn v2_get_award_albums(
|
||||||
|
awardId: String,
|
||||||
|
limit: Option<u32>,
|
||||||
|
offset: Option<u32>,
|
||||||
|
bridge: State<'_, CoreBridgeState>,
|
||||||
|
runtime: State<'_, RuntimeManagerState>,
|
||||||
|
) -> Result<SearchResultsPage<Album>, RuntimeError> {
|
||||||
|
runtime
|
||||||
|
.manager()
|
||||||
|
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let limit = limit.unwrap_or(50);
|
||||||
|
let offset = offset.unwrap_or(0);
|
||||||
|
log::info!(
|
||||||
|
"[V2] get_award_albums: award={}, limit={}, offset={}",
|
||||||
|
awardId,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
);
|
||||||
|
let bridge = bridge.get().await;
|
||||||
|
bridge
|
||||||
|
.get_award_albums(&awardId, limit, offset)
|
||||||
|
.await
|
||||||
|
.map_err(RuntimeError::Internal)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get label explore (discover more labels)
|
/// Get label explore (discover more labels)
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
|||||||
@@ -570,6 +570,27 @@ impl CoreBridge {
|
|||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get award page — hero info + award-winning releases.
|
||||||
|
pub async fn get_award_page(&self, award_id: &str) -> Result<qbz_models::AwardPageData, String> {
|
||||||
|
self.core
|
||||||
|
.get_award_page(award_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get paginated albums for an award (/award/getAlbums).
|
||||||
|
pub async fn get_award_albums(
|
||||||
|
&self,
|
||||||
|
award_id: &str,
|
||||||
|
limit: u32,
|
||||||
|
offset: u32,
|
||||||
|
) -> Result<SearchResultsPage<Album>, String> {
|
||||||
|
self.core
|
||||||
|
.get_award_albums(award_id, limit, offset)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// Get label explore (discover more labels)
|
/// Get label explore (discover more labels)
|
||||||
pub async fn get_label_explore(
|
pub async fn get_label_explore(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1601,6 +1601,8 @@ pub fn run() {
|
|||||||
commands_v2::v2_get_label,
|
commands_v2::v2_get_label,
|
||||||
commands_v2::v2_get_label_page,
|
commands_v2::v2_get_label_page,
|
||||||
commands_v2::v2_get_label_explore,
|
commands_v2::v2_get_label_explore,
|
||||||
|
commands_v2::v2_get_award_page,
|
||||||
|
commands_v2::v2_get_award_albums,
|
||||||
commands_v2::v2_pause_playback,
|
commands_v2::v2_pause_playback,
|
||||||
commands_v2::v2_resume_playback,
|
commands_v2::v2_resume_playback,
|
||||||
commands_v2::v2_stop_playback,
|
commands_v2::v2_stop_playback,
|
||||||
|
|||||||
Reference in new issue
Block a user