feat(storage): wire Storage Maintenance screen (A6)
Duplicates / failed / missing-metadata tabs (paginated, reusing TrackRow) plus admin orphan-file cleanup. Adds getDuplicates/getBrokenFiles/getMissingMetadata/ runCleanup endpoints and a DuplicateGroup type; drops the dead scanStorage/ deleteTrackFile stubs that had no backend route. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,32 @@
|
||||
import { api } from '../index';
|
||||
import { toStorageStats, type RawStorageStats } from '../mappers';
|
||||
import type { StorageStats } from '../types';
|
||||
import {
|
||||
toStorageStats,
|
||||
toPage,
|
||||
toTrack,
|
||||
type RawStorageStats,
|
||||
type RawPaged,
|
||||
type RawTrack,
|
||||
} from '../mappers';
|
||||
import type {
|
||||
StorageStats,
|
||||
DuplicateGroup,
|
||||
PaginatedResponse,
|
||||
Track,
|
||||
} from '../types';
|
||||
|
||||
// `GET /storage` returns library + disk statistics (§A6). The maintenance
|
||||
// routes (`/storage/duplicates`, `/storage/broken`, `/storage/missing-metadata`,
|
||||
// `POST /storage/cleanup`) are still backend stubs and unused by the UI.
|
||||
// routes back the Storage Maintenance screen: `/duplicates`, `/broken`,
|
||||
// `/missing-metadata` (read) and `POST /cleanup` (admin, enqueues the worker).
|
||||
|
||||
interface RawDuplicateGroup {
|
||||
fingerprint: string;
|
||||
tracks: RawTrack[];
|
||||
}
|
||||
|
||||
interface Paging {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export const storageApi = api.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
@@ -13,16 +35,32 @@ export const storageApi = api.injectEndpoints({
|
||||
transformResponse: (raw: RawStorageStats) => toStorageStats(raw),
|
||||
providesTags: ['Storage'],
|
||||
}),
|
||||
scanStorage: build.mutation<{ jobId: string }, void>({
|
||||
query: () => ({ url: '/storage/scan', method: 'POST' }),
|
||||
invalidatesTags: ['Storage', 'Track', 'Album', 'Artist'],
|
||||
getDuplicates: build.query<DuplicateGroup[], void>({
|
||||
query: () => '/storage/duplicates',
|
||||
transformResponse: (raw: RawDuplicateGroup[]) =>
|
||||
raw.map((g) => ({
|
||||
fingerprint: g.fingerprint,
|
||||
tracks: g.tracks.map(toTrack),
|
||||
})),
|
||||
providesTags: ['Storage', 'Track'],
|
||||
}),
|
||||
deleteTrackFile: build.mutation<void, string>({
|
||||
query: (trackId) => ({
|
||||
url: `/storage/tracks/${trackId}`,
|
||||
method: 'DELETE',
|
||||
getBrokenFiles: build.query<PaginatedResponse<Track>, Paging | void>({
|
||||
query: (p) => ({ url: '/storage/broken', params: p ?? {} }),
|
||||
transformResponse: (raw: RawPaged<RawTrack>) => toPage(raw, toTrack),
|
||||
providesTags: ['Storage', 'Track'],
|
||||
}),
|
||||
getMissingMetadata: build.query<PaginatedResponse<Track>, Paging | void>({
|
||||
query: (p) => ({ url: '/storage/missing-metadata', params: p ?? {} }),
|
||||
transformResponse: (raw: RawPaged<RawTrack>) => toPage(raw, toTrack),
|
||||
providesTags: ['Storage', 'Track'],
|
||||
}),
|
||||
runCleanup: build.mutation<{ status: string; jobId: string }, void>({
|
||||
query: () => ({ url: '/storage/cleanup', method: 'POST' }),
|
||||
transformResponse: (raw: { status: string; job_id: string }) => ({
|
||||
status: raw.status,
|
||||
jobId: raw.job_id,
|
||||
}),
|
||||
invalidatesTags: ['Storage', { type: 'Track', id: undefined }],
|
||||
invalidatesTags: ['Storage', 'Track'],
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
@@ -30,6 +68,8 @@ export const storageApi = api.injectEndpoints({
|
||||
|
||||
export const {
|
||||
useGetStorageStatsQuery,
|
||||
useScanStorageMutation,
|
||||
useDeleteTrackFileMutation,
|
||||
useGetDuplicatesQuery,
|
||||
useGetBrokenFilesQuery,
|
||||
useGetMissingMetadataQuery,
|
||||
useRunCleanupMutation,
|
||||
} = storageApi;
|
||||
|
||||
@@ -176,6 +176,12 @@ export interface StorageStats {
|
||||
disk?: StorageDiskUsage;
|
||||
}
|
||||
|
||||
/** A set of tracks sharing one acoustic fingerprint — de-dup candidates (§A6). */
|
||||
export interface DuplicateGroup {
|
||||
fingerprint: string;
|
||||
tracks: Track[];
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
|
||||
Reference in New Issue
Block a user