feat(web/api): playlists helper + types + query keys (M7 #352 slice 1)
Wire shapes mirror the server's snake_case envelope. URL helpers use the existing apiFetch + ApiError surface so error codes (not_found, not_authorized, bad_request, server_error) propagate via the same copyForCode chain as everything else.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import { apiFetch } from './client';
|
||||
import { qk } from './queries';
|
||||
import type { Playlist, PlaylistDetail } from './types';
|
||||
|
||||
export type ListPlaylistsResponse = {
|
||||
owned: Playlist[];
|
||||
public: Playlist[];
|
||||
};
|
||||
|
||||
export async function listPlaylists(): Promise<ListPlaylistsResponse> {
|
||||
return (await apiFetch('/api/playlists', { method: 'GET' })) as ListPlaylistsResponse;
|
||||
}
|
||||
|
||||
export async function getPlaylist(id: string): Promise<PlaylistDetail> {
|
||||
return (await apiFetch(`/api/playlists/${encodeURIComponent(id)}`, {
|
||||
method: 'GET'
|
||||
})) as PlaylistDetail;
|
||||
}
|
||||
|
||||
export type CreatePlaylistInput = {
|
||||
name: string;
|
||||
description?: string;
|
||||
is_public?: boolean;
|
||||
};
|
||||
|
||||
export async function createPlaylist(input: CreatePlaylistInput): Promise<Playlist> {
|
||||
return (await apiFetch('/api/playlists', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(input)
|
||||
})) as Playlist;
|
||||
}
|
||||
|
||||
export type UpdatePlaylistInput = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
is_public?: boolean;
|
||||
};
|
||||
|
||||
export async function updatePlaylist(id: string, input: UpdatePlaylistInput): Promise<Playlist> {
|
||||
return (await apiFetch(`/api/playlists/${encodeURIComponent(id)}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(input)
|
||||
})) as Playlist;
|
||||
}
|
||||
|
||||
export async function deletePlaylist(id: string): Promise<void> {
|
||||
await apiFetch(`/api/playlists/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
export async function appendTracks(playlistID: string, trackIDs: string[]): Promise<PlaylistDetail> {
|
||||
return (await apiFetch(`/api/playlists/${encodeURIComponent(playlistID)}/tracks`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ track_ids: trackIDs })
|
||||
})) as PlaylistDetail;
|
||||
}
|
||||
|
||||
export async function removePlaylistTrack(playlistID: string, position: number): Promise<PlaylistDetail> {
|
||||
return (await apiFetch(
|
||||
`/api/playlists/${encodeURIComponent(playlistID)}/tracks/${position}`,
|
||||
{ method: 'DELETE' }
|
||||
)) as PlaylistDetail;
|
||||
}
|
||||
|
||||
export async function reorderPlaylist(playlistID: string, orderedPositions: number[]): Promise<PlaylistDetail> {
|
||||
return (await apiFetch(`/api/playlists/${encodeURIComponent(playlistID)}/tracks`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ordered_positions: orderedPositions })
|
||||
})) as PlaylistDetail;
|
||||
}
|
||||
|
||||
export function createPlaylistsQuery() {
|
||||
return createQuery({
|
||||
queryKey: qk.playlists(),
|
||||
queryFn: listPlaylists,
|
||||
staleTime: 30_000
|
||||
});
|
||||
}
|
||||
|
||||
export function createPlaylistQuery(id: string) {
|
||||
return createQuery({
|
||||
queryKey: qk.playlist(id),
|
||||
queryFn: () => getPlaylist(id),
|
||||
staleTime: 30_000
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user