progress on subsonic api

This commit is contained in:
jeffvli
2025-07-21 00:08:58 -07:00
parent 96221c8fa7
commit 98e8bda45d
94 changed files with 3083 additions and 798 deletions
+2 -4
View File
@@ -73,7 +73,7 @@ function createLoggedFunction<TRequest, TResponse>(
return undefined;
}
return async (request: TRequest, server: ServerListItem, options?: any) => {
return async (request: TRequest, requestOptions?: any) => {
const startTime = Date.now();
const requestId = Math.random().toString(36).substring(2, 15);
@@ -96,13 +96,11 @@ function createLoggedFunction<TRequest, TResponse>(
logger.info(`[API] ${functionName} called`, {
request: truncatedRequest,
requestId,
serverId: server.id,
serverType: server.type,
});
}
try {
const result = await originalFn(request, server, options);
const result = await originalFn(request, requestOptions);
const duration = Date.now() - startTime;
if (result[0]) {
+41 -13
View File
@@ -1,47 +1,75 @@
import { createLoggedApiController } from '/@/renderer/api/api-controller-logger';
import { getServerById } from '/@/renderer/store';
import { useAuthStore } from '/@/renderer/store';
import {
apiClient as subsonicApiClient,
controller as subsonicBaseAdapter,
createApiClient as subsonicApiClient,
authenticate as subsonicAuthenticate,
controller as subsonicController,
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 = {
export const serverApiMap = {
[ServerType.JELLYFIN]: {
apiClient: null,
authenticate: null,
controller: {},
middleware: null,
},
[ServerType.NAVIDROME]: {
apiClient: null,
authenticate: null,
controller: {},
middleware: null,
},
[ServerType.SUBSONIC]: {
apiClient: subsonicApiClient,
controller: createLoggedApiController(subsonicBaseAdapter),
authenticate: subsonicAuthenticate,
controller: subsonicController,
middleware: subsonicMiddleware,
},
};
export const api = (serverId: string): ApiController => {
const server = getServerById(serverId);
const getApiByServer = (serverId: string): ApiController => {
const servers = useAuthStore.getState().serverList;
const server = servers[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));
}
const { apiClient, controller, middleware } = serverApiMap[server.type];
if (!apiClient) {
throw new Error('No api client found');
}
return controller as ApiController;
const client = apiClient(server, middleware);
return createLoggedApiController(controller(client, server));
};
const getAppApi = () => {
const servers = useAuthStore.getState().serverList;
return Object.entries(servers).reduce(
(acc, [id]) => {
acc[id] = getApiByServer(id);
return acc;
},
{} as Record<string, ApiController>,
);
};
export const api = {
authenticate: (serverType: ServerType) => {
const { authenticate } = serverApiMap[serverType];
if (!serverType || !authenticate) {
throw new Error();
}
return authenticate;
},
controller: getAppApi(),
};
@@ -322,7 +322,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query.searchTerm,
SortBy: albumListSortMap.jellyfin[query.sortBy] || 'SortName',
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex,
StartIndex: query.offset,
...query._custom?.jellyfin,
Years: yearsFilter,
},
@@ -334,14 +334,14 @@ export const JellyfinController: ControllerEndpoint = {
return {
items: res.body.Items.map((item) => jfNormalize.album(item, apiClientProps.server)),
startIndex: query.startIndex,
offset: query.offset,
totalRecordCount: res.body.TotalRecordCount,
};
},
getAlbumListCount: async ({ apiClientProps, query }) =>
JellyfinController.getAlbumList({
apiClientProps,
query: { ...query, limit: 1, startIndex: 0 },
query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!),
getArtistList: async (args) => {
const { apiClientProps, query } = args;
@@ -356,7 +356,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query.searchTerm,
SortBy: albumArtistListSortMap.jellyfin[query.sortBy] || 'SortName,Name',
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex,
StartIndex: query.offset,
UserId: apiClientProps.server?.userId || undefined,
},
});
@@ -369,14 +369,14 @@ export const JellyfinController: ControllerEndpoint = {
items: res.body.Items.map((item) =>
jfNormalize.albumArtist(item, apiClientProps.server),
),
startIndex: query.startIndex,
offset: query.offset,
totalRecordCount: res.body.TotalRecordCount,
};
},
getArtistListCount: async ({ apiClientProps, query }) =>
JellyfinController.getArtistList({
apiClientProps,
query: { ...query, limit: 1, startIndex: 0 },
query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!),
getDownloadUrl: (args) => {
const { apiClientProps, query } = args;
@@ -398,7 +398,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query?.searchTerm,
SortBy: genreListSortMap.jellyfin[query.sortBy] || 'SortName',
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex,
StartIndex: query.offset,
UserId: apiClientProps.server?.userId,
},
});
@@ -409,7 +409,7 @@ export const JellyfinController: ControllerEndpoint = {
return {
items: res.body.Items.map((item) => jfNormalize.genre(item, apiClientProps.server)),
startIndex: query.startIndex || 0,
offset: query.offset || 0,
totalRecordCount: res.body?.TotalRecordCount || 0,
};
},
@@ -458,7 +458,7 @@ export const JellyfinController: ControllerEndpoint = {
return {
items: musicFolders.map(jfNormalize.musicFolder),
startIndex: 0,
offset: 0,
totalRecordCount: musicFolders?.length || 0,
};
},
@@ -505,7 +505,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query.searchTerm,
SortBy: playlistListSortMap.jellyfin[query.sortBy],
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex,
StartIndex: query.offset,
},
});
@@ -515,14 +515,14 @@ export const JellyfinController: ControllerEndpoint = {
return {
items: res.body.Items.map((item) => jfNormalize.playlist(item, apiClientProps.server)),
startIndex: 0,
offset: 0,
totalRecordCount: res.body.TotalRecordCount,
};
},
getPlaylistListCount: async ({ apiClientProps, query }) =>
JellyfinController.getPlaylistList({
apiClientProps,
query: { ...query, limit: 1, startIndex: 0 },
query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!),
getPlaylistSongList: async (args) => {
const { apiClientProps, query } = args;
@@ -552,7 +552,7 @@ export const JellyfinController: ControllerEndpoint = {
return {
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
startIndex: query.startIndex,
offset: query.startIndex,
totalRecordCount: res.body.TotalRecordCount,
};
},
@@ -602,7 +602,7 @@ export const JellyfinController: ControllerEndpoint = {
return {
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
startIndex: 0,
offset: 0,
totalRecordCount: res.body.Items.length || 0,
};
},
@@ -742,7 +742,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query.searchTerm,
SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName',
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex,
StartIndex: query.offset,
...query._custom?.jellyfin,
Years: yearsFilter,
},
@@ -777,7 +777,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query.searchTerm,
SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName',
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex,
StartIndex: query.offset,
...query._custom?.jellyfin,
Years: yearsFilter,
},
@@ -806,14 +806,14 @@ export const JellyfinController: ControllerEndpoint = {
items: items.map((item) =>
jfNormalize.song(item, apiClientProps.server, '', query.imageSize),
),
startIndex: query.startIndex,
offset: query.offset,
totalRecordCount,
};
},
getSongListCount: async ({ apiClientProps, query }) =>
JellyfinController.getSongList({
apiClientProps,
query: { ...query, limit: 1, startIndex: 0 },
query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!),
getTags: async (args) => {
const { apiClientProps, query } = args;
@@ -869,7 +869,7 @@ export const JellyfinController: ControllerEndpoint = {
return {
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
startIndex: 0,
offset: 0,
totalRecordCount: res.body.TotalRecordCount,
};
},
@@ -273,10 +273,10 @@ export const NavidromeController: ControllerEndpoint = {
const res = await ndApiClient(apiClientProps).getAlbumList({
query: {
_end: query.startIndex + (query.limit || 0),
_end: query.offset + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: albumListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
_start: query.offset,
artist_id: query.artistIds?.[0],
compilation: query.compilation,
genre_id: query.genres?.[0],
@@ -293,24 +293,24 @@ export const NavidromeController: ControllerEndpoint = {
return {
items: res.body.data.map((album) => ndNormalize.album(album, apiClientProps.server)),
startIndex: query?.startIndex || 0,
offset: query?.offset || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
},
getAlbumListCount: async ({ apiClientProps, query }) =>
NavidromeController.getAlbumList({
apiClientProps,
query: { ...query, limit: 1, startIndex: 0 },
query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!),
getArtistList: async (args) => {
const { apiClientProps, query } = args;
const res = await ndApiClient(apiClientProps).getAlbumArtistList({
query: {
_end: query.startIndex + (query.limit || 0),
_end: query.offset + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: albumArtistListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
_start: query.offset,
name: query.searchTerm,
...query._custom?.navidrome,
role: query.role || undefined,
@@ -335,14 +335,14 @@ export const NavidromeController: ControllerEndpoint = {
apiClientProps.server,
),
),
startIndex: query.startIndex,
offset: query.offset,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
},
getArtistListCount: async ({ apiClientProps, query }) =>
NavidromeController.getArtistList({
apiClientProps,
query: { ...query, limit: 1, startIndex: 0 },
query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!),
getDownloadUrl: SubsonicController.getDownloadUrl,
getGenreList: async (args) => {
@@ -350,10 +350,10 @@ export const NavidromeController: ControllerEndpoint = {
const res = await ndApiClient(apiClientProps).getGenreList({
query: {
_end: query.startIndex + (query.limit || 0),
_end: query.offset + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: genreListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
_start: query.offset,
name: query.searchTerm,
},
});
@@ -364,7 +364,7 @@ export const NavidromeController: ControllerEndpoint = {
return {
items: res.body.data.map((genre) => ndNormalize.genre(genre)),
startIndex: query.startIndex || 0,
offset: query.offset || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
},
@@ -400,10 +400,10 @@ export const NavidromeController: ControllerEndpoint = {
const res = await ndApiClient(apiClientProps).getPlaylistList({
query: {
_end: query.startIndex + (query.limit || 0),
_end: query.offset + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : undefined,
_start: query.startIndex,
_start: query.offset,
q: query.searchTerm,
...customQuery,
},
@@ -415,14 +415,14 @@ export const NavidromeController: ControllerEndpoint = {
return {
items: res.body.data.map((item) => ndNormalize.playlist(item, apiClientProps.server)),
startIndex: query?.startIndex || 0,
offset: query?.offset || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
},
getPlaylistListCount: async ({ apiClientProps, query }) =>
NavidromeController.getPlaylistList({
apiClientProps,
query: { ...query, limit: 1, startIndex: 0 },
query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!),
getPlaylistSongList: async (
args: PlaylistSongListRequest,
@@ -450,7 +450,7 @@ export const NavidromeController: ControllerEndpoint = {
return {
items: res.body.data.map((item) => ndNormalize.song(item, apiClientProps.server)),
startIndex: query?.startIndex || 0,
offset: query?.startIndex || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
},
@@ -576,10 +576,10 @@ export const NavidromeController: ControllerEndpoint = {
const res = await ndApiClient(apiClientProps).getSongList({
query: {
_end: query.startIndex + (query.limit || -1),
_end: query.offset + (query.limit || -1),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: songListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
_start: query.offset,
album_artist_id: query.albumArtistIds,
album_id: query.albumIds,
artist_id: query.artistIds,
@@ -599,14 +599,14 @@ export const NavidromeController: ControllerEndpoint = {
items: res.body.data.map((song) =>
ndNormalize.song(song, apiClientProps.server, query.imageSize),
),
startIndex: query?.startIndex || 0,
offset: query?.offset || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
},
getSongListCount: async ({ apiClientProps, query }) =>
NavidromeController.getSongList({
apiClientProps,
query: { ...query, limit: 1, startIndex: 0 },
query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!),
getStructuredLyrics: SubsonicController.getStructuredLyrics,
getTags: async (args) => {
@@ -655,10 +655,10 @@ export const NavidromeController: ControllerEndpoint = {
const res = await ndApiClient(apiClientProps).getUserList({
query: {
_end: query.startIndex + (query.limit || 0),
_end: query.offset + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: userListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
_start: query.offset,
...query._custom?.navidrome,
},
});
@@ -669,7 +669,7 @@ export const NavidromeController: ControllerEndpoint = {
return {
items: res.body.data.map((user) => ndNormalize.user(user)),
startIndex: query?.startIndex || 0,
offset: query?.offset || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
},
@@ -269,7 +269,7 @@ export const SubsonicController: ControllerEndpoint = {
const res = await ssApiClient(apiClientProps).search3({
query: {
albumCount: query.limit,
albumOffset: query.startIndex,
albumOffset: query.offset,
artistCount: 0,
artistOffset: 0,
query: query.searchTerm || '',
@@ -289,7 +289,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: results,
startIndex: query.startIndex,
offset: query.offset,
totalRecordCount: null,
};
}
@@ -323,7 +323,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: sortAlbumList(items, query.sortBy, query.sortOrder),
startIndex: 0,
offset: 0,
totalRecordCount: albums.length,
};
}
@@ -346,7 +346,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: sortAlbumList(results, query.sortBy, query.sortOrder),
startIndex: 0,
offset: 0,
totalRecordCount: res.body.starred?.album?.length || 0,
};
}
@@ -390,7 +390,7 @@ export const SubsonicController: ControllerEndpoint = {
fromYear,
genre: query.genres?.length ? query.genres[0] : undefined,
musicFolderId: query.musicFolderId,
offset: query.startIndex,
offset: query.offset,
size: query.limit,
toYear,
type,
@@ -406,7 +406,7 @@ export const SubsonicController: ControllerEndpoint = {
res.body.albumList2.album?.map((album) =>
normalize.album(album, apiClientProps.server, 300),
) || [],
startIndex: query.startIndex,
offset: query.offset,
totalRecordCount: null,
};
},
@@ -591,7 +591,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: results,
startIndex: query.startIndex,
offset: query.offset,
totalRecordCount: results?.length || 0,
};
},
@@ -639,7 +639,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: genres,
startIndex: 0,
offset: 0,
totalRecordCount: genres.length,
};
},
@@ -657,7 +657,7 @@ export const SubsonicController: ControllerEndpoint = {
id: folder.id.toString(),
name: folder.name,
})),
startIndex: 0,
offset: 0,
totalRecordCount: res.body.musicFolders.musicFolder.length,
};
},
@@ -720,7 +720,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: results.map((playlist) => normalize.playlist(playlist, apiClientProps.server)),
startIndex: 0,
offset: 0,
totalRecordCount: results.length,
};
},
@@ -764,7 +764,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: results,
startIndex: 0,
offset: 0,
totalRecordCount: results?.length || 0,
};
},
@@ -789,7 +789,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: results.map((song) => normalize.song(song, apiClientProps.server)),
startIndex: 0,
offset: 0,
totalRecordCount: res.body.randomSongs?.song?.length || 0,
};
},
@@ -907,7 +907,7 @@ export const SubsonicController: ControllerEndpoint = {
artistOffset: 0,
query: query.searchTerm || '',
songCount: query.limit,
songOffset: query.startIndex,
songOffset: query.offset,
},
});
@@ -920,7 +920,7 @@ export const SubsonicController: ControllerEndpoint = {
res.body.searchResult3?.song?.map((song) =>
normalize.song(song, apiClientProps.server),
) || [],
startIndex: query.startIndex,
offset: query.offset,
totalRecordCount: null,
};
}
@@ -931,7 +931,7 @@ export const SubsonicController: ControllerEndpoint = {
count: query.limit,
genre: query.genreIds[0],
musicFolderId: query.musicFolderId,
offset: query.startIndex,
offset: query.offset,
},
});
@@ -943,7 +943,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: results.map((song) => normalize.song(song, apiClientProps.server)) || [],
startIndex: 0,
offset: 0,
totalRecordCount: null,
};
}
@@ -966,7 +966,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: sortSongList(results, query.sortBy, query.sortOrder),
startIndex: 0,
offset: 0,
totalRecordCount: (res.body.starred?.song || []).length || 0,
};
}
@@ -1036,7 +1036,7 @@ export const SubsonicController: ControllerEndpoint = {
return {
items: results.map((song) => normalize.song(song, apiClientProps.server)),
startIndex: 0,
offset: 0,
totalRecordCount: results.length,
};
}
@@ -1049,7 +1049,7 @@ export const SubsonicController: ControllerEndpoint = {
artistOffset: 0,
query: query.searchTerm || '',
songCount: query.limit,
songOffset: query.startIndex,
songOffset: query.offset,
},
});
@@ -1062,7 +1062,7 @@ export const SubsonicController: ControllerEndpoint = {
res.body.searchResult3?.song?.map((song) =>
normalize.song(song, apiClientProps.server),
) || [],
startIndex: 0,
offset: 0,
totalRecordCount: null,
};
},
@@ -1299,7 +1299,7 @@ export const SubsonicController: ControllerEndpoint = {
res.body.topSongs?.song?.map((song) =>
normalize.song(song, apiClientProps.server),
) || [],
startIndex: 0,
offset: 0,
totalRecordCount: res.body.topSongs?.song?.length || 0,
};
},