feat(web): add API client modules for Lidarr, requests, admin
T13 of the M5a Lidarr plan. Three new client modules wrap api.get/post/put/del
helpers and the existing TanStack Query qk namespace:
lidarr.ts - searchLidarr() + createLidarrSearchQuery()
requests.ts - createRequest, listMyRequests, getRequest, cancelRequest;
createMyRequestsQuery(); cancel goes through apiFetch
directly because the backend returns the cancelled row body
(api.del's return type is fixed to null).
admin.ts - getLidarrConfig, putLidarrConfig, testLidarrConnection,
listQualityProfiles, listRootFolders, listAdminRequests,
approveRequest, rejectRequest; query factories for each
read; quality profiles + root folders take an enabled prop
so the call site decides when Lidarr is configured.
Shared LidarrRequestStatus / LidarrRequestKind enums and request/config/
search-result shapes added to types.ts. Per-module helpers (CreateRequestParams)
stay in their module files. testLidarrConnection returns a discriminated union
({ok:true,version} | {ok:false,error}) and never throws on ok:false so the
SPA can render either branch.
qk extended with lidarrSearch, myRequests, lidarrConfig,
lidarrQualityProfiles, lidarrRootFolders, adminRequests.
Tests mirror likes.test.ts (vi.mock('./client')) and cover URL construction,
query-param encoding, body shapes, the not-ok testLidarrConnection branch,
and qk additions. 32 new tests, 217 total passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import { api } from './client';
|
||||
import { qk } from './queries';
|
||||
import type {
|
||||
LidarrConfig,
|
||||
LidarrQualityProfile,
|
||||
LidarrRequest,
|
||||
LidarrRequestStatus,
|
||||
LidarrRootFolder,
|
||||
LidarrTestResult
|
||||
} from './types';
|
||||
|
||||
// Admin Lidarr config -----------------------------------------------------
|
||||
|
||||
export async function getLidarrConfig(): Promise<LidarrConfig> {
|
||||
return api.get<LidarrConfig>('/api/admin/lidarr/config');
|
||||
}
|
||||
|
||||
export async function putLidarrConfig(cfg: LidarrConfig): Promise<LidarrConfig> {
|
||||
return api.put<LidarrConfig>('/api/admin/lidarr/config', cfg);
|
||||
}
|
||||
|
||||
// testLidarrConnection always returns 200 with a discriminated-union body —
|
||||
// callers branch on `result.ok`. We do NOT throw on `ok:false`; the SPA wants
|
||||
// to render either branch (e.g. "connected to Lidarr X.Y.Z" vs "auth failed").
|
||||
export async function testLidarrConnection(
|
||||
body: { base_url?: string; api_key?: string } = {}
|
||||
): Promise<LidarrTestResult> {
|
||||
return api.post<LidarrTestResult>('/api/admin/lidarr/test', body);
|
||||
}
|
||||
|
||||
export async function listQualityProfiles(): Promise<LidarrQualityProfile[]> {
|
||||
return api.get<LidarrQualityProfile[]>('/api/admin/lidarr/quality-profiles');
|
||||
}
|
||||
|
||||
export async function listRootFolders(): Promise<LidarrRootFolder[]> {
|
||||
return api.get<LidarrRootFolder[]>('/api/admin/lidarr/root-folders');
|
||||
}
|
||||
|
||||
// Admin request queue -----------------------------------------------------
|
||||
|
||||
export async function listAdminRequests(
|
||||
status?: LidarrRequestStatus,
|
||||
limit?: number
|
||||
): Promise<LidarrRequest[]> {
|
||||
const params = new URLSearchParams();
|
||||
if (status) params.set('status', status);
|
||||
if (limit !== undefined) params.set('limit', String(limit));
|
||||
const qs = params.toString();
|
||||
return api.get<LidarrRequest[]>(
|
||||
qs ? `/api/admin/requests?${qs}` : '/api/admin/requests'
|
||||
);
|
||||
}
|
||||
|
||||
export async function approveRequest(
|
||||
id: string,
|
||||
overrides: { quality_profile_id?: number; root_folder_path?: string } = {}
|
||||
): Promise<LidarrRequest> {
|
||||
return api.post<LidarrRequest>(`/api/admin/requests/${id}/approve`, overrides);
|
||||
}
|
||||
|
||||
export async function rejectRequest(
|
||||
id: string,
|
||||
notes?: string
|
||||
): Promise<LidarrRequest> {
|
||||
const body = notes !== undefined ? { notes } : {};
|
||||
return api.post<LidarrRequest>(`/api/admin/requests/${id}/reject`, body);
|
||||
}
|
||||
|
||||
// Query factories ---------------------------------------------------------
|
||||
|
||||
export function createLidarrConfigQuery() {
|
||||
return createQuery({
|
||||
queryKey: qk.lidarrConfig(),
|
||||
queryFn: getLidarrConfig
|
||||
});
|
||||
}
|
||||
|
||||
// `enabled` is passed in by the caller — typically derived from
|
||||
// LidarrConfig.enabled — so the query only fires once Lidarr is configured.
|
||||
// Keeping it as a prop (vs. reading config inside this factory) preserves
|
||||
// purity and lets the caller choose its own gating logic.
|
||||
export function createQualityProfilesQuery(enabled: boolean = true) {
|
||||
return createQuery({
|
||||
queryKey: qk.lidarrQualityProfiles(),
|
||||
queryFn: listQualityProfiles,
|
||||
enabled
|
||||
});
|
||||
}
|
||||
|
||||
export function createRootFoldersQuery(enabled: boolean = true) {
|
||||
return createQuery({
|
||||
queryKey: qk.lidarrRootFolders(),
|
||||
queryFn: listRootFolders,
|
||||
enabled
|
||||
});
|
||||
}
|
||||
|
||||
export function createAdminRequestsQuery(status?: LidarrRequestStatus) {
|
||||
return createQuery({
|
||||
queryKey: qk.adminRequests(status),
|
||||
queryFn: () => listAdminRequests(status)
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user