import { createQuery } from '@tanstack/svelte-query'; import { api } from './client'; import { qk } from './queries'; import type { ActionResult, AdminQuarantineRow, LidarrConfig, LidarrMetadataProfile, LidarrQualityProfile, LidarrQuarantineActionRow, LidarrRequest, LidarrRequestStatus, LidarrRootFolder, LidarrTestResult } from './types'; // Admin Lidarr config ----------------------------------------------------- export async function getLidarrConfig(): Promise { return api.get('/api/admin/lidarr/config'); } export async function putLidarrConfig(cfg: LidarrConfig): Promise { return api.put('/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 { return api.post('/api/admin/lidarr/test', body); } export async function listQualityProfiles(): Promise { return api.get('/api/admin/lidarr/quality-profiles'); } export async function listMetadataProfiles(): Promise { return api.get('/api/admin/lidarr/metadata-profiles'); } export async function listRootFolders(): Promise { return api.get('/api/admin/lidarr/root-folders'); } // Admin request queue ----------------------------------------------------- export async function listAdminRequests( status?: LidarrRequestStatus, limit?: number ): Promise { 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( qs ? `/api/admin/requests?${qs}` : '/api/admin/requests' ); } export async function approveRequest( id: string, overrides: { quality_profile_id?: number; root_folder_path?: string } = {} ): Promise { return api.post(`/api/admin/requests/${id}/approve`, overrides); } export async function rejectRequest( id: string, notes?: string ): Promise { const body = notes !== undefined ? { notes } : {}; return api.post(`/api/admin/requests/${id}/reject`, body); } // Query factories --------------------------------------------------------- export function createLidarrConfigQuery() { return createQuery({ queryKey: qk.lidarrConfig(), queryFn: getLidarrConfig, staleTime: 60_000 }); } // `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 createMetadataProfilesQuery(enabled: boolean = true) { return createQuery({ queryKey: qk.lidarrMetadataProfiles(), queryFn: listMetadataProfiles, 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) }); } // Admin quarantine -------------------------------------------------------- export async function listAdminQuarantine(): Promise { return api.get('/api/admin/quarantine'); } export async function resolveQuarantine(trackID: string): Promise { return api.post(`/api/admin/quarantine/${trackID}/resolve`, {}); } export async function deleteQuarantineFile(trackID: string): Promise { return api.post(`/api/admin/quarantine/${trackID}/delete-file`, {}); } export async function deleteQuarantineViaLidarr(trackID: string): Promise { return api.post(`/api/admin/quarantine/${trackID}/delete-via-lidarr`, {}); } export async function listQuarantineActions( limit: number = 50 ): Promise { return api.get( `/api/admin/quarantine/actions?limit=${limit}` ); } export function createAdminQuarantineQuery() { return createQuery({ queryKey: qk.adminQuarantine(), queryFn: listAdminQuarantine, staleTime: 30_000 }); } export function createQuarantineActionsQuery(limit: number = 50) { return createQuery({ queryKey: qk.adminQuarantineActions(limit), queryFn: () => listQuarantineActions(limit), staleTime: 60_000 }); } // Admin cover art --------------------------------------------------------- export type RefetchAlbumCoverResponse = { album_id: string; cover_art_path: string | null; cover_art_source: string | null; }; export async function refetchAlbumCover(albumId: string): Promise { return api.post(`/api/admin/albums/${albumId}/cover/refetch`, {}); } export type RefetchMissingResponse = { queued: number; }; export async function refetchMissingCovers(): Promise { return api.post('/api/admin/covers/refetch-missing', {}); } // Library scan ------------------------------------------------------------- export type ScanStageLibrary = { scanned: number; added: number; updated: number; skipped: number; errored: number; }; export type ScanStageMbidBackfill = { processed: number; healed: number; skipped: number; duplicates?: number; }; export type ScanStageCoverEnrich = { processed: number; succeeded: number; failed: number; }; export type ScanStatus = { id: string; started_at: string; finished_at: string | null; library?: ScanStageLibrary; mbid_backfill?: ScanStageMbidBackfill; cover_enrich?: ScanStageCoverEnrich; error_message?: string; in_flight: boolean; }; export async function getScanStatus(): Promise { return api.get('/api/admin/scan/status'); } export type TriggerScanResp = { id?: string }; export async function triggerScan(): Promise { return api.post('/api/admin/scan/run', {}); } // Polls every 3s while a scan is in flight; falls back to a 30s stale-time // when idle so the section auto-updates as workers complete each stage. export function createScanStatusQuery() { return createQuery({ queryKey: qk.scanStatus(), queryFn: getScanStatus, staleTime: 30_000, refetchInterval: 3_000 }); } // Library coverage -------------------------------------------------------- export type CoverageRollup = { total: number; with_art: number; pending: number; settled: number; pending_no_mbid: number; }; export async function getCoverageRollup(): Promise { return api.get('/api/admin/library/coverage'); } // Same pacing as scan-status — 3s while polling, 30s stale-time. The // query is cheap server-side (one aggregate per call) and we want // the gauge to tick during a scan. export function createCoverageQuery() { return createQuery({ queryKey: qk.coverage(), queryFn: getCoverageRollup, staleTime: 30_000, refetchInterval: 3_000 }); }