React + Vite + TypeScript SPA covering the full ABS feature set (library browsing, item detail, metadata/cover editing, podcasts, player with session sync, admin: users/libraries/scanner/server settings). Dev uses a dynamic CORS proxy; production is served by server/index.mjs (static + reverse proxy to ABS_URL). Includes systemd unit and installer under deploy/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
60 lines
2.0 KiB
TypeScript
60 lines
2.0 KiB
TypeScript
import { api } from './client'
|
|
import type { BookMetadata, LibraryItem, PodcastMetadata } from '@/types/abs'
|
|
|
|
export interface GetItemOptions {
|
|
expanded?: boolean
|
|
/** e.g. "progress" */
|
|
include?: string
|
|
}
|
|
|
|
export async function getItem(id: string, opts: GetItemOptions = {}): Promise<LibraryItem> {
|
|
const res = await api.get<LibraryItem>(`/api/items/${id}`, {
|
|
params: {
|
|
expanded: opts.expanded ? 1 : undefined,
|
|
include: opts.include,
|
|
},
|
|
})
|
|
return res.data
|
|
}
|
|
|
|
export async function deleteItem(id: string, hard = false): Promise<void> {
|
|
await api.delete(`/api/items/${id}`, { params: hard ? { hard: 1 } : undefined })
|
|
}
|
|
|
|
/** Update book/podcast metadata. ABS: PATCH /api/items/:id/media with { metadata }. */
|
|
export async function updateMedia(
|
|
id: string,
|
|
payload: { metadata?: Partial<BookMetadata> | Partial<PodcastMetadata>; tags?: string[] },
|
|
): Promise<LibraryItem> {
|
|
const res = await api.patch<LibraryItem>(`/api/items/${id}/media`, payload)
|
|
return res.data
|
|
}
|
|
|
|
/** Upload a cover image file (multipart/form-data). */
|
|
export async function uploadCover(id: string, file: File): Promise<{ success?: boolean; cover?: string }> {
|
|
const form = new FormData()
|
|
form.append('cover', file)
|
|
const res = await api.post(`/api/items/${id}/cover`, form, {
|
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
})
|
|
return res.data
|
|
}
|
|
|
|
/** Set a cover from a remote URL (JSON body). */
|
|
export async function setCoverByUrl(id: string, url: string): Promise<{ success?: boolean; cover?: string }> {
|
|
const res = await api.post(`/api/items/${id}/cover`, { url })
|
|
return res.data
|
|
}
|
|
|
|
export async function removeCover(id: string): Promise<void> {
|
|
await api.delete(`/api/items/${id}/cover`)
|
|
}
|
|
|
|
export async function batchDelete(libraryItemIds: string[], hard = false): Promise<void> {
|
|
await api.post('/api/items/batch/delete', { libraryItemIds }, { params: hard ? { hard: 1 } : undefined })
|
|
}
|
|
|
|
export async function batchUpdate(updates: Array<{ id: string } & Record<string, unknown>>): Promise<void> {
|
|
await api.post('/api/items/batch/update', updates)
|
|
}
|