refactor(web): migrate playlists.ts to api.* wrapper (#375)

Drop 9 hand-rolled apiFetch calls with redundant Content-Type
headers + manual JSON.stringify. The api.* wrapper in client.ts
already handles both. Identical wire shape; existing tests cover
all endpoints.

Also makes api.del generic so DELETE endpoints with typed returns
(playlist track removal) can use the wrapper instead of raw
apiFetch. Default `T = null` preserves existing callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 15:30:34 -04:00
parent 714039bae5
commit e8eff1baa1
2 changed files with 21 additions and 39 deletions
+2 -2
View File
@@ -58,6 +58,6 @@ export const api = {
apiFetch(path, { method: 'PUT', body: JSON.stringify(body) }) as Promise<T>,
patch: <T>(path: string, body: unknown): Promise<T> =>
apiFetch(path, { method: 'PATCH', body: JSON.stringify(body) }) as Promise<T>,
del: (path: string): Promise<null> =>
apiFetch(path, { method: 'DELETE' }) as Promise<null>
del: <T = null>(path: string): Promise<T> =>
apiFetch(path, { method: 'DELETE' }) as Promise<T>
};
+19 -37
View File
@@ -1,5 +1,5 @@
import { createQuery } from '@tanstack/svelte-query';
import { apiFetch } from './client';
import { api } from './client';
import { qk } from './queries';
import type { Playlist, PlaylistDetail } from './types';
@@ -12,13 +12,11 @@ export type ListPlaylistsResponse = {
export async function listPlaylists(kind?: PlaylistKind): Promise<ListPlaylistsResponse> {
const k = kind ?? 'user';
return (await apiFetch(`/api/playlists?kind=${k}`, { method: 'GET' })) as ListPlaylistsResponse;
return api.get<ListPlaylistsResponse>(`/api/playlists?kind=${k}`);
}
export async function getPlaylist(id: string): Promise<PlaylistDetail> {
return (await apiFetch(`/api/playlists/${encodeURIComponent(id)}`, {
method: 'GET'
})) as PlaylistDetail;
return api.get<PlaylistDetail>(`/api/playlists/${encodeURIComponent(id)}`);
}
export type CreatePlaylistInput = {
@@ -28,11 +26,7 @@ export type CreatePlaylistInput = {
};
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;
return api.post<Playlist>('/api/playlists', input);
}
export type UpdatePlaylistInput = {
@@ -42,38 +36,31 @@ export type UpdatePlaylistInput = {
};
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;
return api.patch<Playlist>(`/api/playlists/${encodeURIComponent(id)}`, input);
}
export async function deletePlaylist(id: string): Promise<void> {
await apiFetch(`/api/playlists/${encodeURIComponent(id)}`, { method: 'DELETE' });
await api.del(`/api/playlists/${encodeURIComponent(id)}`);
}
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;
return api.post<PlaylistDetail>(
`/api/playlists/${encodeURIComponent(playlistID)}/tracks`,
{ track_ids: trackIDs }
);
}
export async function removePlaylistTrack(playlistID: string, position: number): Promise<PlaylistDetail> {
return (await apiFetch(
`/api/playlists/${encodeURIComponent(playlistID)}/tracks/${position}`,
{ method: 'DELETE' }
)) as PlaylistDetail;
return api.del<PlaylistDetail>(
`/api/playlists/${encodeURIComponent(playlistID)}/tracks/${position}`
);
}
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;
return api.put<PlaylistDetail>(
`/api/playlists/${encodeURIComponent(playlistID)}/tracks`,
{ ordered_positions: orderedPositions }
);
}
export function createPlaylistsQuery(kind?: PlaylistKind) {
@@ -103,10 +90,7 @@ export type RefreshDiscoverResponse = {
// returns the resulting Discover playlist's id + track count.
// Used by the detail-page Refresh button and the home tile kebab.
export async function refreshDiscover(): Promise<RefreshDiscoverResponse> {
return (await apiFetch('/api/playlists/system/discover/refresh', {
method: 'POST',
body: JSON.stringify({})
})) as RefreshDiscoverResponse;
return api.post<RefreshDiscoverResponse>('/api/playlists/system/discover/refresh', {});
}
// For-You refresh ------------------------------------------------------------
@@ -125,7 +109,5 @@ export type RefreshForYouResponse = {
// triggers refresh, the frontend then calls getPlaylist(new_id) to
// get full TrackRef snapshots and enqueues for playback.
export async function refreshForYou(): Promise<RefreshForYouResponse> {
return (await apiFetch('/api/playlists/system/for-you/refresh', {
method: 'POST'
})) as RefreshForYouResponse;
return api.post<RefreshForYouResponse>('/api/playlists/system/for-you/refresh', {});
}