Files
pmomusic/pmoapp/webapp/src/services/playlists.ts

177 lines
4.1 KiB
TypeScript

export interface PlaylistSummary {
id: string;
title: string;
role: string;
persistent: boolean;
cover_pk?: string | null;
cover_url?: string | null;
artist?: string | null;
track_count: number;
max_size?: number | null;
default_ttl_secs?: number | null;
last_change: string;
}
import type { AudioCacheMetadata } from "./audioCache";
export interface PlaylistTrack {
cache_pk: string;
added_at: string;
ttl_secs?: number | null;
lazy_pk?: string | null;
metadata?: AudioCacheMetadata | null;
cover_url?: string | null;
cover_source?: string | null;
}
export interface PlaylistDetail {
summary: PlaylistSummary;
tracks: PlaylistTrack[];
}
export interface ApiErrorPayload {
error?: string;
message?: string;
}
export interface CreatePlaylistPayload {
id: string;
title?: string;
role?: string;
cover_pk?: string;
persistent?: boolean;
max_size?: number;
default_ttl_secs?: number;
}
export interface UpdatePlaylistPayload {
title?: string;
role?: string;
max_size?: number | null;
default_ttl_secs?: number | null;
cover_pk?: string | null;
artist?: string | null;
}
export interface AddTracksPayload {
cache_pks: string[];
ttl_secs?: number;
lazy?: boolean;
}
async function parseJsonOrThrow<T>(response: Response): Promise<T> {
if (!response.ok) {
let message = `HTTP ${response.status}`;
try {
const error: ApiErrorPayload = await response.json();
if (error?.message) {
message = error.message;
}
} catch {
// Ignore JSON parsing errors and keep default message
}
throw new Error(message);
}
return response.json() as Promise<T>;
}
async function ensureSuccess(response: Response): Promise<void> {
if (!response.ok) {
let message = `HTTP ${response.status}`;
try {
const error: ApiErrorPayload = await response.json();
if (error?.message) {
message = error.message;
}
} catch {
// ignore
}
throw new Error(message);
}
}
export async function listPlaylists(): Promise<PlaylistSummary[]> {
const response = await fetch("/api/playlists");
return parseJsonOrThrow(response);
}
export async function getPlaylistDetail(id: string): Promise<PlaylistDetail> {
const response = await fetch(`/api/playlists/${encodeURIComponent(id)}`);
return parseJsonOrThrow(response);
}
export async function createPlaylist(
body: CreatePlaylistPayload,
): Promise<PlaylistDetail> {
const response = await fetch("/api/playlists", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
return parseJsonOrThrow(response);
}
export async function updatePlaylist(
id: string,
body: UpdatePlaylistPayload,
): Promise<PlaylistDetail> {
const response = await fetch(`/api/playlists/${encodeURIComponent(id)}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
return parseJsonOrThrow(response);
}
export async function deletePlaylist(id: string): Promise<void> {
const response = await fetch(`/api/playlists/${encodeURIComponent(id)}`, {
method: "DELETE",
});
await ensureSuccess(response);
}
export async function addTracksToPlaylist(
id: string,
payload: AddTracksPayload,
): Promise<PlaylistDetail> {
const response = await fetch(
`/api/playlists/${encodeURIComponent(id)}/tracks`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
},
);
return parseJsonOrThrow(response);
}
export async function flushPlaylist(id: string): Promise<PlaylistDetail> {
const response = await fetch(
`/api/playlists/${encodeURIComponent(id)}/tracks`,
{
method: "DELETE",
},
);
return parseJsonOrThrow(response);
}
export async function removeTrackFromPlaylist(
id: string,
cachePk: string,
): Promise<PlaylistDetail> {
const response = await fetch(
`/api/playlists/${encodeURIComponent(id)}/tracks/${encodeURIComponent(cachePk)}`,
{
method: "DELETE",
},
);
return parseJsonOrThrow(response);
}