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:
vicrodh committed 2026-04-14 12:12:01 -06:00
1 parent f0b4499dab
commit fe3213ece5
7 files changed
+174 -3

No files matched your search

+54 -3
View File
@@ -1,9 +1,9 @@
use tauri::State;
use qbz_models::{
Album, Artist, DiscoverAlbum, DiscoverData, DiscoverPlaylistsResponse, DiscoverResponse,
GenreInfo, LabelDetail, LabelExploreResponse, LabelPageData, PageArtistResponse,
PlaylistTag, SearchResultsPage, Track,
Album, Artist, AwardPageData, DiscoverAlbum, DiscoverData, DiscoverPlaylistsResponse,
DiscoverResponse, GenreInfo, LabelDetail, LabelExploreResponse, LabelPageData,
PageArtistResponse, PlaylistTag, SearchResultsPage, Track,
};
use crate::artist_blacklist::BlacklistState;
@@ -442,6 +442,57 @@ pub async fn v2_get_label_page(
.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)
#[tauri::command]
#[allow(non_snake_case)]
+21
View File
@@ -570,6 +570,27 @@ impl CoreBridge {
.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)
pub async fn get_label_explore(
&self,
+2
View File
@@ -1601,6 +1601,8 @@ pub fn run() {
commands_v2::v2_get_label,
commands_v2::v2_get_label_page,
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_resume_playback,
commands_v2::v2_stop_playback,