diff --git a/src/main/features/linux/mpris.ts b/src/main/features/linux/mpris.ts index 9bfdcffe6..e946dc759 100644 --- a/src/main/features/linux/mpris.ts +++ b/src/main/features/linux/mpris.ts @@ -168,7 +168,7 @@ ipcMain.on('update-song', (_event, song: QueueSong | undefined) => { 'xesam:contentCreated': song.releaseDate, 'xesam:discNumber': song.discNumber ? song.discNumber : null, 'xesam:genre': song.genres?.length ? song.genres.map((genre: any) => genre.name) : null, - 'xesam:lastUsed': song.lastPlayedAt, + 'xesam:lastUsed': song.userLastPlayedDate, 'xesam:title': song.name || null, 'xesam:trackNumber': song.trackNumber ? song.trackNumber : null, 'xesam:useCount': diff --git a/src/renderer/api/api-controller.ts b/src/renderer/api/api-controller.ts new file mode 100644 index 000000000..74ed21e72 --- /dev/null +++ b/src/renderer/api/api-controller.ts @@ -0,0 +1,46 @@ +import { getServerById } from '/@/renderer/store'; +import { + controller as subsonicAdapter, + apiClient as subsonicApiClient, + middleware as subsonicMiddleware, +} from '/@/shared/api/subsonic/subsonic-controller'; +import { ApiController } from '/@/shared/types/adapter/api-controller-types'; +import { ServerType } from '/@/shared/types/domain/server-domain-types'; + +export const serverApi = { + [ServerType.JELLYFIN]: { + apiClient: null, + controller: {}, + middleware: null, + }, + [ServerType.NAVIDROME]: { + apiClient: null, + controller: {}, + middleware: null, + }, + [ServerType.SUBSONIC]: { + apiClient: subsonicApiClient, + controller: subsonicAdapter, + middleware: subsonicMiddleware, + }, +}; + +export const api = (serverId: string): ApiController => { + const server = getServerById(serverId); + + if (!server) { + throw new Error('No server or api client selected'); + } + + const { apiClient, controller, middleware } = serverApi[server.type]; + + if (middleware) { + apiClient.use(middleware(server)); + } + + if (!apiClient) { + throw new Error('No api client found'); + } + + return controller as ApiController; +}; diff --git a/src/renderer/api/controller.ts b/src/renderer/api/controller.ts index 7a62b872a..ba7da16ca 100644 --- a/src/renderer/api/controller.ts +++ b/src/renderer/api/controller.ts @@ -4,11 +4,9 @@ import { NavidromeController } from '/@/renderer/api/navidrome/navidrome-control import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller'; import { useAuthStore } from '/@/renderer/store'; import { toast } from '/@/shared/components/toast/toast'; -import { - AuthenticationResponse, - ControllerEndpoint, - ServerType, -} from '/@/shared/types/domain-types'; +import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types'; +import { AuthenticationResponse } from '/@/shared/types/domain/auth-domain-types'; +import { ServerType } from '/@/shared/types/domain/server-domain-types'; type ApiController = { jellyfin: ControllerEndpoint; diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index e8fb1689d..67814414b 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -4,7 +4,7 @@ import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller import { NDSongListSort } from '/@/shared/api/navidrome.types'; import { ndNormalize } from '/@/shared/api/navidrome/navidrome-normalize'; import { ndType } from '/@/shared/api/navidrome/navidrome-types'; -import { ssNormalize } from '/@/shared/api/subsonic/subsonic-normalize'; +import { normalize } from '/@/shared/api/subsonic/subsonic-normalize'; import { SubsonicExtensions } from '/@/shared/api/subsonic/subsonic-types'; import { getFeatures, hasFeature, VersionInfo } from '/@/shared/api/utils'; import { albumListSortMap } from '/@/shared/types/domain/album-domain-types'; @@ -14,7 +14,7 @@ import { AuthenticationResponse } from '/@/shared/types/domain/auth-domain-types import { genreListSortMap } from '/@/shared/types/domain/genre-domain-types'; import { playlistListSortMap, - PlaylistSongListArgs, + PlaylistSongListRequest, PlaylistSongListResponse, } from '/@/shared/types/domain/playlist-domain-types'; import { @@ -424,7 +424,9 @@ export const NavidromeController: ControllerEndpoint = { apiClientProps, query: { ...query, limit: 1, startIndex: 0 }, }).then((result) => result!.totalRecordCount!), - getPlaylistSongList: async (args: PlaylistSongListArgs): Promise => { + getPlaylistSongList: async ( + args: PlaylistSongListRequest, + ): Promise => { const { apiClientProps, query } = args; const res = await ndApiClient(apiClientProps).getPlaylistSongList({ @@ -520,7 +522,7 @@ export const NavidromeController: ControllerEndpoint = { if (res.status === 200 && res.body.similarSongs?.song) { const similar = res.body.similarSongs.song.reduce((acc, song) => { if (song.id !== query.songId) { - acc.push(ssNormalize.song(song, apiClientProps.server)); + acc.push(normalize.song(song, apiClientProps.server)); } return acc; diff --git a/src/renderer/api/subsonic/subsonic-controller.ts b/src/renderer/api/subsonic/subsonic-controller.ts index 65b6b79c0..0e383c1a7 100644 --- a/src/renderer/api/subsonic/subsonic-controller.ts +++ b/src/renderer/api/subsonic/subsonic-controller.ts @@ -8,7 +8,7 @@ import { z } from 'zod'; import { contract, ssApiClient } from '/@/renderer/api/subsonic/subsonic-api'; import { randomString } from '/@/renderer/utils'; -import { ssNormalize } from '/@/shared/api/subsonic/subsonic-normalize'; +import { normalize } from '/@/shared/api/subsonic/subsonic-normalize'; import { AlbumListSortType, ssType, @@ -200,11 +200,11 @@ export const SubsonicController: ControllerEndpoint = { } return { - ...ssNormalize.albumArtist(artist, apiClientProps.server, 300), - albums: artist.album?.map((album) => ssNormalize.album(album, apiClientProps.server)), + ...normalize.albumArtist(artist, apiClientProps.server, 300), + albums: artist.album?.map((album) => normalize.album(album, apiClientProps.server)), similarArtists: artistInfo?.similarArtist?.map((artist) => - ssNormalize.albumArtist(artist, apiClientProps.server, 300), + normalize.albumArtist(artist, apiClientProps.server, 300), ) || null, }; }, @@ -224,7 +224,7 @@ export const SubsonicController: ControllerEndpoint = { const artists = (res.body.artists?.index || []).flatMap((index) => index.artist); let results = artists.map((artist) => - ssNormalize.albumArtist(artist, apiClientProps.server, 300), + normalize.albumArtist(artist, apiClientProps.server, 300), ); if (query.searchTerm) { @@ -260,7 +260,7 @@ export const SubsonicController: ControllerEndpoint = { throw new Error('Failed to get album detail'); } - return ssNormalize.album(res.body.album, apiClientProps.server); + return normalize.album(res.body.album, apiClientProps.server); }, getAlbumList: async (args) => { const { apiClientProps, query } = args; @@ -284,7 +284,7 @@ export const SubsonicController: ControllerEndpoint = { const results = res.body.searchResult3?.album?.map((album) => - ssNormalize.album(album, apiClientProps.server), + normalize.album(album, apiClientProps.server), ) || []; return { @@ -319,7 +319,7 @@ export const SubsonicController: ControllerEndpoint = { return artist.body.artist.album ?? []; }); - const items = albums.map((album) => ssNormalize.album(album, apiClientProps.server)); + const items = albums.map((album) => normalize.album(album, apiClientProps.server)); return { items: sortAlbumList(items, query.sortBy, query.sortOrder), @@ -341,7 +341,7 @@ export const SubsonicController: ControllerEndpoint = { const results = res.body.starred?.album?.map((album) => - ssNormalize.album(album, apiClientProps.server), + normalize.album(album, apiClientProps.server), ) || []; return { @@ -404,7 +404,7 @@ export const SubsonicController: ControllerEndpoint = { return { items: res.body.albumList2.album?.map((album) => - ssNormalize.album(album, apiClientProps.server, 300), + normalize.album(album, apiClientProps.server, 300), ) || [], startIndex: query.startIndex, totalRecordCount: null, @@ -574,7 +574,7 @@ export const SubsonicController: ControllerEndpoint = { } let results = artists.map((artist) => - ssNormalize.albumArtist(artist, apiClientProps.server, 300), + normalize.albumArtist(artist, apiClientProps.server, 300), ); if (query.searchTerm) { @@ -635,7 +635,7 @@ export const SubsonicController: ControllerEndpoint = { break; } - const genres = results.map(ssNormalize.genre); + const genres = results.map(normalize.genre); return { items: genres, @@ -674,7 +674,7 @@ export const SubsonicController: ControllerEndpoint = { throw new Error('Failed to get playlist detail'); } - return ssNormalize.playlist(res.body.playlist, apiClientProps.server); + return normalize.playlist(res.body.playlist, apiClientProps.server); }, getPlaylistList: async ({ apiClientProps, query }) => { const sortOrder = query.sortOrder.toLowerCase() as 'asc' | 'desc'; @@ -719,7 +719,7 @@ export const SubsonicController: ControllerEndpoint = { } return { - items: results.map((playlist) => ssNormalize.playlist(playlist, apiClientProps.server)), + items: results.map((playlist) => normalize.playlist(playlist, apiClientProps.server)), startIndex: 0, totalRecordCount: results.length, }; @@ -755,7 +755,7 @@ export const SubsonicController: ControllerEndpoint = { } let results = - res.body.playlist.entry?.map((song) => ssNormalize.song(song, apiClientProps.server)) || + res.body.playlist.entry?.map((song) => normalize.song(song, apiClientProps.server)) || []; if (query.sortBy && query.sortOrder) { @@ -788,7 +788,7 @@ export const SubsonicController: ControllerEndpoint = { const results = res.body.randomSongs?.song || []; return { - items: results.map((song) => ssNormalize.song(song, apiClientProps.server)), + items: results.map((song) => normalize.song(song, apiClientProps.server)), startIndex: 0, totalRecordCount: res.body.randomSongs?.song?.length || 0, }; @@ -873,7 +873,7 @@ export const SubsonicController: ControllerEndpoint = { return res.body.similarSongs.song.reduce((acc, song) => { if (song.id !== query.songId) { - acc.push(ssNormalize.song(song, apiClientProps.server)); + acc.push(normalize.song(song, apiClientProps.server)); } return acc; @@ -892,7 +892,7 @@ export const SubsonicController: ControllerEndpoint = { throw new Error('Failed to get song detail'); } - return ssNormalize.song(res.body.song, apiClientProps.server); + return normalize.song(res.body.song, apiClientProps.server); }, getSongList: async ({ apiClientProps, query }) => { const fromAlbumPromises: Promise>[] = []; @@ -918,7 +918,7 @@ export const SubsonicController: ControllerEndpoint = { return { items: res.body.searchResult3?.song?.map((song) => - ssNormalize.song(song, apiClientProps.server), + normalize.song(song, apiClientProps.server), ) || [], startIndex: query.startIndex, totalRecordCount: null, @@ -942,7 +942,7 @@ export const SubsonicController: ControllerEndpoint = { const results = res.body.songsByGenre?.song || []; return { - items: results.map((song) => ssNormalize.song(song, apiClientProps.server)) || [], + items: results.map((song) => normalize.song(song, apiClientProps.server)) || [], startIndex: 0, totalRecordCount: null, }; @@ -961,7 +961,7 @@ export const SubsonicController: ControllerEndpoint = { const results = (res.body.starred?.song || []).map((song) => - ssNormalize.song(song, apiClientProps.server), + normalize.song(song, apiClientProps.server), ) || []; return { @@ -1035,7 +1035,7 @@ export const SubsonicController: ControllerEndpoint = { } return { - items: results.map((song) => ssNormalize.song(song, apiClientProps.server)), + items: results.map((song) => normalize.song(song, apiClientProps.server)), startIndex: 0, totalRecordCount: results.length, }; @@ -1060,7 +1060,7 @@ export const SubsonicController: ControllerEndpoint = { return { items: res.body.searchResult3?.song?.map((song) => - ssNormalize.song(song, apiClientProps.server), + normalize.song(song, apiClientProps.server), ) || [], startIndex: 0, totalRecordCount: null, @@ -1297,7 +1297,7 @@ export const SubsonicController: ControllerEndpoint = { return { items: res.body.topSongs?.song?.map((song) => - ssNormalize.song(song, apiClientProps.server), + normalize.song(song, apiClientProps.server), ) || [], startIndex: 0, totalRecordCount: res.body.topSongs?.song?.length || 0, @@ -1367,13 +1367,13 @@ export const SubsonicController: ControllerEndpoint = { return { albumArtists: (res.body.searchResult3?.artist || [])?.map((artist) => - ssNormalize.albumArtist(artist, apiClientProps.server), + normalize.albumArtist(artist, apiClientProps.server), ), albums: (res.body.searchResult3?.album || []).map((album) => - ssNormalize.album(album, apiClientProps.server), + normalize.album(album, apiClientProps.server), ), songs: (res.body.searchResult3?.song || []).map((song) => - ssNormalize.song(song, apiClientProps.server), + normalize.song(song, apiClientProps.server), ), }; }, diff --git a/src/renderer/store/auth.store.ts b/src/renderer/store/auth.store.ts index 80ad00a1b..052746e12 100644 --- a/src/renderer/store/auth.store.ts +++ b/src/renderer/store/auth.store.ts @@ -7,7 +7,7 @@ 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 } from '/@/shared/types/domain/server-domain-types'; export interface AuthSlice extends AuthState { actions: { @@ -70,8 +70,8 @@ export const useAuthStore = createWithEqualityFn()( ...args, }; - state.serverList[id] = updatedServer; - state.currentServer = updatedServer; + state.serverList[id] = updatedServer as ServerListItem; + state.currentServer = updatedServer as ServerListItem; }); }, }, diff --git a/src/shared/api/api-controller.ts b/src/shared/api/api-controller.ts deleted file mode 100644 index 91259caaf..000000000 --- a/src/shared/api/api-controller.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { adapter as subsonicAdapter } from './subsonic/subsonic-controller'; - -import i18n from '/@/i18n/i18n'; -import { ApiController } from '/@/shared/types/adapter/api-controller-types'; -import { ServerType } from '/@/shared/types/domain/server-domain-types'; - -interface ApiControllerOptions { - type: ServerType; -} - -const adapters = { - [ServerType.JELLYFIN]: {}, - [ServerType.NAVIDROME]: {}, - [ServerType.SUBSONIC]: subsonicAdapter, -} as Record; - -export const apiController = (options: ApiControllerOptions): ApiController => { - const { type } = options; - - const adapter = adapters[type]; - - if (!adapter) { - throw new Error(i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })); - } - - return adapter; -}; diff --git a/src/shared/api/jellyfin/jellyfin-normalize.ts b/src/shared/api/jellyfin/jellyfin-normalize.ts index d3b6ae0fa..daac3d960 100644 --- a/src/shared/api/jellyfin/jellyfin-normalize.ts +++ b/src/shared/api/jellyfin/jellyfin-normalize.ts @@ -5,7 +5,7 @@ import { JFAlbum, JFGenre, JFMusicFolder, JFPlaylist } from '/@/shared/api/jelly import { jfType } from '/@/shared/api/jellyfin/jellyfin-types'; import { LibraryItem } from '/@/shared/types/domain-types'; import { Album } from '/@/shared/types/domain/album-domain-types'; -import { AlbumArtist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types'; +import { Artist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types'; import { Genre } from '/@/shared/types/domain/genre-domain-types'; import { Playlist } from '/@/shared/types/domain/playlist-domain-types'; import { @@ -213,6 +213,7 @@ const normalizeSong = ( } return { + _itemType: LibraryItem.SONG, album: item.Album, albumArtists: item.AlbumArtists?.map((entry) => ({ id: entry.Id, @@ -256,8 +257,7 @@ const normalizeSong = ( id: item.Id, imagePlaceholderUrl: null, imageUrl: getSongCoverArtUrl({ baseUrl: server?.url || '', item, size: imageSize || 100 }), - itemType: LibraryItem.SONG, - lastPlayedAt: null, + isCompilation: null, lyrics: null, name: item.Name, participants: getPeople(item), @@ -279,8 +279,9 @@ const normalizeSong = ( tags: getTags(item), trackNumber: item.IndexNumber, uniqueId: nanoid(), - updatedAt: item.DateCreated, + updatedDate: item.DateCreated, userFavorite: (item.UserData && item.UserData.IsFavorite) || false, + userLastPlayedDate: null, userRating: null, }; }; @@ -291,6 +292,9 @@ const normalizeAlbum = ( imageSize?: number, ): Album => { return { + _itemType: LibraryItem.ALBUM, + _serverId: server?.id || '', + _serverType: ServerType.JELLYFIN, albumArtist: item.AlbumArtist, albumArtists: item.AlbumArtists.map((entry) => ({ @@ -321,7 +325,6 @@ const normalizeAlbum = ( size: imageSize || 300, }), isCompilation: null, - itemType: LibraryItem.ALBUM, lastPlayedAt: null, mbzId: item.ProviderIds?.MusicBrainzAlbum || null, name: item.Name, @@ -330,8 +333,6 @@ const normalizeAlbum = ( playCount: item.UserData?.PlayCount || 0, releaseDate: item.PremiereDate?.split('T')[0] || null, releaseYear: item.ProductionYear || null, - serverId: server?.id || '', - serverType: ServerType.JELLYFIN, size: null, songCount: item?.ChildCount || null, songs: item.Songs?.map((song) => normalizeSong(song, server, '', imageSize)), @@ -349,7 +350,7 @@ const normalizeAlbumArtist = ( }, server: null | ServerListItem, imageSize?: number, -): AlbumArtist => { +): Artist => { const similarArtists = item.similarArtists?.Items?.filter((entry) => entry.Name !== 'Various Artists').map( (entry) => ({ @@ -364,6 +365,8 @@ const normalizeAlbumArtist = ( ) || []; return { + _serverId: server?.id || '', + _serverType: ServerType.JELLYFIN, albumCount: item.AlbumCount ?? null, backgroundImageUrl: null, biography: item.Overview || null, @@ -381,15 +384,13 @@ const normalizeAlbumArtist = ( size: imageSize || 300, }), itemType: LibraryItem.ALBUM_ARTIST, - lastPlayedAt: null, - mbz: item.ProviderIds?.MusicBrainzArtist || null, + mbzId: item.ProviderIds?.MusicBrainzArtist || null, name: item.Name, playCount: item.UserData?.PlayCount || 0, - serverId: server?.id || '', - serverType: ServerType.JELLYFIN, similarArtists, songCount: item.SongCount ?? null, userFavorite: item.UserData?.IsFavorite || false, + userLastPlayedDate: null, userRating: null, }; }; @@ -408,6 +409,9 @@ const normalizePlaylist = ( const imagePlaceholderUrl = null; return { + _itemType: LibraryItem.PLAYLIST, + _serverId: server?.id || '', + _serverType: ServerType.JELLYFIN, description: item.Overview || null, duration: item.RunTimeTicks / 10000, genres: item.GenreItems?.map((entry) => ({ @@ -419,14 +423,11 @@ const normalizePlaylist = ( id: item.Id, imagePlaceholderUrl, imageUrl: imageUrl || null, - itemType: LibraryItem.PLAYLIST, name: item.Name, owner: null, ownerId: null, public: null, rules: null, - serverId: server?.id || '', - serverType: ServerType.JELLYFIN, size: null, songCount: item?.ChildCount || null, sync: null, @@ -482,10 +483,10 @@ const getGenreCoverArtUrl = (args: { const normalizeGenre = (item: JFGenre, server: null | ServerListItem): Genre => { return { + _itemType: LibraryItem.GENRE, albumCount: undefined, id: item.Id, imageUrl: getGenreCoverArtUrl({ baseUrl: server?.url || '', item, size: 200 }), - itemType: LibraryItem.GENRE, name: item.Name, songCount: undefined, }; diff --git a/src/shared/api/navidrome/navidrome-normalize.ts b/src/shared/api/navidrome/navidrome-normalize.ts index f3e6ce3b6..2408e73d5 100644 --- a/src/shared/api/navidrome/navidrome-normalize.ts +++ b/src/shared/api/navidrome/navidrome-normalize.ts @@ -6,7 +6,7 @@ import { ndType } from '/@/shared/api/navidrome/navidrome-types'; import { ssType } from '/@/shared/api/subsonic/subsonic-types'; import { LibraryItem } from '/@/shared/types/domain-types'; import { Album } from '/@/shared/types/domain/album-domain-types'; -import { AlbumArtist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types'; +import { Artist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types'; import { Genre } from '/@/shared/types/domain/genre-domain-types'; import { Playlist } from '/@/shared/types/domain/playlist-domain-types'; import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types'; @@ -151,9 +151,9 @@ const normalizeSong = ( bpm: item.bpm ? item.bpm : null, channels: item.channels ? item.channels : null, comment: item.comment ? item.comment : null, - compilation: item.compilation, + isCompilation: item.compilation, container: item.suffix, - createdAt: item.createdAt.split('T')[0], + createdDate: item.createdAt.split('T')[0], discNumber: item.discNumber, discSubtitle: item.discSubtitle ? item.discSubtitle : null, duration: item.duration * 1000, @@ -170,8 +170,8 @@ const normalizeSong = ( id, imagePlaceholderUrl, imageUrl, - itemType: LibraryItem.SONG, - lastPlayedAt: normalizePlayDate(item), + _itemType: LibraryItem.SONG, + userLastPlayedDate: normalizePlayDate(item), lyrics: item.lyrics ? item.lyrics : null, name: item.title, // Thankfully, Windows is merciful and allows a mix of separators. So, we can use the @@ -196,7 +196,7 @@ const normalizeSong = ( tags: item.tags || null, trackNumber: item.trackNumber, uniqueId: nanoid(), - updatedAt: item.updatedAt, + updatedDate: item.updatedAt, userFavorite: item.starred || false, userRating: item.rating || null, }; @@ -237,7 +237,7 @@ const normalizeAlbum = ( imagePlaceholderUrl, imageUrl, isCompilation: item.compilation, - itemType: LibraryItem.ALBUM, + _itemType: LibraryItem.ALBUM, lastPlayedAt: normalizePlayDate(item), mbzId: item.mbzAlbumId || null, @@ -253,8 +253,8 @@ const normalizeAlbum = ( : new Date(Date.UTC(item.minYear, 0, 1)) ).toISOString(), releaseYear: item.minYear, - serverId: server?.id || 'unknown', - serverType: ServerType.NAVIDROME, + _serverId: server?.id || 'unknown', + _serverType: ServerType.NAVIDROME, size: item.size, songCount: item.songCount, songs: item.songs ? item.songs.map((song) => normalizeSong(song, server)) : undefined, @@ -271,7 +271,7 @@ const normalizeAlbumArtist = ( similarArtists?: z.infer['artistInfo']['similarArtist']; }, server: null | ServerListItem, -): AlbumArtist => { +): Artist => { let imageUrl = getImageUrl({ url: item?.largeImageUrl || null }); if (!imageUrl) { @@ -314,12 +314,12 @@ const normalizeAlbumArtist = ( id: item.id, imageUrl: imageUrl || null, itemType: LibraryItem.ALBUM_ARTIST, - lastPlayedAt: normalizePlayDate(item), - mbz: item.mbzArtistId || null, + userLastPlayedDate: normalizePlayDate(item), + mbzId: item.mbzArtistId || null, name: item.name, playCount: item.playCount || 0, - serverId: server?.id || 'unknown', - serverType: ServerType.NAVIDROME, + _serverId: server?.id || 'unknown', + _serverType: ServerType.NAVIDROME, similarArtists: item.similarArtists?.map((artist) => ({ id: artist.id, @@ -353,14 +353,14 @@ const normalizePlaylist = ( id: item.id, imagePlaceholderUrl, imageUrl, - itemType: LibraryItem.PLAYLIST, + _itemType: LibraryItem.PLAYLIST, name: item.name, owner: item.ownerName, ownerId: item.ownerId, public: item.public, rules: item?.rules || null, - serverId: server?.id || 'unknown', - serverType: ServerType.NAVIDROME, + _serverId: server?.id || 'unknown', + _serverType: ServerType.NAVIDROME, size: item.size, songCount: item.songCount, sync: item.sync, @@ -372,7 +372,7 @@ const normalizeGenre = (item: NDGenre): Genre => { albumCount: undefined, id: item.id, imageUrl: null, - itemType: LibraryItem.GENRE, + _itemType: LibraryItem.GENRE, name: item.name, songCount: undefined, }; diff --git a/src/shared/api/subsonic/subsonic-controller.ts b/src/shared/api/subsonic/subsonic-controller.ts index 4a2e74959..246035f4d 100644 --- a/src/shared/api/subsonic/subsonic-controller.ts +++ b/src/shared/api/subsonic/subsonic-controller.ts @@ -1,24 +1,20 @@ -import createClient, { Middleware } from 'openapi-fetch'; +import createClient, { Client, Middleware } from 'openapi-fetch'; import qs from 'qs'; import { paths } from './subsonic-schema'; import i18n from '/@/i18n/i18n'; import { normalize } from '/@/shared/api/subsonic/subsonic-normalize'; -import { ApiController } from '/@/shared/types/adapter/api-controller-types'; +import { API_CLIENT_NAME, ApiController } from '/@/shared/types/adapter/api-controller-types'; import { ApiControllerError } from '/@/shared/types/adapter/api-controller-types'; import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types'; -export function deserializeCredential(credential: string): Record { +function deserializeCredential(credential: string): Record { return JSON.parse(credential); } -export function serializeCredential( - username: string, - credential: Record, - type: string, -) { +function serializeCredential(username: string, credential: Record, type: string) { switch (type) { case 'apiKey': return JSON.stringify({ apiKey: credential.apiKey }); @@ -31,32 +27,36 @@ export function serializeCredential( } } -const middleware: Middleware = { - onRequest: async () => {}, -}; - -const client = createClient({ +export const apiClient = createClient({ querySerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }), }); -client.use(middleware); +export const middleware: (server: ServerListItem) => Middleware = (server: ServerListItem) => ({ + onRequest: async ({ params }) => { + const credential = deserializeCredential(server.credential); + + if (params.query) { + params.query.v = '1.16.1'; + params.query.c = API_CLIENT_NAME; + params.query.f = 'json'; + + for (const [key, value] of Object.entries(credential)) { + params.query[key] = value; + } + } + }, +}); + +const client: SubsonicClient = createClient({ + querySerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }), +}); type ErrorResponseArgs = { code?: number; message?: string; }; -// type Req = paths[T]['get']['parameters']; - -// type Res = T extends keyof paths -// ? paths[T]['get'] extends { -// responses: { -// '200': { content: { 'application/json': { 'subsonic-response'?: infer R } } }; -// }; -// } -// ? NonNullable -// : never -// : never; +type SubsonicClient = Client; function errorResponse(args: ErrorResponseArgs): [ApiControllerError, null] { const message = `${i18n.t('error.genericError', { postProcess: 'sentenceCase' }) as string}${ @@ -131,7 +131,7 @@ function toHttpErrorCode(subsonicErrorCode: number): number { } } -export const adapter: ApiController = { +export const controller: ApiController = { _utility: { getImageUrl: ( args: { id: string; size?: number; type: LibraryItem }, diff --git a/src/shared/types/adapter/api-controller-types.ts b/src/shared/types/adapter/api-controller-types.ts index 2f008efaf..f6e650546 100644 --- a/src/shared/types/adapter/api-controller-types.ts +++ b/src/shared/types/adapter/api-controller-types.ts @@ -211,3 +211,5 @@ export interface BaseQuery { } export type ExtractControllerResponse = T extends ApiControllerFn ? R : never; + +export const API_CLIENT_NAME = 'Feishin'; diff --git a/src/shared/types/domain/server-domain-types.ts b/src/shared/types/domain/server-domain-types.ts index 65f82afa5..64a9569c6 100644 --- a/src/shared/types/domain/server-domain-types.ts +++ b/src/shared/types/domain/server-domain-types.ts @@ -1,5 +1,4 @@ -import i18n from 'src/i18n/i18n'; - +import i18n from '/@/i18n/i18n'; import { BasePaginatedResponse } from '/@/shared/types/adapter/api-controller-types'; export enum ServerListSortOptions {