feat(web): API client for /api/discover/suggestions
This commit is contained in:
@@ -32,6 +32,8 @@ export const qk = {
|
|||||||
adminQuarantine: () => ['adminQuarantine'] as const,
|
adminQuarantine: () => ['adminQuarantine'] as const,
|
||||||
adminQuarantineActions: (limit?: number) =>
|
adminQuarantineActions: (limit?: number) =>
|
||||||
['adminQuarantineActions', { limit: limit ?? 50 }] as const,
|
['adminQuarantineActions', { limit: limit ?? 50 }] as const,
|
||||||
|
suggestions: (limit?: number) =>
|
||||||
|
['suggestions', { limit: limit ?? 12 }] as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createArtistsQuery(sort: ArtistSort) {
|
export function createArtistsQuery(sort: ArtistSort) {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('./client', () => ({
|
||||||
|
api: { get: vi.fn() }
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { listSuggestions } from './suggestions';
|
||||||
|
import { qk } from './queries';
|
||||||
|
import { api } from './client';
|
||||||
|
import type { ArtistSuggestion } from './types';
|
||||||
|
|
||||||
|
afterEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
describe('suggestions client', () => {
|
||||||
|
test('listSuggestions hits the right URL with default limit', async () => {
|
||||||
|
const fixture: ArtistSuggestion[] = [
|
||||||
|
{
|
||||||
|
mbid: 'm1',
|
||||||
|
name: 'Outsider',
|
||||||
|
score: 1.5,
|
||||||
|
attribution: [
|
||||||
|
{ artist_id: 'a1', name: 'Seed', contribution: 0.9, is_liked: true, play_count: 0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
(api.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce(fixture);
|
||||||
|
const got = await listSuggestions();
|
||||||
|
expect(api.get).toHaveBeenCalledWith('/api/discover/suggestions?limit=12');
|
||||||
|
expect(got).toEqual(fixture);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('listSuggestions honors a custom limit', async () => {
|
||||||
|
(api.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce([]);
|
||||||
|
await listSuggestions(20);
|
||||||
|
expect(api.get).toHaveBeenCalledWith('/api/discover/suggestions?limit=20');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('qk.suggestions key shape', () => {
|
||||||
|
expect(qk.suggestions()).toEqual(['suggestions', { limit: 12 }]);
|
||||||
|
expect(qk.suggestions(20)).toEqual(['suggestions', { limit: 20 }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { createQuery } from '@tanstack/svelte-query';
|
||||||
|
import { api } from './client';
|
||||||
|
import { qk } from './queries';
|
||||||
|
import type { ArtistSuggestion } from './types';
|
||||||
|
|
||||||
|
export async function listSuggestions(limit = 12): Promise<ArtistSuggestion[]> {
|
||||||
|
return api.get<ArtistSuggestion[]>(`/api/discover/suggestions?limit=${limit}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSuggestionsQuery(limit = 12) {
|
||||||
|
return createQuery({
|
||||||
|
queryKey: qk.suggestions(limit),
|
||||||
|
queryFn: () => listSuggestions(limit),
|
||||||
|
staleTime: 5 * 60_000 // 5 minutes — see M5c spec §5
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -221,3 +221,18 @@ export type ActionResult = {
|
|||||||
affected_users: number;
|
affected_users: number;
|
||||||
deleted_track_count?: number;
|
deleted_track_count?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SeedContribution = {
|
||||||
|
artist_id: string;
|
||||||
|
name: string;
|
||||||
|
contribution: number;
|
||||||
|
is_liked: boolean;
|
||||||
|
play_count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ArtistSuggestion = {
|
||||||
|
mbid: string;
|
||||||
|
name: string;
|
||||||
|
score: number;
|
||||||
|
attribution: SeedContribution[]; // up to 3 entries, ordered by contribution DESC
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user