3a424c6f73
Adds CoverageRollup type, getCoverageRollup() against /api/admin/library/coverage, and createCoverageQuery() with the same 3s/30s pacing used by scan-status. New qk.coverage() key follows the existing zero-arg tuple pattern. Vitest covers the GET path and the empty-library zero-state plus the with_art + pending + settled = total invariant.
137 lines
5.2 KiB
TypeScript
137 lines
5.2 KiB
TypeScript
import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query';
|
|
import { api } from './client';
|
|
import type { ArtistRef, ArtistDetail, AlbumDetail, Page, SearchResponse } from './types';
|
|
|
|
export type ArtistSort = 'alpha' | 'newest';
|
|
export const ARTIST_PAGE_SIZE = 50;
|
|
export const SEARCH_SUMMARY_LIMIT = 10;
|
|
export const SEARCH_FACET_PAGE_SIZE = 50;
|
|
|
|
export const qk = {
|
|
artists: (sort: ArtistSort) => ['artists', { sort }] as const,
|
|
artist: (id: string) => ['artist', id] as const,
|
|
album: (id: string) => ['album', id] as const,
|
|
search: (q: string) => ['search', { q }] as const,
|
|
searchArtists: (q: string) => ['searchArtists', { q }] as const,
|
|
searchAlbums: (q: string) => ['searchAlbums', { q }] as const,
|
|
searchTracks: (q: string) => ['searchTracks', { q }] as const,
|
|
likedIds: () => ['likedIds'] as const,
|
|
likedTracks: () => ['likedTracks'] as const,
|
|
likedAlbums: () => ['likedAlbums'] as const,
|
|
likedArtists: () => ['likedArtists'] as const,
|
|
history: () => ['history'] as const,
|
|
// Lidarr / requests / admin.
|
|
lidarrSearch: (q: string, kind: string) =>
|
|
['lidarrSearch', { q, kind }] as const,
|
|
myRequests: () => ['myRequests'] as const,
|
|
lidarrConfig: () => ['lidarrConfig'] as const,
|
|
lidarrQualityProfiles: () => ['lidarrQualityProfiles'] as const,
|
|
lidarrMetadataProfiles: () => ['lidarrMetadataProfiles'] as const,
|
|
lidarrRootFolders: () => ['lidarrRootFolders'] as const,
|
|
adminRequests: (status?: string) =>
|
|
['adminRequests', { status: status ?? 'all' }] as const,
|
|
myQuarantine: () => ['myQuarantine'] as const,
|
|
adminQuarantine: () => ['adminQuarantine'] as const,
|
|
adminQuarantineActions: (limit?: number) =>
|
|
['adminQuarantineActions', { limit: limit ?? 50 }] as const,
|
|
scanStatus: () => ['scanStatus'] as const,
|
|
coverage: () => ['coverage'] as const,
|
|
suggestions: (limit?: number) =>
|
|
['suggestions', { limit: limit ?? 12 }] as const,
|
|
home: () => ['home'] as const,
|
|
albumsAlpha: () => ['albumsAlpha'] as const,
|
|
artistTracks: (artistId: string) => ['artistTracks', artistId] as const,
|
|
playlists: (kind?: 'user' | 'system' | 'all') =>
|
|
['playlists', { kind: kind ?? 'user' }] as const,
|
|
playlist: (id: string) => ['playlist', id] as const,
|
|
systemPlaylistsStatus: () => ['systemPlaylistsStatus'] as const,
|
|
};
|
|
|
|
export function createArtistsQuery(sort: ArtistSort) {
|
|
return createInfiniteQuery({
|
|
queryKey: qk.artists(sort),
|
|
queryFn: ({ pageParam = 0 }) =>
|
|
api.get<Page<ArtistRef>>(
|
|
`/api/artists?sort=${sort}&limit=${ARTIST_PAGE_SIZE}&offset=${pageParam}`
|
|
),
|
|
initialPageParam: 0,
|
|
getNextPageParam: (last) => {
|
|
const loaded = last.offset + last.items.length;
|
|
return loaded >= last.total ? undefined : loaded;
|
|
},
|
|
});
|
|
}
|
|
|
|
export function createArtistQuery(id: string) {
|
|
return createQuery({
|
|
queryKey: qk.artist(id),
|
|
queryFn: () => api.get<ArtistDetail>(`/api/artists/${id}`),
|
|
});
|
|
}
|
|
|
|
export function createAlbumQuery(id: string) {
|
|
return createQuery({
|
|
queryKey: qk.album(id),
|
|
queryFn: () => api.get<AlbumDetail>(`/api/albums/${id}`),
|
|
});
|
|
}
|
|
|
|
export function createSearchQuery(q: string) {
|
|
return createQuery({
|
|
queryKey: qk.search(q),
|
|
queryFn: () =>
|
|
api.get<SearchResponse>(
|
|
`/api/search?q=${encodeURIComponent(q)}&limit=${SEARCH_SUMMARY_LIMIT}`
|
|
),
|
|
enabled: q.length > 0
|
|
});
|
|
}
|
|
|
|
export function createSearchArtistsInfiniteQuery(q: string) {
|
|
return createInfiniteQuery({
|
|
queryKey: qk.searchArtists(q),
|
|
queryFn: ({ pageParam = 0 }) =>
|
|
api.get<SearchResponse>(
|
|
`/api/search?q=${encodeURIComponent(q)}&limit=${SEARCH_FACET_PAGE_SIZE}&offset=${pageParam}`
|
|
).then((r) => r.artists),
|
|
initialPageParam: 0,
|
|
getNextPageParam: (last) => {
|
|
const loaded = last.offset + last.items.length;
|
|
return loaded >= last.total ? undefined : loaded;
|
|
},
|
|
enabled: q.length > 0
|
|
});
|
|
}
|
|
|
|
export function createSearchAlbumsInfiniteQuery(q: string) {
|
|
return createInfiniteQuery({
|
|
queryKey: qk.searchAlbums(q),
|
|
queryFn: ({ pageParam = 0 }) =>
|
|
api.get<SearchResponse>(
|
|
`/api/search?q=${encodeURIComponent(q)}&limit=${SEARCH_FACET_PAGE_SIZE}&offset=${pageParam}`
|
|
).then((r) => r.albums),
|
|
initialPageParam: 0,
|
|
getNextPageParam: (last) => {
|
|
const loaded = last.offset + last.items.length;
|
|
return loaded >= last.total ? undefined : loaded;
|
|
},
|
|
enabled: q.length > 0
|
|
});
|
|
}
|
|
|
|
export function createSearchTracksInfiniteQuery(q: string) {
|
|
return createInfiniteQuery({
|
|
queryKey: qk.searchTracks(q),
|
|
queryFn: ({ pageParam = 0 }) =>
|
|
api.get<SearchResponse>(
|
|
`/api/search?q=${encodeURIComponent(q)}&limit=${SEARCH_FACET_PAGE_SIZE}&offset=${pageParam}`
|
|
).then((r) => r.tracks),
|
|
initialPageParam: 0,
|
|
getNextPageParam: (last) => {
|
|
const loaded = last.offset + last.items.length;
|
|
return loaded >= last.total ? undefined : loaded;
|
|
},
|
|
enabled: q.length > 0
|
|
});
|
|
}
|