refactor(api): F.50 — extract 7 small domain modules (#615)

Seven domain-eng splits peel ~200 LOC of read endpoints out of
`api/subsonic.ts`:

- `subsonicStreamUrl.ts` — `buildStreamUrl`, `coverArtCacheKey`,
  `buildCoverArtUrl`, `buildDownloadUrl` (token-signed URL builders
  for the four /rest endpoints we hand to the browser).
- `subsonicStarRating.ts` — `getStarred`, `star`, `unstar`,
  `setRating`, `probeEntityRatingSupport`. `setRating` still triggers
  the lazy `navidromeBrowse` cache invalidation; the same-folder
  lazy import path is preserved.
- `subsonicSearch.ts` — `search`, `searchSongsPaged`.
- `subsonicScrobble.ts` — `scrobbleSong`, `reportNowPlaying`,
  `getNowPlaying`.
- `subsonicAlbumInfo.ts` — `getAlbumInfo2`.
- `subsonicLyrics.ts` — `getLyricsBySongId`.
- `subsonicGenres.ts` — `getGenres`, `getAlbumsByGenre`.

63 external call sites migrated to direct imports. Four `vi.mock`
targets in the store-level tests pointed at `../api/subsonic` and
were updated to the new module paths.

Pure code-move. subsonic.ts: 762 → 561 LOC (−201).
This commit is contained in:
Frank Stellmacher
2026-05-13 00:46:13 +02:00
committed by GitHub
parent 006635de4b
commit 9606a99efb
76 changed files with 347 additions and 275 deletions
+1 -1
View File
@@ -1,3 +1,4 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { getAlbum } from '../api/subsonicLibrary';
import type { SubsonicAlbum } from '../api/subsonicTypes';
import { songToTrack } from '../utils/songToTrack';
@@ -5,7 +6,6 @@ import React, { memo, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { Play, ListPlus, HardDriveDownload, Check } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
import { usePlayerStore } from '../store/playerStore';
import { useOfflineStore } from '../store/offlineStore';
import { useAuthStore } from '../store/authStore';
+1 -1
View File
@@ -1,8 +1,8 @@
import { buildCoverArtUrl } from '../api/subsonicStreamUrl';
import type { EntityRatingSupportLevel, SubsonicSong } from '../api/subsonicTypes';
import React, { useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Play, Heart, ExternalLink, X, ChevronLeft, Download, ListPlus, HardDriveDownload, Share2, Highlighter, Loader2, Shuffle } from 'lucide-react';
import { buildCoverArtUrl } from '../api/subsonic';
import CachedImage from './CachedImage';
import CoverLightbox from './CoverLightbox';
import { useTranslation } from 'react-i18next';
+1 -1
View File
@@ -1,6 +1,6 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import type { SubsonicArtist } from '../api/subsonicTypes';
import React, { useMemo } from 'react';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
import { useNavigate } from 'react-router-dom';
import { Users } from 'lucide-react';
import { useTranslation } from 'react-i18next';
+1 -1
View File
@@ -1,3 +1,4 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { getArtist, getArtistInfo } from '../api/subsonicArtists';
import { getAlbum } from '../api/subsonicLibrary';
import type { SubsonicAlbum } from '../api/subsonicTypes';
@@ -6,7 +7,6 @@ import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Play, ListPlus, Music } from 'lucide-react';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
import CachedImage, { useCachedUrl } from './CachedImage';
import { usePlayerStore } from '../store/playerStore';
import { useAuthStore } from '../store/authStore';
+3 -1
View File
@@ -1,3 +1,5 @@
import { buildDownloadUrl } from '../api/subsonicStreamUrl';
import { star, unstar, setRating } from '../api/subsonicStarRating';
import { getAlbum } from '../api/subsonicLibrary';
import { getSimilarSongs2, getSimilarSongs, getTopSongs, getArtist } from '../api/subsonicArtists';
import type { SubsonicAlbum, SubsonicArtist, SubsonicPlaylist } from '../api/subsonicTypes';
@@ -17,7 +19,7 @@ import StarRating from './StarRating';
import { lastfmLoveTrack, lastfmUnloveTrack } from '../api/lastfm';
import { usePlayerStore } from '../store/playerStore';
import { useShallow } from 'zustand/react/shallow';
import { star, unstar, buildDownloadUrl, getPlaylists, getPlaylist, updatePlaylist, setRating } from '../api/subsonic';
import { getPlaylists, getPlaylist, updatePlaylist } from '../api/subsonic';
import { useNavigate } from 'react-router-dom';
import { useAuthStore } from '../store/authStore';
import { useDownloadModalStore } from '../store/downloadModalStore';
+2 -1
View File
@@ -1,3 +1,5 @@
import { star, unstar } from '../api/subsonicStarRating';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { getArtistInfo } from '../api/subsonicArtists';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress';
import React, { useCallback, useEffect, useState, useRef, memo, useMemo } from 'react';
@@ -7,7 +9,6 @@ import {
Moon, Sunrise,
} from 'lucide-react';
import { usePlayerStore } from '../store/playerStore';
import { buildCoverArtUrl, coverArtCacheKey, star, unstar } from '../api/subsonic';
import { useCachedUrl } from './CachedImage';
import { getCachedBlob } from '../utils/imageCache';
import { extractCoverColors } from '../utils/dynamicColors';
+1 -1
View File
@@ -1,8 +1,8 @@
import { getGenres } from '../api/subsonicGenres';
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { Check, Filter, X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { getGenres } from '../api/subsonic';
interface GenreFilterBarProps {
selected: string[];
+1 -1
View File
@@ -1,10 +1,10 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { getRandomAlbums, getAlbum } from '../api/subsonicLibrary';
import type { SubsonicAlbum } from '../api/subsonicTypes';
import { songToTrack } from '../utils/songToTrack';
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { Play, ListPlus } from 'lucide-react';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
import CachedImage, { useCachedUrl } from './CachedImage';
import { usePlayerStore } from '../store/playerStore';
import { useTranslation } from 'react-i18next';
+1 -1
View File
@@ -1,3 +1,4 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { getSong } from '../api/subsonicLibrary';
import type { SubsonicSong } from '../api/subsonicTypes';
import { useEffect, useMemo, useState } from 'react';
@@ -9,7 +10,6 @@ import {
declineOrbitSuggestion,
suggestionKey,
} from '../utils/orbit';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
import CachedImage from './CachedImage';
import { ORBIT_DEFAULT_SETTINGS } from '../api/orbit';
+2 -1
View File
@@ -1,9 +1,10 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { search } from '../api/subsonicSearch';
import type { SearchResults, SubsonicArtist } from '../api/subsonicTypes';
import { songToTrack } from '../utils/songToTrack';
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { Search, Disc3, Users, Music, TextSearch } from 'lucide-react';
import { search, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
import { usePlayerStore } from '../store/playerStore';
import { useAuthStore } from '../store/authStore';
import { useTranslation } from 'react-i18next';
+1 -1
View File
@@ -1,9 +1,9 @@
import { star, unstar } from '../api/subsonicStarRating';
import { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { emit } from '@tauri-apps/api/event';
import { useTranslation } from 'react-i18next';
import { Play, Trash2, Disc3, User, Heart, Info } from 'lucide-react';
import { star, unstar } from '../api/subsonic';
import type { MiniTrackInfo } from '../utils/miniPlayerBridge';
interface Props {
+1 -1
View File
@@ -1,3 +1,4 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { emit, listen } from '@tauri-apps/api/event';
@@ -5,7 +6,6 @@ import { invoke } from '@tauri-apps/api/core';
import { useTranslation } from 'react-i18next';
import { Play, Pause, SkipBack, SkipForward, Pin, PinOff, Maximize2, X, ListMusic, Volume2, VolumeX, Shuffle, Infinity as InfinityIcon, Waves, MoveRight } from 'lucide-react';
import CachedImage from './CachedImage';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
import { usePlayerStore } from '../store/playerStore';
import { useAuthStore } from '../store/authStore';
import { useKeybindingsStore, matchInAppBinding } from '../store/keybindingsStore';
+2 -1
View File
@@ -1,3 +1,5 @@
import { star, unstar } from '../api/subsonicStarRating';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import type { Track } from '../store/playerStoreTypes';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress';
import React, { useState, useCallback, useMemo, useRef, useEffect, useSyncExternalStore, CSSProperties } from 'react';
@@ -9,7 +11,6 @@ import {
Moon, Sunrise,
} from 'lucide-react';
import { usePlayerStore } from '../store/playerStore';
import { buildCoverArtUrl, coverArtCacheKey, star, unstar } from '../api/subsonic';
import { useCachedUrl } from './CachedImage';
import LyricsPane from './LyricsPane';
import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress';
+2 -1
View File
@@ -1,10 +1,11 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { search } from '../api/subsonicSearch';
import type { SearchResults, SubsonicArtist } from '../api/subsonicTypes';
import { songToTrack } from '../utils/songToTrack';
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { createPortal } from 'react-dom';
import { useNavigate } from 'react-router-dom';
import { X, Search, Disc3, Users, Music, Music2, Clock, ChevronRight } from 'lucide-react';
import { search, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
import { usePlayerStore } from '../store/playerStore';
import { useAuthStore } from '../store/authStore';
import { useTranslation } from 'react-i18next';
+2 -1
View File
@@ -1,8 +1,9 @@
import { buildCoverArtUrl } from '../api/subsonicStreamUrl';
import { getNowPlaying } from '../api/subsonicScrobble';
import type { SubsonicNowPlaying } from '../api/subsonicTypes';
import React, { useState, useEffect, useRef, useCallback, useLayoutEffect } from 'react';
import { createPortal } from 'react-dom';
import { PlayCircle, User, Radio, RefreshCw } from 'lucide-react';
import { getNowPlaying, buildCoverArtUrl } from '../api/subsonic';
import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '../store/playerStore';
import { useTranslation } from 'react-i18next';
+1 -1
View File
@@ -1,10 +1,10 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { getSong } from '../api/subsonicLibrary';
import type { SubsonicSong } from '../api/subsonicTypes';
import { useEffect, useMemo, useState } from 'react';
import { Radio, Clock } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useOrbitStore } from '../store/orbitStore';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
import CachedImage from './CachedImage';
import OrbitQueueHead from './OrbitQueueHead';
+2 -1
View File
@@ -1,3 +1,5 @@
import { star, unstar, setRating } from '../api/subsonicStarRating';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import type { SubsonicAlbum } from '../api/subsonicTypes';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress';
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -12,7 +14,6 @@ import { usePlayerStore } from '../store/playerStore';
import { useShallow } from 'zustand/react/shallow';
import { useAuthStore } from '../store/authStore';
import { useThemeStore } from '../store/themeStore';
import { buildCoverArtUrl, coverArtCacheKey, star, unstar, setRating } from '../api/subsonic';
import CachedImage from './CachedImage';
import WaveformSeek from './WaveformSeek';
import Equalizer from './Equalizer';
+2 -1
View File
@@ -1,3 +1,4 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { getAlbum } from '../api/subsonicLibrary';
import type { SubsonicPlaylist } from '../api/subsonicTypes';
import { registerQueueListScrollTopReader, consumePendingQueueListScrollTop } from '../store/queueUndo';
@@ -11,7 +12,7 @@ import OrbitGuestQueue from './OrbitGuestQueue';
import OrbitQueueHead from './OrbitQueueHead';
import HostApprovalQueue from './HostApprovalQueue';
import { Play, Music, Star, X, Trash2, Save, FolderOpen, Shuffle, Infinity, Waves, MicVocal, ListMusic, Check, ListPlus, MoveRight, Radio, HardDrive, ChevronDown, Info, Share2 } from 'lucide-react';
import { buildCoverArtUrl, coverArtCacheKey, getPlaylists, getPlaylist, updatePlaylist, deletePlaylist } from '../api/subsonic';
import { getPlaylists, getPlaylist, updatePlaylist, deletePlaylist } from '../api/subsonic';
import { usePlaylistStore } from '../store/playlistStore';
import { useCachedUrl } from './CachedImage';
import { useTranslation } from 'react-i18next';
+1 -1
View File
@@ -1,10 +1,10 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import type { SubsonicSong } from '../api/subsonicTypes';
import { songToTrack } from '../utils/songToTrack';
import React, { memo, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { Play, ListPlus, Star } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
import { usePlayerStore } from '../store/playerStore';
import CachedImage from './CachedImage';
import { enqueueAndPlay } from '../utils/playSong';
+1 -1
View File
@@ -1,10 +1,10 @@
import { searchSongsPaged } from '../api/subsonicSearch';
import type { SubsonicSong } from '../api/subsonicTypes';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useRefElementClientHeight } from '../hooks/useResizeClientHeight';
import { Search as SearchIcon, X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useVirtualizer } from '@tanstack/react-virtual';
import { searchSongsPaged } from '../api/subsonic';
import { ndListSongs } from '../api/navidromeBrowse';
import SongRow, { SongListHeader } from './SongRow';