From c7a473d8641c3d1700bb25670fd8edc2725c5fdb Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 2 Nov 2025 21:56:35 -0800 Subject: [PATCH] refactor api controller to internalize server fetch --- src/renderer/api/controller.ts | 544 ++++++++++++++++-- src/renderer/api/jellyfin/jellyfin-api.ts | 4 +- .../api/jellyfin/jellyfin-controller.ts | 4 +- src/renderer/api/navidrome/navidrome-api.ts | 4 +- .../api/navidrome/navidrome-controller.ts | 20 +- src/renderer/api/subsonic/subsonic-api.ts | 4 +- .../api/subsonic/subsonic-controller.ts | 4 +- .../components/audio-player/index.tsx | 4 +- .../grid-carousel/grid-carousel.tsx | 4 +- .../virtual-table/cells/favorite-cell.tsx | 4 +- .../virtual-table/cells/rating-cell.tsx | 2 +- .../virtual-table/hooks/use-virtual-table.ts | 2 +- .../components/server-required.tsx | 8 +- .../routes/action-required-route.tsx | 4 +- src/renderer/features/albums/api/album-api.ts | 7 +- .../components/album-detail-content.tsx | 4 +- .../albums/components/album-detail-header.tsx | 2 +- .../components/album-list-grid-view.tsx | 4 +- .../components/navidrome-album-filters.tsx | 4 +- .../albums/routes/album-list-route.tsx | 2 +- .../routes/dummy-album-detail-route.tsx | 7 +- .../features/artists/api/artists-api.ts | 21 +- .../album-artist-detail-content.tsx | 4 +- .../components/album-artist-detail-header.tsx | 2 +- .../album-artist-list-grid-view.tsx | 4 +- .../album-artist-list-header-filters.tsx | 4 +- .../components/artist-list-grid-view.tsx | 4 +- .../components/artist-list-header-filters.tsx | 4 +- .../context-menu/context-menu-provider.tsx | 21 +- .../features/discord-rpc/use-discord-rpc.ts | 5 +- .../features/genres/api/genres-api.ts | 7 +- .../components/genre-list-grid-view.tsx | 2 +- src/renderer/features/home/api/home-api.ts | 7 +- .../features/lyrics/api/lyrics-api.ts | 10 +- .../player/components/right-controls.tsx | 10 +- .../player/components/shuffle-all-modal.tsx | 6 +- .../player/hooks/use-media-session.ts | 4 - .../features/player/hooks/use-scrobble.ts | 16 +- .../player/mutations/scrobble-mutation.ts | 16 +- src/renderer/features/player/utils.ts | 14 +- .../features/playlists/api/playlists-api.ts | 15 +- .../add-to-playlist-context-modal.tsx | 10 +- .../components/create-playlist-form.tsx | 2 +- .../playlist-detail-song-list-content.tsx | 2 +- ...aylist-detail-song-list-header-filters.tsx | 5 +- .../components/playlist-list-grid-view.tsx | 4 +- .../playlist-list-header-filters.tsx | 4 +- .../components/save-as-playlist-form.tsx | 4 +- .../components/update-playlist-form.tsx | 7 +- .../mutations/add-to-playlist-mutation.ts | 18 +- .../mutations/create-playlist-mutation.ts | 26 +- .../mutations/delete-playlist-mutation.ts | 26 +- .../remove-from-playlist-mutation.ts | 18 +- .../mutations/update-playlist-mutation.ts | 18 +- .../playlist-detail-song-list-route.tsx | 6 +- .../features/search/api/search-api.ts | 5 +- .../search/components/server-commands.tsx | 4 +- .../servers/components/add-server-form.tsx | 4 +- .../servers/components/edit-server-form.tsx | 9 +- .../features/shared/api/shared-api.ts | 21 +- .../shared/hooks/use-handle-favorite.ts | 12 +- .../mutations/create-favorite-mutation.ts | 19 +- .../mutations/delete-favorite-mutation.ts | 25 +- .../shared/mutations/set-rating-mutation.ts | 11 +- .../components/share-item-context-modal.tsx | 2 +- .../sharing/mutations/share-item-mutation.ts | 10 +- src/renderer/features/songs/api/songs-api.ts | 19 +- .../components/navidrome-song-filters.tsx | 4 +- .../songs/components/song-list-grid-view.tsx | 4 +- .../features/titlebar/components/app-menu.tsx | 6 +- src/renderer/hooks/use-list-filter-refresh.ts | 2 +- .../hooks/use-server-authenticated.ts | 7 +- src/renderer/hooks/use-server-version.ts | 2 +- src/renderer/store/auth.store.ts | 33 +- src/renderer/store/settings.store.ts | 1 - .../utils/set-transcoded-queue-data.ts | 4 +- .../api/navidrome/navidrome-normalize.ts | 8 +- src/shared/api/subsonic/subsonic-normalize.ts | 10 +- src/shared/types/domain-types.ts | 113 +++- 79 files changed, 904 insertions(+), 399 deletions(-) diff --git a/src/renderer/api/controller.ts b/src/renderer/api/controller.ts index 7a62b872..041ba62c 100644 --- a/src/renderer/api/controller.ts +++ b/src/renderer/api/controller.ts @@ -2,18 +2,19 @@ import i18n from '/@/i18n/i18n'; import { JellyfinController } from '/@/renderer/api/jellyfin/jellyfin-controller'; import { NavidromeController } from '/@/renderer/api/navidrome/navidrome-controller'; import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller'; -import { useAuthStore } from '/@/renderer/store'; +import { getServerById, useAuthStore } from '/@/renderer/store'; import { toast } from '/@/shared/components/toast/toast'; import { AuthenticationResponse, ControllerEndpoint, + InternalControllerEndpoint, ServerType, } from '/@/shared/types/domain-types'; type ApiController = { - jellyfin: ControllerEndpoint; - navidrome: ControllerEndpoint; - subsonic: ControllerEndpoint; + jellyfin: InternalControllerEndpoint; + navidrome: InternalControllerEndpoint; + subsonic: InternalControllerEndpoint; }; const endpoints: ApiController = { @@ -25,7 +26,7 @@ const endpoints: ApiController = { const apiController = ( endpoint: K, type?: ServerType, -): NonNullable => { +): NonNullable => { const serverType = type || useAuthStore.getState().currentServer?.type; if (!serverType) { @@ -68,129 +69,580 @@ export interface GeneralController extends Omit, 'a export const controller: GeneralController = { addToPlaylist(args) { - return apiController('addToPlaylist', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: addToPlaylist`, + ); + } + + return apiController( + 'addToPlaylist', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, authenticate(url, body, type) { return apiController('authenticate', type)(url, body); }, createFavorite(args) { - return apiController('createFavorite', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: createFavorite`, + ); + } + + return apiController( + 'createFavorite', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, createPlaylist(args) { - return apiController('createPlaylist', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: createPlaylist`, + ); + } + + return apiController( + 'createPlaylist', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, deleteFavorite(args) { - return apiController('deleteFavorite', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: deleteFavorite`, + ); + } + + return apiController( + 'deleteFavorite', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, deletePlaylist(args) { - return apiController('deletePlaylist', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: deletePlaylist`, + ); + } + + return apiController( + 'deletePlaylist', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getAlbumArtistDetail(args) { - return apiController('getAlbumArtistDetail', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumArtistDetail`, + ); + } + + return apiController( + 'getAlbumArtistDetail', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getAlbumArtistList(args) { - return apiController('getAlbumArtistList', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumArtistList`, + ); + } + + return apiController( + 'getAlbumArtistList', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getAlbumArtistListCount(args) { - return apiController('getAlbumArtistListCount', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumArtistListCount`, + ); + } + + return apiController( + 'getAlbumArtistListCount', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getAlbumDetail(args) { - return apiController('getAlbumDetail', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumDetail`, + ); + } + + return apiController( + 'getAlbumDetail', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getAlbumInfo(args) { - return apiController('getAlbumInfo', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumInfo`, + ); + } + + return apiController( + 'getAlbumInfo', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getAlbumList(args) { - return apiController('getAlbumList', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumList`, + ); + } + + return apiController( + 'getAlbumList', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getAlbumListCount(args) { - return apiController('getAlbumListCount', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumListCount`, + ); + } + + return apiController( + 'getAlbumListCount', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getArtistList(args) { - return apiController('getArtistList', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getArtistList`, + ); + } + + return apiController( + 'getArtistList', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getArtistListCount(args) { - return apiController('getArtistListCount', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getArtistListCount`, + ); + } + + return apiController( + 'getArtistListCount', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getDownloadUrl(args) { - return apiController('getDownloadUrl', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getDownloadUrl`, + ); + } + + return apiController( + 'getDownloadUrl', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getGenreList(args) { - return apiController('getGenreList', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getGenreList`, + ); + } + + return apiController( + 'getGenreList', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getLyrics(args) { - return apiController('getLyrics', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getLyrics`, + ); + } + + return apiController( + 'getLyrics', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getMusicFolderList(args) { - return apiController('getMusicFolderList', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getMusicFolderList`, + ); + } + + return apiController( + 'getMusicFolderList', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getPlaylistDetail(args) { - return apiController('getPlaylistDetail', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getPlaylistDetail`, + ); + } + + return apiController( + 'getPlaylistDetail', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getPlaylistList(args) { - return apiController('getPlaylistList', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getPlaylistList`, + ); + } + + return apiController( + 'getPlaylistList', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getPlaylistListCount(args) { - return apiController('getPlaylistListCount', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getPlaylistListCount`, + ); + } + + return apiController( + 'getPlaylistListCount', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getPlaylistSongList(args) { - return apiController('getPlaylistSongList', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getPlaylistSongList`, + ); + } + + return apiController( + 'getPlaylistSongList', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getRandomSongList(args) { - return apiController('getRandomSongList', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getRandomSongList`, + ); + } + + return apiController( + 'getRandomSongList', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getRoles(args) { - return apiController('getRoles', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getRoles`, + ); + } + + return apiController( + 'getRoles', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getServerInfo(args) { - return apiController('getServerInfo', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getServerInfo`, + ); + } + + return apiController( + 'getServerInfo', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getSimilarSongs(args) { - return apiController('getSimilarSongs', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getSimilarSongs`, + ); + } + + return apiController( + 'getSimilarSongs', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getSongDetail(args) { - return apiController('getSongDetail', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getSongDetail`, + ); + } + + return apiController( + 'getSongDetail', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getSongList(args) { - return apiController('getSongList', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getSongList`, + ); + } + + return apiController( + 'getSongList', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getSongListCount(args) { - return apiController('getSongListCount', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getSongListCount`, + ); + } + + return apiController( + 'getSongListCount', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getStructuredLyrics(args) { - return apiController('getStructuredLyrics', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getStructuredLyrics`, + ); + } + + return apiController( + 'getStructuredLyrics', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getTags(args) { - return apiController('getTags', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getTags`, + ); + } + + return apiController( + 'getTags', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getTopSongs(args) { - return apiController('getTopSongs', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getTopSongs`, + ); + } + + return apiController( + 'getTopSongs', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getTranscodingUrl(args) { - return apiController('getTranscodingUrl', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getTranscodingUrl`, + ); + } + + return apiController( + 'getTranscodingUrl', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, getUserList(args) { - return apiController('getUserList', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getUserList`, + ); + } + + return apiController( + 'getUserList', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, movePlaylistItem(args) { - return apiController('movePlaylistItem', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: movePlaylistItem`, + ); + } + + return apiController( + 'movePlaylistItem', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, removeFromPlaylist(args) { - return apiController('removeFromPlaylist', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: removeFromPlaylist`, + ); + } + + return apiController( + 'removeFromPlaylist', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, scrobble(args) { - return apiController('scrobble', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: scrobble`, + ); + } + + return apiController( + 'scrobble', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, search(args) { - return apiController('search', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: search`, + ); + } + + return apiController( + 'search', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, setRating(args) { - return apiController('setRating', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: setRating`, + ); + } + + return apiController( + 'setRating', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, shareItem(args) { - return apiController('shareItem', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: shareItem`, + ); + } + + return apiController( + 'shareItem', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, updatePlaylist(args) { - return apiController('updatePlaylist', args.apiClientProps.server?.type)?.(args); + const server = getServerById(args.apiClientProps.serverId); + + if (!server) { + throw new Error( + `${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: updatePlaylist`, + ); + } + + return apiController( + 'updatePlaylist', + server.type, + )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); }, }; diff --git a/src/renderer/api/jellyfin/jellyfin-api.ts b/src/renderer/api/jellyfin/jellyfin-api.ts index a204e109..c2c0c064 100644 --- a/src/renderer/api/jellyfin/jellyfin-api.ts +++ b/src/renderer/api/jellyfin/jellyfin-api.ts @@ -11,7 +11,7 @@ import { authenticationFailure } from '/@/renderer/api/utils'; import { useAuthStore } from '/@/renderer/store'; import { jfType } from '/@/shared/api/jellyfin/jellyfin-types'; import { getClientType } from '/@/shared/api/utils'; -import { ServerListItem } from '/@/shared/types/domain-types'; +import { ServerListItemWithCredential } from '/@/shared/types/domain-types'; const c = initContract(); @@ -359,7 +359,7 @@ export const createAuthHeader = (): string => { }; export const jfApiClient = (args: { - server: null | ServerListItem; + server: null | ServerListItemWithCredential; signal?: AbortSignal; url?: string; }) => { diff --git a/src/renderer/api/jellyfin/jellyfin-controller.ts b/src/renderer/api/jellyfin/jellyfin-controller.ts index 4926c2b5..8e02105b 100644 --- a/src/renderer/api/jellyfin/jellyfin-controller.ts +++ b/src/renderer/api/jellyfin/jellyfin-controller.ts @@ -9,8 +9,8 @@ import { getFeatures, hasFeature, VersionInfo } from '/@/shared/api/utils'; import { albumArtistListSortMap, albumListSortMap, - ControllerEndpoint, genreListSortMap, + InternalControllerEndpoint, LibraryItem, Played, playlistListSortMap, @@ -40,7 +40,7 @@ const VERSION_INFO: VersionInfo = [ ['10.0.0', { [ServerFeature.TAGS]: [1] }], ]; -export const JellyfinController: ControllerEndpoint = { +export const JellyfinController: InternalControllerEndpoint = { addToPlaylist: async (args) => { const { apiClientProps, body, query } = args; diff --git a/src/renderer/api/navidrome/navidrome-api.ts b/src/renderer/api/navidrome/navidrome-api.ts index 34b24db0..4465805f 100644 --- a/src/renderer/api/navidrome/navidrome-api.ts +++ b/src/renderer/api/navidrome/navidrome-api.ts @@ -11,7 +11,7 @@ import { useAuthStore } from '/@/renderer/store'; import { ndType } from '/@/shared/api/navidrome/navidrome-types'; import { resultWithHeaders } from '/@/shared/api/utils'; import { toast } from '/@/shared/components/toast/toast'; -import { ServerListItem } from '/@/shared/types/domain-types'; +import { ServerListItemWithCredential } from '/@/shared/types/domain-types'; const localSettings = isElectron() ? window.api.localSettings : null; @@ -379,7 +379,7 @@ axiosClient.interceptors.response.use( ); export const ndApiClient = (args: { - server: null | ServerListItem; + server: null | ServerListItemWithCredential; signal?: AbortSignal; url?: string; }) => { diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 10b6e675..4755926e 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -9,12 +9,12 @@ import { albumArtistListSortMap, albumListSortMap, AuthenticationResponse, - ControllerEndpoint, genreListSortMap, + InternalControllerEndpoint, playlistListSortMap, PlaylistSongListArgs, PlaylistSongListResponse, - ServerListItem, + ServerListItemWithCredential, Song, songListSortMap, sortOrderMap, @@ -47,7 +47,11 @@ const NAVIDROME_ROLES: Array = [ const EXCLUDED_TAGS = new Set(['disctotal', 'genre', 'tracktotal']); -const excludeMissing = (server: null | ServerListItem) => { +const excludeMissing = (server?: null | ServerListItemWithCredential) => { + if (!server) { + return undefined; + } + if (hasFeature(server, ServerFeature.BFR)) { return { missing: false }; } @@ -55,10 +59,10 @@ const excludeMissing = (server: null | ServerListItem) => { return undefined; }; -const getArtistSongKey = (server: null | ServerListItem) => +const getArtistSongKey = (server: null | ServerListItemWithCredential) => hasFeature(server, ServerFeature.TRACK_ALBUM_ARTIST_SEARCH) ? 'artists_id' : 'album_artist_id'; -export const NavidromeController: ControllerEndpoint = { +export const NavidromeController: InternalControllerEndpoint = { addToPlaylist: async (args) => { const { apiClientProps, body, query } = args; @@ -156,7 +160,7 @@ export const NavidromeController: ControllerEndpoint = { throw new Error('Failed to get album artist detail'); } - if (!apiClientProps.server) { + if (!apiClientProps.serverId) { throw new Error('Server is required'); } @@ -431,7 +435,7 @@ export const NavidromeController: ControllerEndpoint = { getPlaylistSongList: async (args: PlaylistSongListArgs): Promise => { const { apiClientProps, query } = args; - const res = await ndApiClient(apiClientProps).getPlaylistSongList({ + const res = await ndApiClient(apiClientProps as any).getPlaylistSongList({ params: { id: query.id, }, @@ -481,7 +485,7 @@ export const NavidromeController: ControllerEndpoint = { return { features, - id: apiClientProps.server?.id, + id: apiClientProps.serverId, version: ping.body.serverVersion!, }; }, diff --git a/src/renderer/api/subsonic/subsonic-api.ts b/src/renderer/api/subsonic/subsonic-api.ts index 0e7b0913..4057f879 100644 --- a/src/renderer/api/subsonic/subsonic-api.ts +++ b/src/renderer/api/subsonic/subsonic-api.ts @@ -7,7 +7,7 @@ import { z } from 'zod'; import i18n from '/@/i18n/i18n'; import { ssType } from '/@/shared/api/subsonic/subsonic-types'; import { toast } from '/@/shared/components/toast/toast'; -import { ServerListItem } from '/@/shared/types/domain-types'; +import { ServerListItemWithCredential } from '/@/shared/types/domain-types'; const c = initContract(); @@ -288,7 +288,7 @@ const silentlyTransformResponse = (data: any) => { }; export const ssApiClient = (args: { - server: null | ServerListItem; + server: null | ServerListItemWithCredential; signal?: AbortSignal; silent?: boolean; url?: string; diff --git a/src/renderer/api/subsonic/subsonic-controller.ts b/src/renderer/api/subsonic/subsonic-controller.ts index 80798883..60d5f074 100644 --- a/src/renderer/api/subsonic/subsonic-controller.ts +++ b/src/renderer/api/subsonic/subsonic-controller.ts @@ -16,8 +16,8 @@ import { } from '/@/shared/api/subsonic/subsonic-types'; import { AlbumListSort, - ControllerEndpoint, GenreListSort, + InternalControllerEndpoint, LibraryItem, PlaylistListSort, Song, @@ -51,7 +51,7 @@ const MAX_SUBSONIC_ITEMS = 500; // A trick to skip ahead 10x const SUBSONIC_FAST_BATCH_SIZE = MAX_SUBSONIC_ITEMS * 10; -export const SubsonicController: ControllerEndpoint = { +export const SubsonicController: InternalControllerEndpoint = { addToPlaylist: async ({ apiClientProps, body, query }) => { const res = await ssApiClient(apiClientProps).updatePlaylist({ query: { diff --git a/src/renderer/components/audio-player/index.tsx b/src/renderer/components/audio-player/index.tsx index a762d239..b288b6c8 100644 --- a/src/renderer/components/audio-player/index.tsx +++ b/src/renderer/components/audio-player/index.tsx @@ -20,7 +20,7 @@ import { gaplessHandler, } from '/@/renderer/components/audio-player/utils/list-handlers'; import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio'; -import { getServerById, TranscodingConfig, usePlaybackSettings, useSpeed } from '/@/renderer/store'; +import { TranscodingConfig, usePlaybackSettings, useSpeed } from '/@/renderer/store'; import { useSettingsStore, useSettingsStoreActions } from '/@/renderer/store/settings.store'; import { toast } from '/@/shared/components/toast/toast'; import { PlaybackStyle, PlayerStatus } from '/@/shared/types/types'; @@ -76,7 +76,7 @@ const useSongUrl = (transcode: TranscodingConfig, current: boolean, song?: Song) const result = api.controller.getTranscodingUrl({ apiClientProps: { - server: getServerById(song.serverId), + serverId: song.serverId, }, query: { base: song.streamUrl, diff --git a/src/renderer/components/grid-carousel/grid-carousel.tsx b/src/renderer/components/grid-carousel/grid-carousel.tsx index 08b54fed..22326f55 100644 --- a/src/renderer/components/grid-carousel/grid-carousel.tsx +++ b/src/renderer/components/grid-carousel/grid-carousel.tsx @@ -142,19 +142,19 @@ export const SwiperGridCarousel = ({ const { id, isFavorite, itemType, serverId } = options; if (isFavorite) { deleteFavoriteMutation.mutate({ + apiClientProps: { serverId }, query: { id, type: itemType, }, - serverId, }); } else { createFavoriteMutation.mutate({ + apiClientProps: { serverId }, query: { id, type: itemType, }, - serverId, }); } }, diff --git a/src/renderer/components/virtual-table/cells/favorite-cell.tsx b/src/renderer/components/virtual-table/cells/favorite-cell.tsx index 71ca889d..6213d3b1 100644 --- a/src/renderer/components/virtual-table/cells/favorite-cell.tsx +++ b/src/renderer/components/virtual-table/cells/favorite-cell.tsx @@ -15,11 +15,11 @@ export const FavoriteCell = ({ data, node, value }: ICellRendererParams) => { if (newFavoriteValue) { createMutation.mutate( { + apiClientProps: { serverId: data.serverId }, query: { id: [data.id], type: data.itemType, }, - serverId: data.serverId, }, { onSuccess: () => { @@ -30,11 +30,11 @@ export const FavoriteCell = ({ data, node, value }: ICellRendererParams) => { } else { deleteMutation.mutate( { + apiClientProps: { serverId: data.serverId }, query: { id: [data.id], type: data.itemType, }, - serverId: data.serverId, }, { onSuccess: () => { diff --git a/src/renderer/components/virtual-table/cells/rating-cell.tsx b/src/renderer/components/virtual-table/cells/rating-cell.tsx index 266a4265..4d82ff6b 100644 --- a/src/renderer/components/virtual-table/cells/rating-cell.tsx +++ b/src/renderer/components/virtual-table/cells/rating-cell.tsx @@ -10,11 +10,11 @@ export const RatingCell = ({ node, value }: ICellRendererParams) => { const handleUpdateRating = (rating: number) => { updateRatingMutation.mutate( { + apiClientProps: { serverId: value?.serverId || '' }, query: { item: [value], rating, }, - serverId: value?.serverId, }, { onSuccess: () => { diff --git a/src/renderer/components/virtual-table/hooks/use-virtual-table.ts b/src/renderer/components/virtual-table/hooks/use-virtual-table.ts index f7e158c3..ec105303 100644 --- a/src/renderer/components/virtual-table/hooks/use-virtual-table.ts +++ b/src/renderer/components/virtual-table/hooks/use-virtual-table.ts @@ -168,7 +168,7 @@ export const useVirtualTable = >({ queryFn: async ({ signal }) => { const res = await queryFn!({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: { diff --git a/src/renderer/features/action-required/components/server-required.tsx b/src/renderer/features/action-required/components/server-required.tsx index 4d950e79..b247dcd5 100644 --- a/src/renderer/features/action-required/components/server-required.tsx +++ b/src/renderer/features/action-required/components/server-required.tsx @@ -18,7 +18,11 @@ import { Icon } from '/@/shared/components/icon/icon'; import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area'; import { Stack } from '/@/shared/components/stack/stack'; import { Text } from '/@/shared/components/text/text'; -import { ServerListItem, ServerType } from '/@/shared/types/domain-types'; +import { + ServerListItem, + ServerListItemWithCredential, + ServerType, +} from '/@/shared/types/domain-types'; const localSettings = isElectron() ? window.api.localSettings : null; @@ -69,7 +73,7 @@ function ServerSelector() { const currentServer = useCurrentServer(); const { setCurrentServer } = useAuthStoreActions(); - const handleSetCurrentServer = (server: ServerListItem) => { + const handleSetCurrentServer = (server: ServerListItemWithCredential) => { navigate(AppRoute.HOME); setCurrentServer(server); }; diff --git a/src/renderer/features/action-required/routes/action-required-route.tsx b/src/renderer/features/action-required/routes/action-required-route.tsx index 2fa1c901..39b7bbad 100644 --- a/src/renderer/features/action-required/routes/action-required-route.tsx +++ b/src/renderer/features/action-required/routes/action-required-route.tsx @@ -9,7 +9,7 @@ import { ServerRequired } from '/@/renderer/features/action-required/components/ import { ServerList } from '/@/renderer/features/servers/components/server-list'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer } from '/@/renderer/store'; +import { useCurrentServerWithCredential } from '/@/renderer/store'; import { Button } from '/@/shared/components/button/button'; import { Center } from '/@/shared/components/center/center'; import { Group } from '/@/shared/components/group/group'; @@ -18,7 +18,7 @@ import { Stack } from '/@/shared/components/stack/stack'; const ActionRequiredRoute = () => { const { t } = useTranslation(); - const currentServer = useCurrentServer(); + const currentServer = useCurrentServerWithCredential(); const isServerRequired = !currentServer; const isCredentialRequired = currentServer && !currentServer.credential; diff --git a/src/renderer/features/albums/api/album-api.ts b/src/renderer/features/albums/api/album-api.ts index 07d9e520..933eae8b 100644 --- a/src/renderer/features/albums/api/album-api.ts +++ b/src/renderer/features/albums/api/album-api.ts @@ -3,7 +3,6 @@ import { queryOptions } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { QueryHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { AlbumDetailQuery, AlbumListQuery, ListCountQuery } from '/@/shared/types/domain-types'; export const albumQueries = { @@ -11,7 +10,7 @@ export const albumQueries = { return queryOptions({ queryFn: ({ signal }) => { return api.controller.getAlbumDetail({ - apiClientProps: { server: getServerById(args.serverId), signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, @@ -23,7 +22,7 @@ export const albumQueries = { return queryOptions({ queryFn: ({ signal }) => { return api.controller.getAlbumList({ - apiClientProps: { server: getServerById(args.serverId), signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, @@ -39,7 +38,7 @@ export const albumQueries = { return queryOptions({ queryFn: ({ signal }) => { return api.controller.getAlbumListCount({ - apiClientProps: { server: getServerById(args.serverId), signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, diff --git a/src/renderer/features/albums/components/album-detail-content.tsx b/src/renderer/features/albums/components/album-detail-content.tsx index b1ed7665..a78d7421 100644 --- a/src/renderer/features/albums/components/album-detail-content.tsx +++ b/src/renderer/features/albums/components/album-detail-content.tsx @@ -290,19 +290,19 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP if (detailQuery.data.userFavorite) { deleteFavoriteMutation.mutate({ + apiClientProps: { serverId: detailQuery.data.serverId }, query: { id: [detailQuery.data.id], type: LibraryItem.ALBUM, }, - serverId: detailQuery.data.serverId, }); } else { createFavoriteMutation.mutate({ + apiClientProps: { serverId: detailQuery.data.serverId }, query: { id: [detailQuery.data.id], type: LibraryItem.ALBUM, }, - serverId: detailQuery.data.serverId, }); } }; diff --git a/src/renderer/features/albums/components/album-detail-header.tsx b/src/renderer/features/albums/components/album-detail-header.tsx index 2c47e314..4c9a18cf 100644 --- a/src/renderer/features/albums/components/album-detail-header.tsx +++ b/src/renderer/features/albums/components/album-detail-header.tsx @@ -118,11 +118,11 @@ export const AlbumDetailHeader = forwardRef( if (!detailQuery?.data) return; updateRatingMutation.mutate({ + apiClientProps: { serverId: detailQuery.data.serverId }, query: { item: [detailQuery.data], rating, }, - serverId: detailQuery.data.serverId, }); }; diff --git a/src/renderer/features/albums/components/album-list-grid-view.tsx b/src/renderer/features/albums/components/album-list-grid-view.tsx index 20b57cf1..7adae523 100644 --- a/src/renderer/features/albums/components/album-list-grid-view.tsx +++ b/src/renderer/features/albums/components/album-list-grid-view.tsx @@ -35,7 +35,7 @@ export const AlbumListGridView = ({ gridRef, itemCount }: any) => { const scrollOffset = searchParams.get('scrollOffset'); const initialScrollOffset = Number(id ? scrollOffset : grid?.scrollOffset) || 0; - const handleFavorite = useHandleFavorite({ gridRef, server }); + const handleFavorite = useHandleFavorite({ gridRef }); const cardRows = useMemo(() => { const rows: CardRow[] = [ALBUM_CARD_ROWS.name]; @@ -177,7 +177,7 @@ export const AlbumListGridView = ({ gridRef, itemCount }: any) => { queryFn: async ({ signal }) => controller.getAlbumList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query, diff --git a/src/renderer/features/albums/components/navidrome-album-filters.tsx b/src/renderer/features/albums/components/navidrome-album-filters.tsx index a31efc6e..250cb376 100644 --- a/src/renderer/features/albums/components/navidrome-album-filters.tsx +++ b/src/renderer/features/albums/components/navidrome-album-filters.tsx @@ -12,7 +12,7 @@ import { genresQueries } from '/@/renderer/features/genres/api/genres-api'; import { sharedQueries } from '/@/renderer/features/shared/api/shared-api'; import { AlbumListFilter, - getServerById, + useCurrentServer, useListStoreActions, useListStoreByKey, } from '/@/renderer/store'; @@ -53,7 +53,7 @@ export const NavidromeAlbumFilters = ({ const { t } = useTranslation(); const { filter } = useListStoreByKey({ key: pageKey }); const { setFilter } = useListStoreActions(); - const server = getServerById(serverId); + const server = useCurrentServer(); const genreListQuery = useQuery( genresQueries.list({ diff --git a/src/renderer/features/albums/routes/album-list-route.tsx b/src/renderer/features/albums/routes/album-list-route.tsx index f39cfe31..76d2ded6 100644 --- a/src/renderer/features/albums/routes/album-list-route.tsx +++ b/src/renderer/features/albums/routes/album-list-route.tsx @@ -107,7 +107,7 @@ const AlbumListRoute = () => { const albumListRes = await queryClient.fetchQuery({ queryFn: ({ signal }) => { return api.controller.getAlbumList({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: server?.id || '', signal }, query, }); }, diff --git a/src/renderer/features/albums/routes/dummy-album-detail-route.tsx b/src/renderer/features/albums/routes/dummy-album-detail-route.tsx index a007ff42..1948f6e2 100644 --- a/src/renderer/features/albums/routes/dummy-album-detail-route.tsx +++ b/src/renderer/features/albums/routes/dummy-album-detail-route.tsx @@ -42,9 +42,8 @@ const DummyAlbumDetailRoute = () => { const queryKey = queryKeys.songs.detail(server?.id || '', albumId); const detailQuery = useQuery({ queryFn: ({ signal }) => { - if (!server) throw new Error('Server not found'); return api.controller.getSongDetail({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: server?.id || '', signal }, query: { id: albumId }, }); }, @@ -70,19 +69,19 @@ const DummyAlbumDetailRoute = () => { try { if (wasFavorite) { await deleteFavoriteMutation.mutateAsync({ + apiClientProps: { serverId: detailQuery.data.serverId }, query: { id: [detailQuery.data.id], type: LibraryItem.SONG, }, - serverId: detailQuery.data.serverId, }); } else { await createFavoriteMutation.mutateAsync({ + apiClientProps: { serverId: detailQuery.data.serverId }, query: { id: [detailQuery.data.id], type: LibraryItem.SONG, }, - serverId: detailQuery.data.serverId, }); } diff --git a/src/renderer/features/artists/api/artists-api.ts b/src/renderer/features/artists/api/artists-api.ts index 853a843c..cc8f07ca 100644 --- a/src/renderer/features/artists/api/artists-api.ts +++ b/src/renderer/features/artists/api/artists-api.ts @@ -3,7 +3,6 @@ import { queryOptions } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { QueryHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { AlbumArtistDetailQuery, AlbumArtistListQuery, @@ -17,11 +16,11 @@ export const artistsQueries = { return queryOptions({ queryFn: ({ signal }) => { return api.controller.getAlbumArtistDetail({ - apiClientProps: { server: getServerById(args.serverId), signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, - queryKey: queryKeys.albumArtists.detail(args.serverId || '', args.query), + queryKey: queryKeys.albumArtists.detail(args.serverId, args.query), ...args.options, }); }, @@ -29,11 +28,11 @@ export const artistsQueries = { return queryOptions({ queryFn: ({ signal }) => { return api.controller.getAlbumArtistList({ - apiClientProps: { server: getServerById(args.serverId), signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, - queryKey: queryKeys.albumArtists.list(args.serverId || '', args.query), + queryKey: queryKeys.albumArtists.list(args.serverId, args.query), ...args.options, }); }, @@ -41,12 +40,12 @@ export const artistsQueries = { return queryOptions({ queryFn: ({ signal }) => { return api.controller.getAlbumArtistListCount({ - apiClientProps: { server: getServerById(args.serverId), signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, queryKey: queryKeys.albumArtists.count( - args.serverId || '', + args.serverId, Object.keys(args.query).length === 0 ? undefined : args.query, ), ...args.options, @@ -56,12 +55,12 @@ export const artistsQueries = { return queryOptions({ queryFn: ({ signal }) => { return api.controller.getArtistListCount({ - apiClientProps: { server: getServerById(args.serverId), signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, queryKey: queryKeys.albumArtists.count( - args.serverId || '', + args.serverId, Object.keys(args.query).length === 0 ? undefined : args.query, ), ...args.options, @@ -71,11 +70,11 @@ export const artistsQueries = { return queryOptions({ queryFn: ({ signal }) => { return api.controller.getTopSongs({ - apiClientProps: { server: getServerById(args.serverId), signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, - queryKey: queryKeys.albumArtists.topSongs(args.serverId || '', args.query), + queryKey: queryKeys.albumArtists.topSongs(args.serverId, args.query), ...args.options, }); }, diff --git a/src/renderer/features/artists/components/album-artist-detail-content.tsx b/src/renderer/features/artists/components/album-artist-detail-content.tsx index fbbf9815..7ea276c9 100644 --- a/src/renderer/features/artists/components/album-artist-detail-content.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-content.tsx @@ -311,19 +311,19 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten if (detailQuery.data.userFavorite) { deleteFavoriteMutation.mutate({ + apiClientProps: { serverId: detailQuery.data.serverId }, query: { id: [detailQuery.data.id], type: LibraryItem.ALBUM_ARTIST, }, - serverId: detailQuery.data.serverId, }); } else { createFavoriteMutation.mutate({ + apiClientProps: { serverId: detailQuery.data.serverId }, query: { id: [detailQuery.data.id], type: LibraryItem.ALBUM_ARTIST, }, - serverId: detailQuery.data.serverId, }); } }; diff --git a/src/renderer/features/artists/components/album-artist-detail-header.tsx b/src/renderer/features/artists/components/album-artist-detail-header.tsx index 26d56e29..f9634c64 100644 --- a/src/renderer/features/artists/components/album-artist-detail-header.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-header.tsx @@ -71,11 +71,11 @@ export const AlbumArtistDetailHeader = forwardRef( if (!detailQuery?.data) return; updateRatingMutation.mutate({ + apiClientProps: { serverId: detailQuery?.data.serverId }, query: { item: [detailQuery.data], rating, }, - serverId: detailQuery?.data.serverId, }); }; diff --git a/src/renderer/features/artists/components/album-artist-list-grid-view.tsx b/src/renderer/features/artists/components/album-artist-list-grid-view.tsx index f6227889..ae9116cb 100644 --- a/src/renderer/features/artists/components/album-artist-list-grid-view.tsx +++ b/src/renderer/features/artists/components/album-artist-list-grid-view.tsx @@ -38,7 +38,7 @@ export const AlbumArtistListGridView = ({ gridRef, itemCount }: AlbumArtistListG const { pageKey } = useListContext(); const { display, filter, grid } = useListStoreByKey({ key: pageKey }); const { setGrid } = useListStoreActions(); - const handleFavorite = useHandleFavorite({ gridRef, server }); + const handleFavorite = useHandleFavorite({ gridRef }); const fetchInitialData = useCallback(() => { const query: Omit = { @@ -89,7 +89,7 @@ export const AlbumArtistListGridView = ({ gridRef, itemCount }: AlbumArtistListG queryFn: async ({ signal }) => api.controller.getAlbumArtistList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query, diff --git a/src/renderer/features/artists/components/album-artist-list-header-filters.tsx b/src/renderer/features/artists/components/album-artist-list-header-filters.tsx index 3ae74973..42c8bea1 100644 --- a/src/renderer/features/artists/components/album-artist-list-header-filters.tsx +++ b/src/renderer/features/artists/components/album-artist-list-header-filters.tsx @@ -184,7 +184,7 @@ export const AlbumArtistListHeaderFilters = ({ queryFn: async ({ signal }) => api.controller.getAlbumArtistList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: { @@ -220,7 +220,7 @@ export const AlbumArtistListHeaderFilters = ({ queryFn: async ({ signal }) => api.controller.getAlbumArtistList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: { diff --git a/src/renderer/features/artists/components/artist-list-grid-view.tsx b/src/renderer/features/artists/components/artist-list-grid-view.tsx index d87a539b..47900da2 100644 --- a/src/renderer/features/artists/components/artist-list-grid-view.tsx +++ b/src/renderer/features/artists/components/artist-list-grid-view.tsx @@ -39,7 +39,7 @@ export const ArtistListGridView = ({ gridRef, itemCount }: ArtistListGridViewPro const { pageKey } = useListContext(); const { display, filter, grid } = useListStoreByKey({ key: pageKey }); const { setGrid } = useListStoreActions(); - const handleFavorite = useHandleFavorite({ gridRef, server }); + const handleFavorite = useHandleFavorite({ gridRef }); const fetchInitialData = useCallback(() => { const query: Omit = { @@ -90,7 +90,7 @@ export const ArtistListGridView = ({ gridRef, itemCount }: ArtistListGridViewPro queryFn: async ({ signal }) => api.controller.getArtistList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query, diff --git a/src/renderer/features/artists/components/artist-list-header-filters.tsx b/src/renderer/features/artists/components/artist-list-header-filters.tsx index 0f572555..094f5936 100644 --- a/src/renderer/features/artists/components/artist-list-header-filters.tsx +++ b/src/renderer/features/artists/components/artist-list-header-filters.tsx @@ -191,7 +191,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF queryFn: async ({ signal }) => api.controller.getArtistList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: { @@ -227,7 +227,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF queryFn: async ({ signal }) => api.controller.getArtistList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: { diff --git a/src/renderer/features/context-menu/context-menu-provider.tsx b/src/renderer/features/context-menu/context-menu-provider.tsx index 5df98770..e2eade4c 100644 --- a/src/renderer/features/context-menu/context-menu-provider.tsx +++ b/src/renderer/features/context-menu/context-menu-provider.tsx @@ -39,7 +39,6 @@ import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete- import { useSetRating } from '/@/renderer/features/shared/mutations/set-rating-mutation'; import { AppRoute } from '/@/renderer/router/routes'; import { - getServerById, useAuthStore, useCurrentServer, usePlayerStore, @@ -260,7 +259,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const handleDeletePlaylist = useCallback(() => { for (const item of ctx.data) { deletePlaylistMutation?.mutate( - { query: { id: item.id }, serverId: item.serverId }, + { apiClientProps: { serverId: item.serverId }, query: { id: item.id } }, { onError: (err) => { toast.error({ @@ -330,11 +329,11 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { createFavoriteMutation.mutate( { + apiClientProps: { serverId }, query: { id: items.map((item) => item.id), type: ctx.type, }, - serverId, }, { onError: (err) => { @@ -369,11 +368,11 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { createFavoriteMutation.mutate( { + apiClientProps: { serverId }, query: { id: items.map((item: AnyLibraryItem) => item.id), type: ctx.type, }, - serverId, }, { onError: (err) => { @@ -408,11 +407,11 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const idsToUnfavorite = nodesByServerId[serverId].map((node) => node.data.id); deleteFavoriteMutation.mutate( { + apiClientProps: { serverId }, query: { id: idsToUnfavorite, type: ctx.type, }, - serverId, }, { onSuccess: () => { @@ -441,11 +440,11 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { (item: AnyLibraryItem) => item.id, ); deleteFavoriteMutation.mutate({ + apiClientProps: { serverId }, query: { id: idsToUnfavorite, type: ctx.type, }, - serverId, }); } } @@ -529,11 +528,11 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const confirm = () => { removeFromPlaylistMutation.mutate( { + apiClientProps: { serverId: ctx.data?.[0]?.serverId }, query: { id: ctx.context.playlistId, songId: songId || [], }, - serverId: ctx.data?.[0]?.serverId, }, { onError: (err) => { @@ -604,11 +603,11 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { updateRatingMutation.mutate( { + apiClientProps: { serverId }, query: { item: items, rating: ratingToSet, }, - serverId, }, { onSuccess: () => { @@ -719,7 +718,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const item = ctx.data[0]; const songs = await controller.getSimilarSongs({ apiClientProps: { - server: getServerById(item.serverId), + serverId: item.serverId, signal: undefined, }, query: { albumArtistIds: item.albumArtistIds, songId: item.id }, @@ -732,7 +731,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const handleDownload = useCallback(() => { const item = ctx.data[0]; const url = api.controller.getDownloadUrl({ - apiClientProps: { server }, + apiClientProps: { serverId: item.serverId }, query: { id: item.id }, }); @@ -741,7 +740,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { } else { window.open(url, '_blank'); } - }, [ctx.data, server]); + }, [ctx.data]); const handleGoToAlbum = useCallback(() => { const item = ctx.data[0]; diff --git a/src/renderer/features/discord-rpc/use-discord-rpc.ts b/src/renderer/features/discord-rpc/use-discord-rpc.ts index 75f95a22..b9c599bc 100644 --- a/src/renderer/features/discord-rpc/use-discord-rpc.ts +++ b/src/renderer/features/discord-rpc/use-discord-rpc.ts @@ -6,7 +6,6 @@ import { controller } from '/@/renderer/api/controller'; import { DiscordDisplayType, DiscordLinkType, - getServerById, useAppStore, useDiscordSettings, useGeneralSettings, @@ -123,11 +122,9 @@ export const useDiscordRpc = () => { if (song.serverType === ServerType.JELLYFIN && song.imageUrl) { activity.largeImageKey = song.imageUrl; } else if (song.serverType === ServerType.NAVIDROME) { - const server = getServerById(song.serverId); - try { const info = await controller.getAlbumInfo({ - apiClientProps: { server }, + apiClientProps: { serverId: song.serverId }, query: { id: song.albumId }, }); diff --git a/src/renderer/features/genres/api/genres-api.ts b/src/renderer/features/genres/api/genres-api.ts index bca0a4fb..e69be174 100644 --- a/src/renderer/features/genres/api/genres-api.ts +++ b/src/renderer/features/genres/api/genres-api.ts @@ -3,7 +3,6 @@ import { queryOptions } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { QueryHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { GenreListQuery } from '/@/shared/types/domain-types'; export const genresQueries = { @@ -11,14 +10,12 @@ export const genresQueries = { return queryOptions({ gcTime: 1000 * 5, queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return api.controller.getGenreList({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, - queryKey: queryKeys.genres.list(args.serverId || '', args.query), + queryKey: queryKeys.genres.list(args.serverId, args.query), ...args.options, }); }, diff --git a/src/renderer/features/genres/components/genre-list-grid-view.tsx b/src/renderer/features/genres/components/genre-list-grid-view.tsx index 3bd31eab..6747c1ac 100644 --- a/src/renderer/features/genres/components/genre-list-grid-view.tsx +++ b/src/renderer/features/genres/components/genre-list-grid-view.tsx @@ -109,7 +109,7 @@ export const GenreListGridView = ({ gridRef, itemCount }: any) => { queryFn: async ({ signal }) => { return api.controller.getGenreList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query, diff --git a/src/renderer/features/home/api/home-api.ts b/src/renderer/features/home/api/home-api.ts index 783287bc..34348cb2 100644 --- a/src/renderer/features/home/api/home-api.ts +++ b/src/renderer/features/home/api/home-api.ts @@ -3,7 +3,6 @@ import { queryOptions } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { QueryHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { AlbumListQuery, AlbumListSort, SortOrder } from '/@/shared/types/domain-types'; export const homeQueries = { @@ -18,14 +17,12 @@ export const homeQueries = { return queryOptions({ queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return api.controller.getAlbumList({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: requestQuery, }); }, - queryKey: queryKeys.albums.list(args.serverId || '', requestQuery), + queryKey: queryKeys.albums.list(args.serverId, requestQuery), ...args.options, }); }, diff --git a/src/renderer/features/lyrics/api/lyrics-api.ts b/src/renderer/features/lyrics/api/lyrics-api.ts index cc3893e4..e49b4c5d 100644 --- a/src/renderer/features/lyrics/api/lyrics-api.ts +++ b/src/renderer/features/lyrics/api/lyrics-api.ts @@ -87,11 +87,11 @@ export const lyricsQueries = { // This should only be called for Jellyfin. Return null to ignore errors if (server.type !== ServerType.JELLYFIN) return null; return api.controller.getLyrics({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, - queryKey: queryKeys.songs.lyrics(args.serverId || '', args.query), + queryKey: queryKeys.songs.lyrics(args.serverId, args.query), ...args.options, }); }, @@ -111,7 +111,7 @@ export const lyricsQueries = { if (hasFeature(server, ServerFeature.LYRICS_MULTIPLE_STRUCTURED)) { const subsonicLyrics = await api.controller .getStructuredLyrics({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: { songId: song.id }, }) .catch(console.error); @@ -122,7 +122,7 @@ export const lyricsQueries = { } else if (hasFeature(server, ServerFeature.LYRICS_SINGLE_STRUCTURED)) { const jfLyrics = await api.controller .getLyrics({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: { songId: song.id }, }) .catch((err) => console.error(err)); @@ -175,7 +175,7 @@ export const lyricsQueries = { return null; }, - queryKey: queryKeys.songs.lyrics(args.serverId || '', args.query), + queryKey: queryKeys.songs.lyrics(args.serverId, args.query), staleTime: Infinity, ...args.options, }); diff --git a/src/renderer/features/player/components/right-controls.tsx b/src/renderer/features/player/components/right-controls.tsx index 1c2b7694..a6bc4bed 100644 --- a/src/renderer/features/player/components/right-controls.tsx +++ b/src/renderer/features/player/components/right-controls.tsx @@ -62,11 +62,11 @@ export const RightControls = () => { if (!song?.id) return; addToFavoritesMutation.mutate({ + apiClientProps: { serverId: song?.serverId || '' }, query: { id: [song.id], type: LibraryItem.SONG, }, - serverId: song?.serverId, }); }; @@ -74,11 +74,11 @@ export const RightControls = () => { if (!currentSong) return; updateRatingMutation.mutate({ + apiClientProps: { serverId: currentSong?.serverId || '' }, query: { item: [currentSong], rating, }, - serverId: currentSong?.serverId, }); }; @@ -86,11 +86,11 @@ export const RightControls = () => { if (!song?.id) return; removeFromFavoritesMutation.mutate({ + apiClientProps: { serverId: song?.serverId || '' }, query: { id: [song.id], type: LibraryItem.SONG, }, - serverId: song?.serverId, }); }; @@ -163,16 +163,17 @@ export const RightControls = () => { remote.requestFavorite((_event, { favorite, id, serverId }) => { const mutator = favorite ? addToFavoritesMutation : removeFromFavoritesMutation; mutator.mutate({ + apiClientProps: { serverId }, query: { id: [id], type: LibraryItem.SONG, }, - serverId, }); }); remote.requestRating((_event, { id, rating, serverId }) => { updateRatingMutation.mutate({ + apiClientProps: { serverId }, query: { item: [ { @@ -183,7 +184,6 @@ export const RightControls = () => { ], rating, }, - serverId, }); }); diff --git a/src/renderer/features/player/components/shuffle-all-modal.tsx b/src/renderer/features/player/components/shuffle-all-modal.tsx index 8f72abf4..717bf3f7 100644 --- a/src/renderer/features/player/components/shuffle-all-modal.tsx +++ b/src/renderer/features/player/components/shuffle-all-modal.tsx @@ -98,7 +98,7 @@ export const ShuffleAllModal = ({ queryFn: ({ signal }) => api.controller.getRandomSongList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: { @@ -257,7 +257,7 @@ export const openShuffleAllModal = async ( queryFn: ({ signal }) => api.controller.getGenreList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: { @@ -275,7 +275,7 @@ export const openShuffleAllModal = async ( queryFn: ({ signal }) => api.controller.getMusicFolderList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, }), diff --git a/src/renderer/features/player/hooks/use-media-session.ts b/src/renderer/features/player/hooks/use-media-session.ts index 94fba8ea..3bcdfc00 100644 --- a/src/renderer/features/player/hooks/use-media-session.ts +++ b/src/renderer/features/player/hooks/use-media-session.ts @@ -39,22 +39,18 @@ export const useMediaSession = ({ } mediaSession.setActionHandler('nexttrack', () => { - console.log('nexttrack'); handleNextTrack(); }); mediaSession.setActionHandler('pause', () => { - console.log('pause'); handlePause(); }); mediaSession.setActionHandler('play', () => { - console.log('play'); handlePlay(); }); mediaSession.setActionHandler('previoustrack', () => { - console.log('previoustrack'); handlePrevTrack(); }); diff --git a/src/renderer/features/player/hooks/use-scrobble.ts b/src/renderer/features/player/hooks/use-scrobble.ts index d7f21856..5c3ce5a3 100644 --- a/src/renderer/features/player/hooks/use-scrobble.ts +++ b/src/renderer/features/player/hooks/use-scrobble.ts @@ -75,13 +75,13 @@ export const useScrobble = () => { currentSong?.serverType === ServerType.JELLYFIN ? currentTime * 1e7 : undefined; sendScrobble.mutate({ + apiClientProps: { serverId: currentSong?.serverId || '' }, query: { event: 'timeupdate', id: currentSong.id, position, submission: false, }, - serverId: currentSong?.serverId, }); }, [isScrobbleEnabled, isPrivateModeEnabled, sendScrobble], @@ -149,12 +149,12 @@ export const useScrobble = () => { : undefined; sendScrobble.mutate({ + apiClientProps: { serverId: previousSong?.serverId || '' }, query: { id: previousSong.id, position, submission: true, }, - serverId: previousSong?.serverId, }); } } @@ -173,13 +173,13 @@ export const useScrobble = () => { // Send start scrobble when song changes and the new song is playing if (currentStatus === PlayerStatus.PLAYING && currentSong?.id) { sendScrobble.mutate({ + apiClientProps: { serverId: currentSong?.serverId || '' }, query: { event: 'start', id: currentSong.id, position: 0, submission: false, }, - serverId: currentSong?.serverId, }); if (currentSong?.serverType === ServerType.JELLYFIN) { @@ -228,13 +228,13 @@ export const useScrobble = () => { // Whenever the player is restarted, send a 'start' scrobble if (currentStatus === PlayerStatus.PLAYING) { sendScrobble.mutate({ + apiClientProps: { serverId: currentSong?.serverId || '' }, query: { event: 'unpause', id: currentSong.id, position, submission: false, }, - serverId: currentSong?.serverId, }); if (currentSong?.serverType === ServerType.JELLYFIN) { @@ -253,13 +253,13 @@ export const useScrobble = () => { // Jellyfin is the only one that needs to send a 'pause' event to the server } else if (currentSong?.serverType === ServerType.JELLYFIN) { sendScrobble.mutate({ + apiClientProps: { serverId: currentSong?.serverId || '' }, query: { event: 'pause', id: currentSong.id, position, submission: false, }, - serverId: currentSong?.serverId, }); if (progressIntervalId.current) { @@ -287,11 +287,11 @@ export const useScrobble = () => { if (!isCurrentSongScrobbled && shouldSubmitScrobble) { sendScrobble.mutate({ + apiClientProps: { serverId: currentSong?.serverId || '' }, query: { id: currentSong.id, submission: true, }, - serverId: currentSong?.serverId, }); setIsCurrentSongScrobbled(true); @@ -332,24 +332,24 @@ export const useScrobble = () => { if (!isCurrentSongScrobbled && shouldSubmitScrobble) { sendScrobble.mutate({ + apiClientProps: { serverId: currentSong?.serverId || '' }, query: { id: currentSong.id, position, submission: true, }, - serverId: currentSong?.serverId, }); } if (currentSong?.serverType === ServerType.JELLYFIN) { sendScrobble.mutate({ + apiClientProps: { serverId: currentSong?.serverId || '' }, query: { event: 'start', id: currentSong.id, position: 0, submission: false, }, - serverId: currentSong?.serverId, }); } diff --git a/src/renderer/features/player/mutations/scrobble-mutation.ts b/src/renderer/features/player/mutations/scrobble-mutation.ts index ca835734..a96efa80 100644 --- a/src/renderer/features/player/mutations/scrobble-mutation.ts +++ b/src/renderer/features/player/mutations/scrobble-mutation.ts @@ -3,7 +3,7 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { MutationOptions } from '/@/renderer/lib/react-query'; -import { getServerById, useIncrementQueuePlayCount } from '/@/renderer/store'; +import { useIncrementQueuePlayCount } from '/@/renderer/store'; import { usePlayEvent } from '/@/renderer/store/event.store'; import { ScrobbleArgs, ScrobbleResponse } from '/@/shared/types/domain-types'; @@ -11,16 +11,12 @@ export const useSendScrobble = (options?: MutationOptions) => { const incrementPlayCount = useIncrementQueuePlayCount(); const sendPlayEvent = usePlayEvent(); - return useMutation< - ScrobbleResponse, - AxiosError, - Omit, - null - >({ + return useMutation({ mutationFn: (args) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.scrobble({ ...args, apiClientProps: { server } }); + return api.controller.scrobble({ + ...args, + apiClientProps: { serverId: args.apiClientProps.serverId }, + }); }, onSuccess: (_data, variables) => { // Manually increment the play count for the song in the queue if scrobble was submitted diff --git a/src/renderer/features/player/utils.ts b/src/renderer/features/player/utils.ts index be0136ab..f470b20f 100644 --- a/src/renderer/features/player/utils.ts +++ b/src/renderer/features/player/utils.ts @@ -33,7 +33,7 @@ export const getPlaylistSongsById = async (args: { queryFn: async ({ signal }) => api.controller.getPlaylistSongList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: queryFilter, @@ -77,7 +77,7 @@ export const getAlbumSongsById = async (args: { queryFn: async ({ signal }) => api.controller.getSongList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: queryFilter, @@ -119,7 +119,7 @@ export const getGenreSongsById = async (args: { queryFn: async ({ signal }) => api.controller.getSongList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: queryFilter, @@ -161,7 +161,7 @@ export const getAlbumArtistSongsById = async (args: { queryFn: async ({ signal }) => api.controller.getSongList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: queryFilter, @@ -196,7 +196,7 @@ export const getArtistSongsById = async (args: { queryFn: async ({ signal }) => api.controller.getSongList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: queryFilter, @@ -229,7 +229,7 @@ export const getSongsByQuery = async (args: { queryFn: async ({ signal }) => { return api.controller.getSongList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: queryFilter, @@ -258,7 +258,7 @@ export const getSongById = async (args: { queryFn: async ({ signal }) => api.controller.getSongDetail({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: queryFilter, diff --git a/src/renderer/features/playlists/api/playlists-api.ts b/src/renderer/features/playlists/api/playlists-api.ts index 635f2fa8..3f683b70 100644 --- a/src/renderer/features/playlists/api/playlists-api.ts +++ b/src/renderer/features/playlists/api/playlists-api.ts @@ -3,7 +3,6 @@ import { queryOptions } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { QueryHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { PlaylistDetailQuery, PlaylistListQuery, @@ -14,14 +13,12 @@ export const playlistsQueries = { detail: (args: QueryHookArgs) => { return queryOptions({ queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return api.controller.getPlaylistDetail({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, - queryKey: queryKeys.playlists.detail(args.serverId || '', args.query.id, args.query), + queryKey: queryKeys.playlists.detail(args.serverId, args.query.id, args.query), ...args.options, }); }, @@ -29,10 +26,8 @@ export const playlistsQueries = { return queryOptions({ gcTime: 1000 * 60 * 60, queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return api.controller.getPlaylistList({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, @@ -43,10 +38,8 @@ export const playlistsQueries = { songList: (args: QueryHookArgs) => { return queryOptions({ queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return api.controller.getPlaylistSongList({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, diff --git a/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx b/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx index f99fa59b..f2f957a6 100644 --- a/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx +++ b/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx @@ -120,7 +120,7 @@ export const AddToPlaylistContextModal = ({ queryFn: ({ signal }) => { if (!server) throw new Error('No server'); return api.controller.getSongList({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: server?.id || '', signal }, query, }); }, @@ -147,7 +147,7 @@ export const AddToPlaylistContextModal = ({ queryFn: ({ signal }) => { if (!server) throw new Error('No server'); return api.controller.getSongList({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: server?.id || '', signal }, query, }); }, @@ -203,7 +203,7 @@ export const AddToPlaylistContextModal = ({ for (const playlist of values.newPlaylists) { try { const response = await api.controller.createPlaylist({ - apiClientProps: { server }, + apiClientProps: { serverId: server?.id || '' }, body: { name: playlist, public: false, @@ -238,7 +238,7 @@ export const AddToPlaylistContextModal = ({ ); return api.controller.getPlaylistSongList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: { @@ -266,9 +266,9 @@ export const AddToPlaylistContextModal = ({ } addToPlaylistMutation.mutate( { + apiClientProps: { serverId: server.id }, body: { songId: values.skipDuplicates ? uniqueSongIds : allSongIds }, query: { id: playlistId }, - serverId: server?.id, }, { onError: (err) => { diff --git a/src/renderer/features/playlists/components/create-playlist-form.tsx b/src/renderer/features/playlists/components/create-playlist-form.tsx index aaef9255..5d1fe5e7 100644 --- a/src/renderer/features/playlists/components/create-playlist-form.tsx +++ b/src/renderer/features/playlists/components/create-playlist-form.tsx @@ -58,6 +58,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => { mutation.mutate( { + apiClientProps: { serverId: server.id }, body: { ...values, _custom: { @@ -75,7 +76,6 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => { }, }, }, - serverId: server.id, }, { onError: (err) => { diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx index 7fca0633..4e355f12 100644 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx @@ -111,7 +111,7 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai try { await api.controller.movePlaylistItem({ apiClientProps: { - server, + serverId: server?.id || '', }, query: { endingIndex: e.overIndex, diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx index 9d30839a..6661ba1d 100644 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx @@ -376,7 +376,10 @@ export const PlaylistDetailSongListHeaderFilters = ({ const handleDeletePlaylist = useCallback(() => { if (!detailQuery.data) return; deletePlaylistMutation?.mutate( - { query: { id: detailQuery.data.id }, serverId: detailQuery.data.serverId }, + { + apiClientProps: { serverId: detailQuery.data.serverId }, + query: { id: detailQuery.data.id }, + }, { onError: (err) => { toast.error({ diff --git a/src/renderer/features/playlists/components/playlist-list-grid-view.tsx b/src/renderer/features/playlists/components/playlist-list-grid-view.tsx index 9d834e2b..1952be18 100644 --- a/src/renderer/features/playlists/components/playlist-list-grid-view.tsx +++ b/src/renderer/features/playlists/components/playlist-list-grid-view.tsx @@ -38,7 +38,7 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie const handlePlayQueueAdd = usePlayQueueAdd(); const { display, filter, grid } = useListStoreByKey({ key: pageKey }); const { setGrid } = useListStoreActions(); - const handleFavorite = useHandleFavorite({ gridRef, server }); + const handleFavorite = useHandleFavorite({ gridRef }); const cardRows = useMemo(() => { const rows: CardRow[] = [PLAYLIST_CARD_ROWS.nameFull]; @@ -126,7 +126,7 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie queryFn: async ({ signal }) => controller.getPlaylistList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query, diff --git a/src/renderer/features/playlists/components/playlist-list-header-filters.tsx b/src/renderer/features/playlists/components/playlist-list-header-filters.tsx index 15073eed..0099e260 100644 --- a/src/renderer/features/playlists/components/playlist-list-header-filters.tsx +++ b/src/renderer/features/playlists/components/playlist-list-header-filters.tsx @@ -174,7 +174,7 @@ export const PlaylistListHeaderFilters = ({ queryFn: async ({ signal }) => api.controller.getPlaylistList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query, @@ -213,7 +213,7 @@ export const PlaylistListHeaderFilters = ({ queryFn: async ({ signal }) => api.controller.getPlaylistList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query: { diff --git a/src/renderer/features/playlists/components/save-as-playlist-form.tsx b/src/renderer/features/playlists/components/save-as-playlist-form.tsx index b14e7f88..82f1b2f4 100644 --- a/src/renderer/features/playlists/components/save-as-playlist-form.tsx +++ b/src/renderer/features/playlists/components/save-as-playlist-form.tsx @@ -21,7 +21,7 @@ interface SaveAsPlaylistFormProps { body: Partial; onCancel: () => void; onSuccess: (data: CreatePlaylistResponse) => void; - serverId: string | undefined; + serverId?: string; } export const SaveAsPlaylistForm = ({ @@ -50,7 +50,7 @@ export const SaveAsPlaylistForm = ({ const handleSubmit = form.onSubmit((values) => { mutation.mutate( - { body: values, serverId }, + { apiClientProps: { serverId: serverId || '' }, body: values }, { onError: (err) => { toast.error({ diff --git a/src/renderer/features/playlists/components/update-playlist-form.tsx b/src/renderer/features/playlists/components/update-playlist-form.tsx index e88b26d0..db23f465 100644 --- a/src/renderer/features/playlists/components/update-playlist-form.tsx +++ b/src/renderer/features/playlists/components/update-playlist-form.tsx @@ -65,9 +65,9 @@ export const UpdatePlaylistForm = ({ body, onCancel, query, users }: UpdatePlayl const handleSubmit = form.onSubmit((values) => { mutation.mutate( { + apiClientProps: { serverId: server?.id || '' }, body: values, query, - serverId: server?.id, }, { onError: (err) => { @@ -174,7 +174,10 @@ export const openUpdatePlaylistModal = async (args: { ? await queryClient .fetchQuery({ queryFn: ({ signal }) => - api.controller.getUserList({ apiClientProps: { server, signal }, query }), + api.controller.getUserList({ + apiClientProps: { serverId: server?.id || '', signal }, + query, + }), queryKey: queryKeys.users.list(server?.id || '', query), }) .catch((error) => { diff --git a/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts b/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts index 8e6825bd..f25ec2e6 100644 --- a/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts @@ -4,26 +4,22 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { AddToPlaylistArgs, AddToPlaylistResponse } from '/@/shared/types/domain-types'; export const useAddToPlaylist = (args: MutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); - return useMutation< - AddToPlaylistResponse, - AxiosError, - Omit, - null - >({ + return useMutation({ mutationFn: (args) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.addToPlaylist({ ...args, apiClientProps: { server } }); + return api.controller.addToPlaylist({ + ...args, + apiClientProps: { serverId: args.apiClientProps.serverId }, + }); }, onSuccess: (_data, variables) => { - const { serverId } = variables; + const { apiClientProps } = variables; + const serverId = apiClientProps.serverId; if (!serverId) return; diff --git a/src/renderer/features/playlists/mutations/create-playlist-mutation.ts b/src/renderer/features/playlists/mutations/create-playlist-mutation.ts index 107cc5ce..c64e6fb2 100644 --- a/src/renderer/features/playlists/mutations/create-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/create-playlist-mutation.ts @@ -4,32 +4,24 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { CreatePlaylistArgs, CreatePlaylistResponse } from '/@/shared/types/domain-types'; export const useCreatePlaylist = (args: MutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); - return useMutation< - CreatePlaylistResponse, - AxiosError, - Omit, - null - >({ + return useMutation({ mutationFn: (args) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.createPlaylist({ ...args, apiClientProps: { server } }); + return api.controller.createPlaylist({ + ...args, + apiClientProps: { serverId: args.apiClientProps.serverId }, + }); }, onSuccess: (_args, variables) => { - const server = getServerById(variables.serverId); - if (server) { - queryClient.invalidateQueries({ - exact: false, - queryKey: queryKeys.playlists.list(server.id), - }); - } + queryClient.invalidateQueries({ + exact: false, + queryKey: queryKeys.playlists.list(variables.apiClientProps.serverId), + }); }, ...options, }); diff --git a/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts b/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts index cf60888c..f65e2004 100644 --- a/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts @@ -4,33 +4,29 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById, useCurrentServer } from '/@/renderer/store'; import { DeletePlaylistArgs, DeletePlaylistResponse } from '/@/shared/types/domain-types'; export const useDeletePlaylist = (args: MutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); - const server = useCurrentServer(); - return useMutation< - DeletePlaylistResponse, - AxiosError, - Omit, - null - >({ + return useMutation({ mutationFn: (args) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.deletePlaylist({ ...args, apiClientProps: { server } }); + return api.controller.deletePlaylist({ + ...args, + apiClientProps: { serverId: args.apiClientProps.serverId }, + }); }, - onMutate: () => { - queryClient.cancelQueries({ queryKey: queryKeys.playlists.list(server?.id || '') }); + onMutate: (variables) => { + queryClient.cancelQueries({ + queryKey: queryKeys.playlists.list(variables.apiClientProps.serverId), + }); return null; }, - onSuccess: () => { + onSuccess: (_data, variables) => { queryClient.invalidateQueries({ exact: false, - queryKey: queryKeys.playlists.list(server?.id || ''), + queryKey: queryKeys.playlists.list(variables.apiClientProps.serverId), }); }, ...options, diff --git a/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts b/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts index 9434ede9..084d434b 100644 --- a/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts @@ -4,25 +4,21 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationOptions } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { RemoveFromPlaylistArgs, RemoveFromPlaylistResponse } from '/@/shared/types/domain-types'; export const useRemoveFromPlaylist = (options?: MutationOptions) => { const queryClient = useQueryClient(); - return useMutation< - RemoveFromPlaylistResponse, - AxiosError, - Omit, - null - >({ + return useMutation({ mutationFn: (args) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.removeFromPlaylist({ ...args, apiClientProps: { server } }); + return api.controller.removeFromPlaylist({ + ...args, + apiClientProps: { serverId: args.apiClientProps.serverId }, + }); }, onSuccess: (_data, variables) => { - const { serverId } = variables; + const { apiClientProps } = variables; + const serverId = apiClientProps.serverId; if (!serverId) return; diff --git a/src/renderer/features/playlists/mutations/update-playlist-mutation.ts b/src/renderer/features/playlists/mutations/update-playlist-mutation.ts index 964550ed..fbf9ba39 100644 --- a/src/renderer/features/playlists/mutations/update-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/update-playlist-mutation.ts @@ -4,26 +4,22 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { UpdatePlaylistArgs, UpdatePlaylistResponse } from '/@/shared/types/domain-types'; export const useUpdatePlaylist = (args: MutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); - return useMutation< - UpdatePlaylistResponse, - AxiosError, - Omit, - null - >({ + return useMutation({ mutationFn: (args) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.updatePlaylist({ ...args, apiClientProps: { server } }); + return api.controller.updatePlaylist({ + ...args, + apiClientProps: { serverId: args.apiClientProps.serverId }, + }); }, onSuccess: (_data, variables) => { - const { query, serverId } = variables; + const { apiClientProps, query } = variables; + const serverId = apiClientProps.serverId; if (!serverId) return; diff --git a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx index 88b090d5..abe32d2d 100644 --- a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx +++ b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx @@ -56,6 +56,7 @@ const PlaylistDetailSongListRoute = () => { createPlaylistMutation.mutate( { + apiClientProps: { serverId: detailQuery?.data?.serverId }, body: { _custom: { navidrome: { @@ -69,7 +70,6 @@ const PlaylistDetailSongListRoute = () => { name: detailQuery?.data?.name, public: detailQuery?.data?.public || false, }, - serverId: detailQuery?.data?.serverId, }, { onSuccess: (data) => { @@ -83,8 +83,8 @@ const PlaylistDetailSongListRoute = () => { }, ); deletePlaylistMutation.mutate({ + apiClientProps: { serverId: detailQuery?.data?.serverId }, query: { id: playlistId }, - serverId: detailQuery?.data?.serverId, }); }, }, @@ -124,7 +124,7 @@ const PlaylistDetailSongListRoute = () => { }), ) } - serverId={detailQuery?.data?.serverId} + serverId={detailQuery?.data?.serverId || ''} /> ), title: t('common.saveAs', { postProcess: 'sentenceCase' }), diff --git a/src/renderer/features/search/api/search-api.ts b/src/renderer/features/search/api/search-api.ts index d90a6494..38a3c744 100644 --- a/src/renderer/features/search/api/search-api.ts +++ b/src/renderer/features/search/api/search-api.ts @@ -3,7 +3,6 @@ import { queryOptions } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { QueryHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { SearchQuery } from '/@/shared/types/domain-types'; export const searchQueries = { @@ -11,11 +10,11 @@ export const searchQueries = { return queryOptions({ queryFn: ({ signal }) => { return api.controller.search({ - apiClientProps: { server: getServerById(args.serverId), signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, - queryKey: queryKeys.search.list(args.serverId || '', args.query), + queryKey: queryKeys.search.list(args.serverId, args.query), ...args.options, }); }, diff --git a/src/renderer/features/search/components/server-commands.tsx b/src/renderer/features/search/components/server-commands.tsx index 33ff0ea6..31abfc0f 100644 --- a/src/renderer/features/search/components/server-commands.tsx +++ b/src/renderer/features/search/components/server-commands.tsx @@ -7,7 +7,7 @@ import { Command, CommandPalettePages } from '/@/renderer/features/search/compon import { ServerList } from '/@/renderer/features/servers/components/server-list'; import { AppRoute } from '/@/renderer/router/routes'; import { useAuthStoreActions, useServerList } from '/@/renderer/store'; -import { ServerListItem } from '/@/shared/types/domain-types'; +import { ServerListItemWithCredential } from '/@/shared/types/domain-types'; interface ServerCommandsProps { handleClose: () => void; @@ -32,7 +32,7 @@ export const ServerCommands = ({ handleClose, setPages, setQuery }: ServerComman }, [handleClose, setPages, setQuery, t]); const handleSelectServer = useCallback( - (server: ServerListItem) => { + (server: ServerListItemWithCredential) => { navigate(AppRoute.HOME); setCurrentServer(server); handleClose(); diff --git a/src/renderer/features/servers/components/add-server-form.tsx b/src/renderer/features/servers/components/add-server-form.tsx index efa56d25..4c49a85e 100644 --- a/src/renderer/features/servers/components/add-server-form.tsx +++ b/src/renderer/features/servers/components/add-server-form.tsx @@ -21,7 +21,7 @@ import { Stack } from '/@/shared/components/stack/stack'; import { TextInput } from '/@/shared/components/text-input/text-input'; import { Text } from '/@/shared/components/text/text'; import { toast } from '/@/shared/components/toast/toast'; -import { AuthenticationResponse, ServerListItem } from '/@/shared/types/domain-types'; +import { AuthenticationResponse, ServerListItemWithCredential } from '/@/shared/types/domain-types'; import { DiscoveredServerItem, ServerType, toServerType } from '/@/shared/types/types'; const autodiscover = isElectron() ? window.api.autodiscover : null; @@ -152,7 +152,7 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => { }); } - const serverItem: ServerListItem = { + const serverItem: ServerListItemWithCredential = { credential: data.credential, id: nanoid(), name: values.name, diff --git a/src/renderer/features/servers/components/edit-server-form.tsx b/src/renderer/features/servers/components/edit-server-form.tsx index 936f35e9..9f8c0256 100644 --- a/src/renderer/features/servers/components/edit-server-form.tsx +++ b/src/renderer/features/servers/components/edit-server-form.tsx @@ -18,7 +18,12 @@ import { Stack } from '/@/shared/components/stack/stack'; import { TextInput } from '/@/shared/components/text-input/text-input'; import { toast } from '/@/shared/components/toast/toast'; import { Tooltip } from '/@/shared/components/tooltip/tooltip'; -import { AuthenticationResponse, ServerListItem, ServerType } from '/@/shared/types/domain-types'; +import { + AuthenticationResponse, + ServerListItem, + ServerListItemWithCredential, + ServerType, +} from '/@/shared/types/domain-types'; const localSettings = isElectron() ? window.api.localSettings : null; @@ -86,7 +91,7 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer }); } - const serverItem: ServerListItem = { + const serverItem: ServerListItemWithCredential = { credential: data.credential, id: server.id, name: values.name, diff --git a/src/renderer/features/shared/api/shared-api.ts b/src/renderer/features/shared/api/shared-api.ts index 48d53ff9..2c22d9f1 100644 --- a/src/renderer/features/shared/api/shared-api.ts +++ b/src/renderer/features/shared/api/shared-api.ts @@ -3,28 +3,25 @@ import { queryOptions } from '@tanstack/react-query'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { QueryHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { MusicFolderListQuery, TagQuery, UserListQuery } from '/@/shared/types/domain-types'; export const sharedQueries = { musicFolders: (args: QueryHookArgs) => { return queryOptions({ queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.getMusicFolderList({ apiClientProps: { server, signal } }); + return api.controller.getMusicFolderList({ + apiClientProps: { serverId: args.serverId, signal }, + }); }, - queryKey: queryKeys.musicFolders.list(args.serverId || ''), + queryKey: queryKeys.musicFolders.list(args.serverId), ...args.options, }); }, roles: (args: QueryHookArgs) => { return queryOptions({ queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return api.controller.getRoles({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, }); }, queryKey: queryKeys.roles.list(args.serverId || ''), @@ -35,10 +32,8 @@ export const sharedQueries = { return queryOptions({ gcTime: 1000 * 60, queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return api.controller.getTags({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, @@ -49,10 +44,8 @@ export const sharedQueries = { users: (args: QueryHookArgs) => { return queryOptions({ queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return api.controller.getUserList({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, diff --git a/src/renderer/features/shared/hooks/use-handle-favorite.ts b/src/renderer/features/shared/hooks/use-handle-favorite.ts index b4c6d1f3..f385a37d 100644 --- a/src/renderer/features/shared/hooks/use-handle-favorite.ts +++ b/src/renderer/features/shared/hooks/use-handle-favorite.ts @@ -3,17 +3,17 @@ import { MutableRefObject, useCallback } from 'react'; import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid/virtual-infinite-grid'; import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation'; import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation'; +import { useCurrentServerId } from '/@/renderer/store'; import { LibraryItem } from '/@/shared/types/domain-types'; -import { ServerListItem } from '/@/shared/types/types'; interface HandleFavoriteProps { gridRef: MutableRefObject; - server: null | ServerListItem; } -export const useHandleFavorite = ({ gridRef, server }: HandleFavoriteProps) => { +export const useHandleFavorite = ({ gridRef }: HandleFavoriteProps) => { const createFavoriteMutation = useCreateFavorite({}); const deleteFavoriteMutation = useDeleteFavorite({}); + const serverId = useCurrentServerId(); const handleFavorite = useCallback( async (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => { @@ -21,19 +21,19 @@ export const useHandleFavorite = ({ gridRef, server }: HandleFavoriteProps) => { try { if (isFavorite) { await deleteFavoriteMutation.mutateAsync({ + apiClientProps: { serverId }, query: { id, type: itemType, }, - serverId: server?.id, }); } else { await createFavoriteMutation.mutateAsync({ + apiClientProps: { serverId }, query: { id, type: itemType, }, - serverId: server?.id, }); } @@ -50,7 +50,7 @@ export const useHandleFavorite = ({ gridRef, server }: HandleFavoriteProps) => { console.error(error); } }, - [createFavoriteMutation, deleteFavoriteMutation, gridRef, server?.id], + [createFavoriteMutation, deleteFavoriteMutation, gridRef, serverId], ); return handleFavorite; diff --git a/src/renderer/features/shared/mutations/create-favorite-mutation.ts b/src/renderer/features/shared/mutations/create-favorite-mutation.ts index 154ae762..5defd8ff 100644 --- a/src/renderer/features/shared/mutations/create-favorite-mutation.ts +++ b/src/renderer/features/shared/mutations/create-favorite-mutation.ts @@ -5,7 +5,7 @@ import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store'; +import { useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store'; import { useFavoriteEvent } from '/@/renderer/store/event.store'; import { AlbumArtistDetailResponse, @@ -24,19 +24,16 @@ export const useCreateFavorite = (args: MutationHookArgs) => { const setQueueFavorite = useSetQueueFavorite(); const setFavoriteEvent = useFavoriteEvent(); - return useMutation< - FavoriteResponse, - AxiosError, - Omit, - null - >({ + return useMutation({ mutationFn: (args) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.createFavorite({ ...args, apiClientProps: { server } }); + return api.controller.createFavorite({ + ...args, + apiClientProps: { serverId: args.apiClientProps.serverId }, + }); }, onSuccess: (_data, variables) => { - const { serverId } = variables; + const { apiClientProps } = variables; + const serverId = apiClientProps.serverId; if (!serverId) return; diff --git a/src/renderer/features/shared/mutations/delete-favorite-mutation.ts b/src/renderer/features/shared/mutations/delete-favorite-mutation.ts index 2b68c58c..58431b57 100644 --- a/src/renderer/features/shared/mutations/delete-favorite-mutation.ts +++ b/src/renderer/features/shared/mutations/delete-favorite-mutation.ts @@ -5,7 +5,7 @@ import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store'; +import { useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store'; import { useFavoriteEvent } from '/@/renderer/store/event.store'; import { AlbumArtistDetailResponse, @@ -24,21 +24,16 @@ export const useDeleteFavorite = (args: MutationHookArgs) => { const setQueueFavorite = useSetQueueFavorite(); const setFavoriteEvent = useFavoriteEvent(); - return useMutation< - FavoriteResponse, - AxiosError, - Omit, - null - >({ + return useMutation({ mutationFn: (args) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.deleteFavorite({ ...args, apiClientProps: { server } }); + return api.controller.deleteFavorite({ + ...args, + apiClientProps: { serverId: args.apiClientProps.serverId }, + }); }, onSuccess: (_data, variables) => { - const { serverId } = variables; - - if (!serverId) return; + const { apiClientProps } = variables; + const serverId = apiClientProps.serverId; for (const id of variables.query.id) { // Set the userFavorite property to false for the album in the album list data store @@ -55,7 +50,9 @@ export const useDeleteFavorite = (args: MutationHookArgs) => { // We only need to set if we're already on the album detail page if (variables.query.type === LibraryItem.ALBUM && variables.query.id.length === 1) { - const queryKey = queryKeys.albums.detail(serverId, { id: variables.query.id[0] }); + const queryKey = queryKeys.albums.detail(serverId, { + id: variables.query.id[0], + }); const previous = queryClient.getQueryData(queryKey); if (previous) { diff --git a/src/renderer/features/shared/mutations/set-rating-mutation.ts b/src/renderer/features/shared/mutations/set-rating-mutation.ts index ad37c974..a5fab7ec 100644 --- a/src/renderer/features/shared/mutations/set-rating-mutation.ts +++ b/src/renderer/features/shared/mutations/set-rating-mutation.ts @@ -5,7 +5,7 @@ import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById, useSetAlbumListItemDataById, useSetQueueRating } from '/@/renderer/store'; +import { useSetAlbumListItemDataById, useSetQueueRating } from '/@/renderer/store'; import { useRatingEvent } from '/@/renderer/store/event.store'; import { Album, @@ -30,13 +30,14 @@ export const useSetRating = (args: MutationHookArgs) => { return useMutation< RatingResponse, AxiosError, - Omit, + SetRatingArgs, { previous: undefined | { items: AnyLibraryItems } } >({ mutationFn: (args) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.setRating({ ...args, apiClientProps: { server } }); + return api.controller.setRating({ + ...args, + apiClientProps: { serverId: args.apiClientProps.serverId }, + }); }, onError: (_error, _variables, context) => { for (const item of context?.previous?.items || []) { diff --git a/src/renderer/features/sharing/components/share-item-context-modal.tsx b/src/renderer/features/sharing/components/share-item-context-modal.tsx index 90392f22..26b912e4 100644 --- a/src/renderer/features/sharing/components/share-item-context-modal.tsx +++ b/src/renderer/features/sharing/components/share-item-context-modal.tsx @@ -48,6 +48,7 @@ export const ShareItemContextModal = ({ const handleSubmit = form.onSubmit(async (values) => { shareItemMutation.mutate( { + apiClientProps: { serverId: server?.id || '' }, body: { description: values.description, downloadable: values.allowDownloading, @@ -55,7 +56,6 @@ export const ShareItemContextModal = ({ resourceIds: itemIds.join(), resourceType, }, - serverId: server?.id, }, { onError: () => { diff --git a/src/renderer/features/sharing/mutations/share-item-mutation.ts b/src/renderer/features/sharing/mutations/share-item-mutation.ts index 38fb8e42..f1c58ab8 100644 --- a/src/renderer/features/sharing/mutations/share-item-mutation.ts +++ b/src/renderer/features/sharing/mutations/share-item-mutation.ts @@ -3,7 +3,6 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { AnyLibraryItems, ShareItemArgs, ShareItemResponse } from '/@/shared/types/domain-types'; export const useShareItem = (args: MutationHookArgs) => { @@ -12,13 +11,14 @@ export const useShareItem = (args: MutationHookArgs) => { return useMutation< ShareItemResponse, AxiosError, - Omit, + ShareItemArgs, { previous: undefined | { items: AnyLibraryItems } } >({ mutationFn: (args) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); - return api.controller.shareItem({ ...args, apiClientProps: { server } }); + return api.controller.shareItem({ + ...args, + apiClientProps: { serverId: args.apiClientProps.serverId }, + }); }, ...options, }); diff --git a/src/renderer/features/songs/api/songs-api.ts b/src/renderer/features/songs/api/songs-api.ts index 351bc007..6af26db8 100644 --- a/src/renderer/features/songs/api/songs-api.ts +++ b/src/renderer/features/songs/api/songs-api.ts @@ -4,36 +4,31 @@ import { api } from '/@/renderer/api'; import { controller } from '/@/renderer/api/controller'; import { queryKeys } from '/@/renderer/api/query-keys'; import { QueryHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; import { ListCountQuery, SimilarSongsQuery, SongListQuery } from '/@/shared/types/domain-types'; export const songsQueries = { list: (args: QueryHookArgs, imageSize?: number) => { return queryOptions({ queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return controller.getSongList({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: { ...args.query, imageSize }, }); }, - queryKey: queryKeys.songs.list(args.serverId || '', { ...args.query, imageSize }), + queryKey: queryKeys.songs.list(args.serverId, { ...args.query, imageSize }), ...args.options, }); }, listCount: (args: QueryHookArgs>) => { return queryOptions({ queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return api.controller.getSongListCount({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: args.query, }); }, queryKey: queryKeys.songs.count( - args.serverId || '', + args.serverId, Object.keys(args.query).length === 0 ? undefined : args.query, ), ...args.options, @@ -42,10 +37,8 @@ export const songsQueries = { similar: (args: QueryHookArgs) => { return queryOptions({ queryFn: ({ signal }) => { - const server = getServerById(args.serverId); - if (!server) throw new Error('Server not found'); return api.controller.getSimilarSongs({ - apiClientProps: { server, signal }, + apiClientProps: { serverId: args.serverId, signal }, query: { albumArtistIds: args.query.albumArtistIds, count: args.query.count ?? 50, @@ -53,7 +46,7 @@ export const songsQueries = { }, }); }, - queryKey: queryKeys.songs.similar(args.serverId || '', args.query), + queryKey: queryKeys.songs.similar(args.serverId, args.query), ...args.options, }); }, diff --git a/src/renderer/features/songs/components/navidrome-song-filters.tsx b/src/renderer/features/songs/components/navidrome-song-filters.tsx index b24fc718..0e1a1821 100644 --- a/src/renderer/features/songs/components/navidrome-song-filters.tsx +++ b/src/renderer/features/songs/components/navidrome-song-filters.tsx @@ -10,8 +10,8 @@ import { import { genresQueries } from '/@/renderer/features/genres/api/genres-api'; import { sharedQueries } from '/@/renderer/features/shared/api/shared-api'; import { - getServerById, SongListFilter, + useCurrentServer, useListFilterByKey, useListStoreActions, } from '/@/renderer/store'; @@ -42,7 +42,7 @@ export const NavidromeSongFilters = ({ const { t } = useTranslation(); const { setFilter } = useListStoreActions(); const filter = useListFilterByKey({ key: pageKey }); - const server = getServerById(serverId); + const server = useCurrentServer(); const isGenrePage = customFilters?.genreIds !== undefined; diff --git a/src/renderer/features/songs/components/song-list-grid-view.tsx b/src/renderer/features/songs/components/song-list-grid-view.tsx index cf2d5d58..b19637b3 100644 --- a/src/renderer/features/songs/components/song-list-grid-view.tsx +++ b/src/renderer/features/songs/components/song-list-grid-view.tsx @@ -43,7 +43,7 @@ export const SongListGridView = ({ gridRef, itemCount }: SongListGridViewProps) const scrollOffset = searchParams.get('scrollOffset'); const initialScrollOffset = Number(id ? scrollOffset : grid?.scrollOffset) || 0; - const handleFavorite = useHandleFavorite({ gridRef, server }); + const handleFavorite = useHandleFavorite({ gridRef }); useEffect(() => { const unSub = useEventStore.subscribe((state) => { @@ -189,7 +189,7 @@ export const SongListGridView = ({ gridRef, itemCount }: SongListGridViewProps) queryFn: async ({ signal }) => controller.getSongList({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query, diff --git a/src/renderer/features/titlebar/components/app-menu.tsx b/src/renderer/features/titlebar/components/app-menu.tsx index 292e7590..d6383c6d 100644 --- a/src/renderer/features/titlebar/components/app-menu.tsx +++ b/src/renderer/features/titlebar/components/app-menu.tsx @@ -20,7 +20,7 @@ import { import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu'; import { Icon } from '/@/shared/components/icon/icon'; import { toast } from '/@/shared/components/toast/toast'; -import { ServerListItem, ServerType } from '/@/shared/types/domain-types'; +import { ServerListItemWithCredential, ServerType } from '/@/shared/types/domain-types'; const browser = isElectron() ? window.api.browser : null; const localSettings = isElectron() ? window.api.localSettings : null; @@ -35,12 +35,12 @@ export const AppMenu = () => { const { privateMode } = useAppStore(); const { setPrivateMode, setSideBar } = useAppStoreActions(); - const handleSetCurrentServer = (server: ServerListItem) => { + const handleSetCurrentServer = (server: ServerListItemWithCredential) => { navigate(AppRoute.HOME); setCurrentServer(server); }; - const handleCredentialsModal = async (server: ServerListItem) => { + const handleCredentialsModal = async (server: ServerListItemWithCredential) => { let password: null | string = null; try { diff --git a/src/renderer/hooks/use-list-filter-refresh.ts b/src/renderer/hooks/use-list-filter-refresh.ts index 83d60bae..1e11640c 100644 --- a/src/renderer/hooks/use-list-filter-refresh.ts +++ b/src/renderer/hooks/use-list-filter-refresh.ts @@ -86,7 +86,7 @@ export const useListFilterRefresh = ({ queryFn: async ({ signal }) => { return queryFn({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, query, diff --git a/src/renderer/hooks/use-server-authenticated.ts b/src/renderer/hooks/use-server-authenticated.ts index d23544cd..b1ada72f 100644 --- a/src/renderer/hooks/use-server-authenticated.ts +++ b/src/renderer/hooks/use-server-authenticated.ts @@ -3,7 +3,7 @@ import { debounce } from 'lodash'; import { useCallback, useEffect, useRef, useState } from 'react'; import { api } from '/@/renderer/api'; -import { useAuthStoreActions, useCurrentServer } from '/@/renderer/store'; +import { getServerById, useAuthStoreActions, useCurrentServer } from '/@/renderer/store'; import { toast } from '/@/shared/components/toast/toast'; import { SongListSort, SortOrder } from '/@/shared/types/domain-types'; import { AuthState, ServerListItem, ServerType } from '/@/shared/types/types'; @@ -26,7 +26,7 @@ export const useServerAuthenticated = () => { // making one request first try { await api.controller.getSongList({ - apiClientProps: { server }, + apiClientProps: { serverId: server?.id || '' }, query: { limit: 1, sortBy: SongListSort.NAME, @@ -59,11 +59,12 @@ export const useServerAuthenticated = () => { useEffect(() => { if (priorServerId.current !== server?.id) { + const serverWithAuth = getServerById(server!.id); priorServerId.current = server?.id || ''; if (server?.type === ServerType.NAVIDROME) { setReady(AuthState.LOADING); - debouncedAuth(server); + debouncedAuth(serverWithAuth!); } else { setReady(AuthState.VALID); } diff --git a/src/renderer/hooks/use-server-version.ts b/src/renderer/hooks/use-server-version.ts index fed47401..7cc2c1e9 100644 --- a/src/renderer/hooks/use-server-version.ts +++ b/src/renderer/hooks/use-server-version.ts @@ -15,7 +15,7 @@ export const useServerVersion = () => { queryFn: async ({ signal }) => { return controller.getServerInfo({ apiClientProps: { - server, + serverId: server?.id || '', signal, }, }); diff --git a/src/renderer/store/auth.store.ts b/src/renderer/store/auth.store.ts index c2329a23..4ad6b531 100644 --- a/src/renderer/store/auth.store.ts +++ b/src/renderer/store/auth.store.ts @@ -2,27 +2,28 @@ import merge from 'lodash/merge'; import { nanoid } from 'nanoid/non-secure'; import { devtools, persist } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; +import { shallow } from 'zustand/shallow'; import { createWithEqualityFn } from 'zustand/traditional'; import { useAlbumArtistListDataStore } from '/@/renderer/store/album-artist-list-data.store'; import { useAlbumListDataStore } from '/@/renderer/store/album-list-data.store'; import { useListStore } from '/@/renderer/store/list.store'; -import { ServerListItem } from '/@/shared/types/domain-types'; +import { ServerListItem, ServerListItemWithCredential } from '/@/shared/types/domain-types'; export interface AuthSlice extends AuthState { actions: { - addServer: (args: ServerListItem) => void; + addServer: (args: ServerListItemWithCredential) => void; deleteServer: (id: string) => void; - getServer: (id: string) => null | ServerListItem; - setCurrentServer: (server: null | ServerListItem) => void; - updateServer: (id: string, args: Partial) => void; + getServer: (id: string) => null | ServerListItemWithCredential; + setCurrentServer: (server: null | ServerListItemWithCredential) => void; + updateServer: (id: string, args: Partial) => void; }; } export interface AuthState { - currentServer: null | ServerListItem; + currentServer: null | ServerListItemWithCredential; deviceId: string; - serverList: Record; + serverList: Record; } export const useAuthStore = createWithEqualityFn()( @@ -92,7 +93,23 @@ export const useAuthStore = createWithEqualityFn()( export const useCurrentServerId = () => useAuthStore((state) => state.currentServer)?.id || ''; export const useCurrentServer = () => - useAuthStore((state) => state.currentServer) as ServerListItem; + useAuthStore((state) => { + return { + features: state.currentServer?.features, + id: state.currentServer?.id, + name: state.currentServer?.name, + preferInstantMix: state.currentServer?.preferInstantMix, + savePassword: state.currentServer?.savePassword, + type: state.currentServer?.type, + url: state.currentServer?.url, + userId: state.currentServer?.userId, + username: state.currentServer?.username, + version: state.currentServer?.version, + }; + }, shallow) as ServerListItem; + +export const useCurrentServerWithCredential = () => + useAuthStore((state) => state.currentServer) as ServerListItemWithCredential; export const useServerList = () => useAuthStore((state) => state.serverList); diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 6e0ac18c..e867093f 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -914,7 +914,6 @@ export const useSettingsStore = createWithEqualityFn()( merge: mergeOverridingColumns, migrate(persistedState, version) { const state = persistedState as SettingsSlice; - console.log('migrate: ', version); if (version === 8) { state.general.sidebarItems = state.general.sidebarItems.filter( diff --git a/src/renderer/utils/set-transcoded-queue-data.ts b/src/renderer/utils/set-transcoded-queue-data.ts index f7a7d13f..6f7d69ee 100644 --- a/src/renderer/utils/set-transcoded-queue-data.ts +++ b/src/renderer/utils/set-transcoded-queue-data.ts @@ -3,7 +3,7 @@ import type { PlayerData, QueueSong } from '/@/shared/types/domain-types'; import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; -import { getServerById, useSettingsStore } from '/@/renderer/store'; +import { useSettingsStore } from '/@/renderer/store'; const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; @@ -12,7 +12,7 @@ const modifyUrl = (song: QueueSong): string => { if (transcode.enabled) { const streamUrl = api.controller.getTranscodingUrl({ apiClientProps: { - server: getServerById(song.serverId), + serverId: song.serverId, }, query: { base: song.streamUrl, diff --git a/src/shared/api/navidrome/navidrome-normalize.ts b/src/shared/api/navidrome/navidrome-normalize.ts index 92a4056a..742ee30e 100644 --- a/src/shared/api/navidrome/navidrome-normalize.ts +++ b/src/shared/api/navidrome/navidrome-normalize.ts @@ -126,7 +126,7 @@ const getArtists = ( const normalizeSong = ( item: z.infer | z.infer, - server: null | ServerListItem, + server?: null | ServerListItem, imageSize?: number, ): Song => { let id; @@ -222,7 +222,7 @@ const normalizeAlbum = ( item: z.infer & { songs?: z.infer; }, - server: null | ServerListItem, + server?: null | ServerListItem, imageSize?: number, ): Album => { const imageUrl = getCoverArtUrl({ @@ -293,7 +293,7 @@ const normalizeAlbumArtist = ( item: z.infer & { similarArtists?: z.infer['artistInfo']['similarArtist']; }, - server: null | ServerListItem, + server?: null | ServerListItem, ): AlbumArtist => { let imageUrl = getImageUrl({ url: item?.largeImageUrl || null }); @@ -358,7 +358,7 @@ const normalizeAlbumArtist = ( const normalizePlaylist = ( item: z.infer, - server: null | ServerListItem, + server?: null | ServerListItem, imageSize?: number, ): Playlist => { const imageUrl = getCoverArtUrl({ diff --git a/src/shared/api/subsonic/subsonic-normalize.ts b/src/shared/api/subsonic/subsonic-normalize.ts index 2721bc40..02d9afe8 100644 --- a/src/shared/api/subsonic/subsonic-normalize.ts +++ b/src/shared/api/subsonic/subsonic-normalize.ts @@ -11,7 +11,7 @@ import { Playlist, QueueSong, RelatedArtist, - ServerListItem, + ServerListItemWithCredential, ServerType, } from '/@/shared/types/domain-types'; @@ -117,7 +117,7 @@ const getGenres = ( const normalizeSong = ( item: z.infer, - server: null | ServerListItem, + server?: null | ServerListItemWithCredential, size?: number, ): QueueSong => { const imageUrl = @@ -200,7 +200,7 @@ const normalizeAlbumArtist = ( item: | z.infer | z.infer, - server: null | ServerListItem, + server?: null | ServerListItemWithCredential, imageSize?: number, ): AlbumArtist => { const imageUrl = @@ -235,7 +235,7 @@ const normalizeAlbumArtist = ( const normalizeAlbum = ( item: z.infer | z.infer, - server: null | ServerListItem, + server?: null | ServerListItemWithCredential, imageSize?: number, ): Album => { const imageUrl = @@ -294,7 +294,7 @@ const normalizePlaylist = ( item: | z.infer | z.infer, - server: null | ServerListItem, + server?: null | ServerListItemWithCredential, ): Playlist => { return { description: item.comment || null, diff --git a/src/shared/types/domain-types.ts b/src/shared/types/domain-types.ts index baeb3550..c0a9e7ba 100644 --- a/src/shared/types/domain-types.ts +++ b/src/shared/types/domain-types.ts @@ -1,3 +1,4 @@ +import { Omit } from 'lodash'; import orderBy from 'lodash/orderBy'; import reverse from 'lodash/reverse'; import shuffle from 'lodash/shuffle'; @@ -84,11 +85,9 @@ export type QueueSong = Song & { }; export type ServerListItem = { - credential: string; features?: ServerFeatures; id: string; name: string; - ndCredential?: string; preferInstantMix?: boolean; savePassword?: boolean; type: ServerType; @@ -98,6 +97,11 @@ export type ServerListItem = { version?: string; }; +export type ServerListItemWithCredential = ServerListItem & { + credential: string; + ndCredential?: string; +}; + export type User = { createdAt: null | string; email: null | string; @@ -372,7 +376,8 @@ export type Song = { type BaseEndpointArgs = { apiClientProps: { - server: null | ServerListItem; + server?: null | ServerListItemWithCredential; + serverId: string; signal?: AbortSignal; }; }; @@ -829,7 +834,6 @@ export enum PlaylistListSort { export type AddToPlaylistArgs = BaseEndpointArgs & { body: AddToPlaylistBody; query: AddToPlaylistQuery; - serverId?: string; }; export type AddToPlaylistBody = { @@ -843,7 +847,7 @@ export type AddToPlaylistQuery = { // Add to playlist export type AddToPlaylistResponse = null | undefined; -export type CreatePlaylistArgs = BaseEndpointArgs & { body: CreatePlaylistBody; serverId?: string }; +export type CreatePlaylistArgs = BaseEndpointArgs & { body: CreatePlaylistBody }; export type CreatePlaylistBody = { _custom?: { @@ -864,7 +868,6 @@ export type CreatePlaylistResponse = undefined | { id: string }; export type DeletePlaylistArgs = BaseEndpointArgs & { query: DeletePlaylistQuery; - serverId?: string; }; export type DeletePlaylistQuery = { id: string }; @@ -872,7 +875,7 @@ export type DeletePlaylistQuery = { id: string }; // Delete Playlist export type DeletePlaylistResponse = null | undefined; -export type FavoriteArgs = BaseEndpointArgs & { query: FavoriteQuery; serverId?: string }; +export type FavoriteArgs = BaseEndpointArgs & { query: FavoriteQuery }; export type FavoriteQuery = { id: string[]; @@ -909,7 +912,6 @@ export type RatingResponse = null | undefined; export type RemoveFromPlaylistArgs = BaseEndpointArgs & { query: RemoveFromPlaylistQuery; - serverId?: string; }; export type RemoveFromPlaylistQuery = { @@ -920,9 +922,9 @@ export type RemoveFromPlaylistQuery = { // Remove from playlist export type RemoveFromPlaylistResponse = null | undefined; -export type SetRatingArgs = BaseEndpointArgs & { query: RatingQuery; serverId?: string }; +export type SetRatingArgs = BaseEndpointArgs & { query: RatingQuery }; -export type ShareItemArgs = BaseEndpointArgs & { body: ShareItemBody; serverId?: string }; +export type ShareItemArgs = BaseEndpointArgs & { body: ShareItemBody }; export type ShareItemBody = { description: string; @@ -938,7 +940,6 @@ export type ShareItemResponse = undefined | { id: string }; export type UpdatePlaylistArgs = BaseEndpointArgs & { body: UpdatePlaylistBody; query: UpdatePlaylistQuery; - serverId?: string; }; export type UpdatePlaylistBody = { @@ -1135,7 +1136,6 @@ export type RandomSongListResponse = SongListResponse; export type ScrobbleArgs = BaseEndpointArgs & { query: ScrobbleQuery; - serverId?: string; }; export type ScrobbleQuery = { @@ -1279,6 +1279,85 @@ export type FontData = { style: string; }; +export type InternalControllerEndpoint = { + addToPlaylist: ( + args: ReplaceApiClientProps, + ) => Promise; + authenticate: ( + url: string, + body: { legacy?: boolean; password: string; username: string }, + ) => Promise; + createFavorite: (args: ReplaceApiClientProps) => Promise; + createPlaylist: ( + args: ReplaceApiClientProps, + ) => Promise; + deleteFavorite: (args: ReplaceApiClientProps) => Promise; + deletePlaylist: ( + args: ReplaceApiClientProps, + ) => Promise; + getAlbumArtistDetail: ( + args: ReplaceApiClientProps, + ) => Promise; + getAlbumArtistList: ( + args: ReplaceApiClientProps, + ) => Promise; + getAlbumArtistListCount: ( + args: ReplaceApiClientProps, + ) => Promise; + getAlbumDetail: (args: ReplaceApiClientProps) => Promise; + getAlbumInfo?: (args: ReplaceApiClientProps) => Promise; + getAlbumList: (args: ReplaceApiClientProps) => Promise; + getAlbumListCount: (args: ReplaceApiClientProps) => Promise; + // getArtistInfo?: (args: any) => void; + getArtistList: (args: ReplaceApiClientProps) => Promise; + getArtistListCount: (args: ReplaceApiClientProps) => Promise; + getDownloadUrl: (args: ReplaceApiClientProps) => string; + getGenreList: (args: ReplaceApiClientProps) => Promise; + getLyrics?: (args: ReplaceApiClientProps) => Promise; + getMusicFolderList: ( + args: ReplaceApiClientProps, + ) => Promise; + getPlaylistDetail: ( + args: ReplaceApiClientProps, + ) => Promise; + getPlaylistList: ( + args: ReplaceApiClientProps, + ) => Promise; + getPlaylistListCount: (args: ReplaceApiClientProps) => Promise; + getPlaylistSongList: ( + args: ReplaceApiClientProps, + ) => Promise; + getRandomSongList: ( + args: ReplaceApiClientProps, + ) => Promise; + getRoles: ( + args: ReplaceApiClientProps, + ) => Promise>; + getServerInfo: (args: ReplaceApiClientProps) => Promise; + getSimilarSongs: (args: ReplaceApiClientProps) => Promise; + getSongDetail: (args: ReplaceApiClientProps) => Promise; + getSongList: (args: ReplaceApiClientProps) => Promise; + getSongListCount: (args: ReplaceApiClientProps) => Promise; + getStructuredLyrics?: ( + args: ReplaceApiClientProps, + ) => Promise; + getTags?: (args: ReplaceApiClientProps) => Promise; + getTopSongs: (args: ReplaceApiClientProps) => Promise; + getTranscodingUrl: (args: ReplaceApiClientProps) => string; + getUserList?: (args: ReplaceApiClientProps) => Promise; + movePlaylistItem?: (args: ReplaceApiClientProps) => Promise; + removeFromPlaylist: ( + args: ReplaceApiClientProps, + ) => Promise; + scrobble: (args: ReplaceApiClientProps) => Promise; + search: (args: ReplaceApiClientProps) => Promise; + setRating?: (args: ReplaceApiClientProps) => Promise; + shareItem?: (args: ReplaceApiClientProps) => Promise; + updatePlaylist: ( + args: ReplaceApiClientProps, + ) => Promise; +}; + export type LyricGetQuery = { remoteSongId: string; remoteSource: LyricSource; @@ -1305,6 +1384,8 @@ export type MoveItemQuery = { trackId: string; }; +export type ReplaceApiClientProps = BaseEndpointArgsWithServer & Omit; + export type ServerInfo = { features: ServerFeatures; id?: string; @@ -1370,6 +1451,14 @@ export type TranscodingQuery = { format?: string; }; +type BaseEndpointArgsWithServer = { + apiClientProps: { + server: null | ServerListItemWithCredential; + serverId: string; + signal?: AbortSignal; + }; +}; + export const sortAlbumList = (albums: Album[], sortBy: AlbumListSort, sortOrder: SortOrder) => { let results = albums;