feat: add playlists mvp for web and subsonic

This commit is contained in:
2026-04-02 23:21:01 +03:00
parent b16f9de6c8
commit 675e173303
9 changed files with 930 additions and 15 deletions

View File

@@ -64,6 +64,21 @@ export type HomePayload = {
artists: Artist[]
}
export type PlaylistSummary = {
id: string
name: string
comment: string
public: boolean
songCount: number
durationSeconds: number
createdAt: string
updatedAt: string
}
export type PlaylistDetail = PlaylistSummary & {
tracks: Track[]
}
const API_BASE = import.meta.env.VITE_API_BASE ?? 'http://localhost:4040'
async function request<T>(path: string, init?: RequestInit): Promise<T> {
@@ -81,6 +96,10 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
throw new Error(`Request failed: ${response.status}`)
}
if (response.status === 204) {
return undefined as T
}
return response.json() as Promise<T>
}
@@ -131,6 +150,46 @@ export async function triggerScan() {
return request<ScanStatus>('/api/admin/scan', { method: 'POST' })
}
export async function fetchPlaylists() {
return request<{ items: PlaylistSummary[] }>('/api/playlists')
}
export async function fetchPlaylist(id: string) {
return request<PlaylistDetail>(`/api/playlists/${id}`)
}
export async function createPlaylist(input: {
name: string
comment?: string
public?: boolean
trackIds?: string[]
}) {
return request<PlaylistDetail>('/api/playlists', {
method: 'POST',
body: JSON.stringify(input),
})
}
export async function updatePlaylist(
id: string,
input: {
name?: string
comment?: string
public?: boolean
addTrackIds?: string[]
removeTrackIds?: string[]
},
) {
return request<PlaylistDetail>(`/api/playlists/${id}`, {
method: 'PATCH',
body: JSON.stringify(input),
})
}
export async function deletePlaylist(id: string) {
await request<void>(`/api/playlists/${id}`, { method: 'DELETE' })
}
export function coverArtUrl(id: string) {
const token = useSessionStore.getState().token
return `${API_BASE}/api/cover-art/${id}${token ? `?token=${encodeURIComponent(token)}` : ''}`