feat(web/m7-cover-sources): cover-providers API client + query

Adds CoverProvider type + capability union, getCoverProviders /
updateCoverProvider / testCoverProvider against the three new admin
endpoints, plus createCoverProvidersQuery (60s staleTime; refetches
on focus + after mutations rather than polling — settings rarely
change in operator time).

New api.patch helper added to client.ts (the existing helpers were
get/post/put/del; PATCH was missing).

New qk.coverProviders() key follows the existing tuple pattern.
api_key_set: bool replaces echoing the credential. UpdateCoverProviderPatch
uses optional fields so the operator can change just enabled, just
key, or both atomically.

Vitest covers GET path, PATCH path with body, version_bumped
response, POST /test path with empty body, ok=false error response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 13:11:08 -04:00
parent b6632136b7
commit 39a07a280c
4 changed files with 166 additions and 0 deletions
+60
View File
@@ -269,3 +269,63 @@ export function createCoverageQuery() {
refetchInterval: 3_000
});
}
// Cover-art providers ------------------------------------------------------
export type CoverProviderCapability = 'album_cover' | 'artist_thumb' | 'artist_fanart';
export type CoverProvider = {
id: string;
display_name: string;
requires_api_key: boolean;
supports: CoverProviderCapability[];
enabled: boolean;
api_key_set: boolean;
display_order: number;
testable: boolean;
};
export type CoverProvidersResponse = {
providers: CoverProvider[];
sources_version: number;
};
export async function getCoverProviders(): Promise<CoverProvidersResponse> {
return api.get<CoverProvidersResponse>('/api/admin/cover-sources');
}
export type UpdateCoverProviderPatch = {
enabled?: boolean;
api_key?: string;
};
export type UpdateCoverProviderResponse = CoverProvider & {
version_bumped: boolean;
};
export async function updateCoverProvider(
id: string,
patch: UpdateCoverProviderPatch
): Promise<UpdateCoverProviderResponse> {
return api.patch<UpdateCoverProviderResponse>(`/api/admin/cover-sources/${id}`, patch);
}
export type TestCoverProviderResponse = {
ok: boolean;
duration_ms?: number;
error?: string;
};
export async function testCoverProvider(id: string): Promise<TestCoverProviderResponse> {
return api.post<TestCoverProviderResponse>(`/api/admin/cover-sources/${id}/test`, {});
}
// Settings rarely change in operator time. 60s staleTime; refetches
// on window focus + after any mutation rather than polling.
export function createCoverProvidersQuery() {
return createQuery({
queryKey: qk.coverProviders(),
queryFn: getCoverProviders,
staleTime: 60_000
});
}