mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(api): co-locate server-protocol clients into lib/api
Move the Subsonic + Navidrome protocol clients and the library IPC facade (subsonic*, navidrome*, library.ts + tests) from src/api/ into src/lib/api/, the feature-free infra layer (plan M4, decision §10.3: shared client core → lib/). Pure move: deep-path import specifiers @/api/* -> @/lib/api/* across ~150 consumers; no behaviour change. Domain REST (lyrics/events) and cover/analysis infra stay in src/api/ pending their own M4 placement. tsc 0, lint 0/0, full suite 319 files / 2353 tests green.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { nowPlayingPresence, NOW_PLAYING_IDLE_MINUTES } from '@/features/nowPlaying/api/nowPlayingPresence';
|
||||
import type { SubsonicNowPlaying, PlaybackReportState } from '@/api/subsonicTypes';
|
||||
import type { SubsonicNowPlaying, PlaybackReportState } from '@/lib/api/subsonicTypes';
|
||||
|
||||
// The function only reads `state` and `minutesAgo`; cast a minimal fixture.
|
||||
function entry(partial: { state?: PlaybackReportState; minutesAgo?: number }): SubsonicNowPlaying {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SubsonicNowPlaying } from '@/api/subsonicTypes';
|
||||
import type { SubsonicNowPlaying } from '@/lib/api/subsonicTypes';
|
||||
|
||||
/**
|
||||
* Derived liveness of a now-playing entry, surfaced as the indicator dot in the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Disc3, ExternalLink, Star } from 'lucide-react';
|
||||
import type { SubsonicAlbum, SubsonicSong } from '@/api/subsonicTypes';
|
||||
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { formatTotalDuration } from '@/utils/componentHelpers/nowPlayingHelpers';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { describe, it, expect, vi } from 'vitest';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { renderWithProviders } from '@/test/helpers/renderWithProviders';
|
||||
import ArtistCard from '@/features/nowPlaying/components/ArtistCard';
|
||||
import type { SubsonicArtistInfo } from '@/api/subsonicTypes';
|
||||
import type { SubsonicArtistInfo } from '@/lib/api/subsonicTypes';
|
||||
|
||||
const infoWithImage: SubsonicArtistInfo = {
|
||||
biography: 'Some bio text here.',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ExternalLink } from 'lucide-react';
|
||||
import type { SubsonicArtistInfo } from '@/api/subsonicTypes';
|
||||
import type { SubsonicArtistInfo } from '@/lib/api/subsonicTypes';
|
||||
import { isRealArtistImage, sanitizeHtml } from '@/utils/componentHelpers/nowPlayingHelpers';
|
||||
import CachedImage from '@/ui/CachedImage';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Disc3, ExternalLink, Music } from 'lucide-react';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
|
||||
import { COVER_DENSE_RAIL_CELL_CSS_PX } from '@/cover/layoutSizes';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Headphones, Heart, MicVocal, Music, Star } from 'lucide-react';
|
||||
import { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import type { CoverArtRef } from '@/cover/types';
|
||||
import type { ArtistStats, TrackStats } from '@/music-network';
|
||||
import type { SubsonicOpenArtistRef } from '@/api/subsonicTypes';
|
||||
import type { SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes';
|
||||
import { OpenArtistRefInline } from '@/features/artist';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { useEnrichmentPrimaryLabel } from '@/hooks/useEnrichmentPrimaryLabel';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TrackCoverArtImage } from '@/cover/TrackCoverArtImage';
|
||||
import { getNowPlaying } from '@/api/subsonicScrobble';
|
||||
import type { SubsonicNowPlaying } from '@/api/subsonicTypes';
|
||||
import { getNowPlaying } from '@/lib/api/subsonicScrobble';
|
||||
import type { SubsonicNowPlaying } from '@/lib/api/subsonicTypes';
|
||||
import React, { useState, useEffect, useRef, useCallback, useLayoutEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { PlayCircle, Pause, User, Radio, RefreshCw } from 'lucide-react';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getSongForServer } from '@/api/subsonicLibrary';
|
||||
import { getSongForServer } from '@/lib/api/subsonicLibrary';
|
||||
import { getArtistInfoForServer } from '@/features/artist';
|
||||
import type { SubsonicArtistInfo, SubsonicSong } from '@/api/subsonicTypes';
|
||||
import type { SubsonicArtistInfo, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ExternalLink, Play, TrendingUp } from 'lucide-react';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
|
||||
interface TopSongsCardProps {
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
*/
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { SubsonicArtistInfo, SubsonicSong, SubsonicAlbum, SubsonicArtist } from '@/api/subsonicTypes';
|
||||
import type { SubsonicArtistInfo, SubsonicSong, SubsonicAlbum, SubsonicArtist } from '@/lib/api/subsonicTypes';
|
||||
|
||||
vi.mock('@/features/artist');
|
||||
vi.mock('@/api/subsonicLibrary');
|
||||
vi.mock('@/lib/api/subsonicLibrary');
|
||||
vi.mock('@/api/bandsintown');
|
||||
vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
|
||||
shouldAttemptSubsonicForServer: vi.fn(() => true),
|
||||
@@ -22,7 +22,7 @@ vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
|
||||
|
||||
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||
import { getArtistForServer, getArtistInfoForServer, getTopSongsForServer } from '@/features/artist';
|
||||
import { getAlbumForServer, getSongForServer } from '@/api/subsonicLibrary';
|
||||
import { getAlbumForServer, getSongForServer } from '@/lib/api/subsonicLibrary';
|
||||
import { fetchBandsintownEvents } from '@/api/bandsintown';
|
||||
import { useNowPlayingFetchers, type NowPlayingFetchersDeps } from '@/features/nowPlaying/hooks/useNowPlayingFetchers';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getArtistInfoForServer } from '@/features/artist';
|
||||
import type { SubsonicAlbum, SubsonicArtistInfo, SubsonicSong } from '@/api/subsonicTypes';
|
||||
import type { SubsonicAlbum, SubsonicArtistInfo, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { resolveNpAlbum, resolveNpDiscography, resolveNpSongMeta, resolveNpTopSongs } from '@/features/nowPlaying/utils/nowPlayingMetadataResolve';
|
||||
import { fetchBandsintownEvents, type BandsintownEvent } from '@/api/bandsintown';
|
||||
import type { ArtistStats, TrackStats } from '@/music-network';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { queueSongStar } from '@/store/pendingStarSync';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { Track } from '@/store/playerStoreTypes';
|
||||
import type { TrackStats } from '@/music-network';
|
||||
import { getMusicNetworkRuntime } from '@/music-network';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useCoverArt } from '@/cover/useCoverArt';
|
||||
import { albumCoverRef } from '@/cover/ref';
|
||||
import { usePlaybackTrackCoverRef } from '@/cover/useLibraryCoverRef';
|
||||
import { coverArtIdFromRadio } from '@/cover/ids';
|
||||
import type { SubsonicArtistInfo, SubsonicSong } from '@/api/subsonicTypes';
|
||||
import type { SubsonicArtistInfo, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { usePlaybackLibraryNavigate } from '@/hooks/usePlaybackLibraryNavigate';
|
||||
import { usePlaybackServerId } from '@/hooks/usePlaybackServerId';
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { onInvoke } from '@/test/mocks/tauri';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import type { LibraryAdvancedSearchResponse } from '@/api/library';
|
||||
import type { LibraryAdvancedSearchResponse } from '@/lib/api/library';
|
||||
import * as subsonicArtists from '@/features/artist';
|
||||
import * as subsonicLibrary from '@/api/subsonicLibrary';
|
||||
import * as subsonicLibrary from '@/lib/api/subsonicLibrary';
|
||||
|
||||
// Network reachability is decided by the guard; mock it so we can test both arms.
|
||||
vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
* `artistInfo` (bio / similar) has no index source and stays network-only — it
|
||||
* is intentionally absent here.
|
||||
*/
|
||||
import { libraryGetTrack, libraryGetTracksByAlbum } from '@/api/library';
|
||||
import { libraryGetTrack, libraryGetTracksByAlbum } from '@/lib/api/library';
|
||||
import { getArtistForServer, getTopSongsForServer } from '@/features/artist';
|
||||
import { getAlbumForServer, getSongForServer } from '@/api/subsonicLibrary';
|
||||
import type { SubsonicAlbum, SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { getAlbumForServer, getSongForServer } from '@/lib/api/subsonicLibrary';
|
||||
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||
import { loadAlbumFromLibraryIndex, loadArtistFromLibraryIndex } from '@/features/offline';
|
||||
import { trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||
|
||||
Reference in New Issue
Block a user