diff --git a/src/api/endpoints/streaming.ts b/src/api/endpoints/streaming.ts index 630544a..8a8e9c3 100644 --- a/src/api/endpoints/streaming.ts +++ b/src/api/endpoints/streaming.ts @@ -32,3 +32,18 @@ export function getTrackCoverUrl( const base = getApiBaseUrl(); return `${base}/tracks/${trackId}/cover?token=${encodeURIComponent(token)}`; } + +/** + * Cover image URL for an album, served by `GET /albums/{id}/cover`. Same + * `?token=` rationale as the track cover. Returns undefined when the album has + * no cover (so callers fall back to generated tile art). + */ +export function getAlbumCoverUrl( + albumId: string, + token: string, + hasCover: boolean, +): string | undefined { + if (!hasCover) return undefined; + const base = getApiBaseUrl(); + return `${base}/albums/${albumId}/cover?token=${encodeURIComponent(token)}`; +} diff --git a/src/api/mappers.ts b/src/api/mappers.ts index a4e2b63..f4845bd 100644 --- a/src/api/mappers.ts +++ b/src/api/mappers.ts @@ -95,6 +95,7 @@ export interface RawAlbum { artist_name: string; year: number | null; track_count: number; + has_cover: boolean; created_at: string; } @@ -171,7 +172,10 @@ export const toAlbum = (r: RawAlbum): Album => ({ title: r.title, artistId: r.artist_id, artistName: r.artist_name, + // The album record carries no cover *URL*; `hasCover` says one exists, and the + // URL (which needs `?token=`) is built in components via `getAlbumCoverUrl`. artUrl: undefined, + hasCover: r.has_cover, year: r.year ?? undefined, trackCount: r.track_count, // AlbumOut has no aggregate duration; computed client-side from tracks when diff --git a/src/api/types.ts b/src/api/types.ts index 8feea2e..0e3194d 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -44,6 +44,8 @@ export interface Album { artistId: string; artistName: string; artUrl?: string; + /** Whether the album has cover art served by `GET /albums/{id}/cover`. */ + hasCover: boolean; year?: number; trackCount: number; totalDurationMs: number; diff --git a/src/features/artist-detail/ArtistDetailPage.tsx b/src/features/artist-detail/ArtistDetailPage.tsx index 290b3ec..7241c90 100644 --- a/src/features/artist-detail/ArtistDetailPage.tsx +++ b/src/features/artist-detail/ArtistDetailPage.tsx @@ -20,7 +20,7 @@ import { } from '../../store/selectors/localLibrary'; import { setQueue } from '../../store/slices/queue'; import { formatDuration } from '../../lib/format'; -import { getCoverUrl } from '../../api/endpoints/streaming'; +import { getCoverUrl, getAlbumCoverUrl } from '../../api/endpoints/streaming'; import type { Album } from '../../api/types'; export function ArtistDetailPage() { @@ -251,7 +251,10 @@ export function ArtistDetailPage() { function AlbumCard({ album, onClick }: { album: Album; onClick: () => void }) { const { t } = useTranslation(); - const artUrl = getCoverUrl(album.artUrl); + const token = useAppSelector((s) => s.auth.accessToken); + const artUrl = + getCoverUrl(album.artUrl) ?? + (token ? getAlbumCoverUrl(album.id, token, album.hasCover) : undefined); return ( void }) { const { t } = useTranslation(); - const artUrl = getCoverUrl(album.artUrl); + const token = useAppSelector((s) => s.auth.accessToken); + // The album record has no cover URL; build one from `hasCover` (served by + // GET /albums/{id}/cover, token in the query — can't send a header). + const artUrl = + getCoverUrl(album.artUrl) ?? + (token ? getAlbumCoverUrl(album.id, token, album.hasCover) : undefined); return (