Pass full server to controller
This commit is contained in:
parent
1cbd61888f
commit
8f042ad448
6 changed files with 144 additions and 181 deletions
|
@ -1,5 +1,7 @@
|
||||||
import { initClient, initContract } from '@ts-rest/core';
|
import { initClient, initContract } from '@ts-rest/core';
|
||||||
import axios, { Method, AxiosError, AxiosResponse, isAxiosError } from 'axios';
|
import axios, { Method, AxiosError, AxiosResponse, isAxiosError } from 'axios';
|
||||||
|
import omitBy from 'lodash/omitBy';
|
||||||
|
import qs from 'qs';
|
||||||
import { ndType } from './navidrome-types';
|
import { ndType } from './navidrome-types';
|
||||||
import { resultWithHeaders } from '/@/renderer/api/utils';
|
import { resultWithHeaders } from '/@/renderer/api/utils';
|
||||||
import { toast } from '/@/renderer/components';
|
import { toast } from '/@/renderer/components';
|
||||||
|
@ -15,6 +17,7 @@ export const contract = c.router({
|
||||||
path: 'playlist/:id/tracks',
|
path: 'playlist/:id/tracks',
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.addToPlaylist),
|
200: resultWithHeaders(ndType._response.addToPlaylist),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authenticate: {
|
authenticate: {
|
||||||
|
@ -23,6 +26,7 @@ export const contract = c.router({
|
||||||
path: 'auth/login',
|
path: 'auth/login',
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.authenticate),
|
200: resultWithHeaders(ndType._response.authenticate),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
createPlaylist: {
|
createPlaylist: {
|
||||||
|
@ -31,6 +35,7 @@ export const contract = c.router({
|
||||||
path: 'playlist',
|
path: 'playlist',
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.createPlaylist),
|
200: resultWithHeaders(ndType._response.createPlaylist),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
deletePlaylist: {
|
deletePlaylist: {
|
||||||
|
@ -39,6 +44,7 @@ export const contract = c.router({
|
||||||
path: 'playlist/:id',
|
path: 'playlist/:id',
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.deletePlaylist),
|
200: resultWithHeaders(ndType._response.deletePlaylist),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getAlbumArtistDetail: {
|
getAlbumArtistDetail: {
|
||||||
|
@ -46,6 +52,7 @@ export const contract = c.router({
|
||||||
path: 'artist/:id',
|
path: 'artist/:id',
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.albumArtist),
|
200: resultWithHeaders(ndType._response.albumArtist),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getAlbumArtistList: {
|
getAlbumArtistList: {
|
||||||
|
@ -54,6 +61,7 @@ export const contract = c.router({
|
||||||
query: ndType._parameters.albumArtistList,
|
query: ndType._parameters.albumArtistList,
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.albumArtistList),
|
200: resultWithHeaders(ndType._response.albumArtistList),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getAlbumDetail: {
|
getAlbumDetail: {
|
||||||
|
@ -61,6 +69,7 @@ export const contract = c.router({
|
||||||
path: 'album/:id',
|
path: 'album/:id',
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.album),
|
200: resultWithHeaders(ndType._response.album),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getAlbumList: {
|
getAlbumList: {
|
||||||
|
@ -69,6 +78,7 @@ export const contract = c.router({
|
||||||
query: ndType._parameters.albumList,
|
query: ndType._parameters.albumList,
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.albumList),
|
200: resultWithHeaders(ndType._response.albumList),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getGenreList: {
|
getGenreList: {
|
||||||
|
@ -76,6 +86,7 @@ export const contract = c.router({
|
||||||
path: 'genre',
|
path: 'genre',
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.genreList),
|
200: resultWithHeaders(ndType._response.genreList),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getPlaylistDetail: {
|
getPlaylistDetail: {
|
||||||
|
@ -83,6 +94,7 @@ export const contract = c.router({
|
||||||
path: 'playlist/:id',
|
path: 'playlist/:id',
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.playlist),
|
200: resultWithHeaders(ndType._response.playlist),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getPlaylistList: {
|
getPlaylistList: {
|
||||||
|
@ -91,6 +103,7 @@ export const contract = c.router({
|
||||||
query: ndType._parameters.playlistList,
|
query: ndType._parameters.playlistList,
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.playlistList),
|
200: resultWithHeaders(ndType._response.playlistList),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getPlaylistSongList: {
|
getPlaylistSongList: {
|
||||||
|
@ -99,6 +112,7 @@ export const contract = c.router({
|
||||||
query: ndType._parameters.songList,
|
query: ndType._parameters.songList,
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.playlistSongList),
|
200: resultWithHeaders(ndType._response.playlistSongList),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getSongDetail: {
|
getSongDetail: {
|
||||||
|
@ -106,6 +120,7 @@ export const contract = c.router({
|
||||||
path: 'song/:id',
|
path: 'song/:id',
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.song),
|
200: resultWithHeaders(ndType._response.song),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getSongList: {
|
getSongList: {
|
||||||
|
@ -114,6 +129,7 @@ export const contract = c.router({
|
||||||
query: ndType._parameters.songList,
|
query: ndType._parameters.songList,
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.songList),
|
200: resultWithHeaders(ndType._response.songList),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getUserList: {
|
getUserList: {
|
||||||
|
@ -122,6 +138,7 @@ export const contract = c.router({
|
||||||
query: ndType._parameters.userList,
|
query: ndType._parameters.userList,
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.userList),
|
200: resultWithHeaders(ndType._response.userList),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
removeFromPlaylist: {
|
removeFromPlaylist: {
|
||||||
|
@ -131,6 +148,7 @@ export const contract = c.router({
|
||||||
query: ndType._parameters.removeFromPlaylist,
|
query: ndType._parameters.removeFromPlaylist,
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.removeFromPlaylist),
|
200: resultWithHeaders(ndType._response.removeFromPlaylist),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
updatePlaylist: {
|
updatePlaylist: {
|
||||||
|
@ -139,12 +157,17 @@ export const contract = c.router({
|
||||||
path: 'playlist/:id',
|
path: 'playlist/:id',
|
||||||
responses: {
|
responses: {
|
||||||
200: resultWithHeaders(ndType._response.updatePlaylist),
|
200: resultWithHeaders(ndType._response.updatePlaylist),
|
||||||
|
500: resultWithHeaders(ndType._response.error),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const axiosClient = axios.create({});
|
const axiosClient = axios.create({});
|
||||||
|
|
||||||
|
axiosClient.defaults.paramsSerializer = (params) => {
|
||||||
|
return qs.stringify(params, { arrayFormat: 'repeat' });
|
||||||
|
};
|
||||||
|
|
||||||
axiosClient.interceptors.response.use(
|
axiosClient.interceptors.response.use(
|
||||||
(response) => {
|
(response) => {
|
||||||
const serverId = useAuthStore.getState().currentServer?.id;
|
const serverId = useAuthStore.getState().currentServer?.id;
|
||||||
|
@ -175,20 +198,37 @@ axiosClient.interceptors.response.use(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ndApiClient = (args: { server: ServerListItem | string; signal?: AbortSignal }) => {
|
const parsePath = (fullPath: string) => {
|
||||||
const { server, signal } = args;
|
const [path, params] = fullPath.split('?');
|
||||||
|
|
||||||
|
const parsedParams = qs.parse(params);
|
||||||
|
const notNilParams = omitBy(parsedParams, (value) => value === 'undefined' || value === 'null');
|
||||||
|
|
||||||
|
return {
|
||||||
|
params: notNilParams,
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ndApiClient = (args: {
|
||||||
|
server?: ServerListItem;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
url?: string;
|
||||||
|
}) => {
|
||||||
|
const { server, url, signal } = args;
|
||||||
|
|
||||||
return initClient(contract, {
|
return initClient(contract, {
|
||||||
api: async ({ path, method, headers, body }) => {
|
api: async ({ path, method, headers, body }) => {
|
||||||
let baseUrl: string | undefined;
|
let baseUrl: string | undefined;
|
||||||
let token: string | undefined;
|
let token: string | undefined;
|
||||||
|
|
||||||
if (typeof server === 'object') {
|
const { params, path: api } = parsePath(path);
|
||||||
const selectedServer = useAuthStore.getState().actions.getServer(server.id);
|
|
||||||
baseUrl = `${selectedServer?.url}/api`;
|
if (server) {
|
||||||
token = selectedServer?.ndCredential;
|
baseUrl = `${server?.url}/api`;
|
||||||
|
token = server?.ndCredential;
|
||||||
} else {
|
} else {
|
||||||
baseUrl = server;
|
baseUrl = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -199,8 +239,9 @@ export const ndApiClient = (args: { server: ServerListItem | string; signal?: Ab
|
||||||
...(token && { 'x-nd-authorization': `Bearer ${token}` }),
|
...(token && { 'x-nd-authorization': `Bearer ${token}` }),
|
||||||
},
|
},
|
||||||
method: method as Method,
|
method: method as Method,
|
||||||
|
params,
|
||||||
signal,
|
signal,
|
||||||
url: `${baseUrl}/${path}`,
|
url: `${baseUrl}/${api}`,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
body: { data: result.data, headers: result.headers },
|
body: { data: result.data, headers: result.headers },
|
||||||
|
@ -222,5 +263,6 @@ export const ndApiClient = (args: { server: ServerListItem | string; signal?: Ab
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
|
jsonQuery: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,7 +49,7 @@ const authenticate = async (
|
||||||
): Promise<AuthenticationResponse> => {
|
): Promise<AuthenticationResponse> => {
|
||||||
const cleanServerUrl = url.replace(/\/$/, '');
|
const cleanServerUrl = url.replace(/\/$/, '');
|
||||||
|
|
||||||
const res = await ndApiClient({ server: cleanServerUrl }).authenticate({
|
const res = await ndApiClient({ url: cleanServerUrl }).authenticate({
|
||||||
body: {
|
body: {
|
||||||
password: body.password,
|
password: body.password,
|
||||||
username: body.username,
|
username: body.username,
|
||||||
|
@ -69,19 +69,15 @@ const authenticate = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserList = async (args: UserListArgs): Promise<UserListResponse> => {
|
const getUserList = async (args: UserListArgs): Promise<UserListResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).getUserList({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).getUserList({
|
|
||||||
query: {
|
query: {
|
||||||
_end: query.startIndex + (query.limit || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
_sort: userListSortMap.navidrome[query.sortBy],
|
_sort: userListSortMap.navidrome[query.sortBy],
|
||||||
_start: query.startIndex,
|
_start: query.startIndex,
|
||||||
...query.ndParams,
|
...query._custom?.navidrome,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -97,13 +93,9 @@ const getUserList = async (args: UserListArgs): Promise<UserListResponse> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getGenreList = async (args: GenreListArgs): Promise<GenreListResponse> => {
|
const getGenreList = async (args: GenreListArgs): Promise<GenreListResponse> => {
|
||||||
const { server, signal } = args;
|
const { apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).getGenreList({});
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).getGenreList({});
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error('Failed to get genre list');
|
throw new Error('Failed to get genre list');
|
||||||
|
@ -119,13 +111,9 @@ const getGenreList = async (args: GenreListArgs): Promise<GenreListResponse> =>
|
||||||
const getAlbumArtistDetail = async (
|
const getAlbumArtistDetail = async (
|
||||||
args: AlbumArtistDetailArgs,
|
args: AlbumArtistDetailArgs,
|
||||||
): Promise<AlbumArtistDetailResponse> => {
|
): Promise<AlbumArtistDetailResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).getAlbumArtistDetail({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).getAlbumArtistDetail({
|
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
|
@ -135,17 +123,17 @@ const getAlbumArtistDetail = async (
|
||||||
throw new Error('Failed to get album artist detail');
|
throw new Error('Failed to get album artist detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ndNormalize.albumArtist(res.body.data, server);
|
if (!apiClientProps.server) {
|
||||||
|
throw new Error('Server is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ndNormalize.albumArtist(res.body.data, apiClientProps.server);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<AlbumArtistListResponse> => {
|
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<AlbumArtistListResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).getAlbumArtistList({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).getAlbumArtistList({
|
|
||||||
query: {
|
query: {
|
||||||
_end: query.startIndex + (query.limit || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
|
@ -160,27 +148,29 @@ const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<AlbumArtis
|
||||||
throw new Error('Failed to get album artist list');
|
throw new Error('Failed to get album artist list');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!apiClientProps.server) {
|
||||||
|
throw new Error('Server is required');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.data.map((albumArtist) => ndNormalize.albumArtist(albumArtist, server)),
|
items: res.body.data.map((albumArtist) =>
|
||||||
|
ndNormalize.albumArtist(albumArtist, apiClientProps.server),
|
||||||
|
),
|
||||||
startIndex: query.startIndex,
|
startIndex: query.startIndex,
|
||||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumDetail = async (args: AlbumDetailArgs): Promise<AlbumDetailResponse> => {
|
const getAlbumDetail = async (args: AlbumDetailArgs): Promise<AlbumDetailResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const albumRes = await ndApiClient(apiClientProps).getAlbumDetail({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const albumRes = await ndApiClient({ server, signal }).getAlbumDetail({
|
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const songsData = await ndApiClient({ server, signal }).getSongList({
|
const songsData = await ndApiClient(apiClientProps).getSongList({
|
||||||
query: {
|
query: {
|
||||||
_end: 0,
|
_end: 0,
|
||||||
_order: 'ASC',
|
_order: 'ASC',
|
||||||
|
@ -194,17 +184,16 @@ const getAlbumDetail = async (args: AlbumDetailArgs): Promise<AlbumDetailRespons
|
||||||
throw new Error('Failed to get album detail');
|
throw new Error('Failed to get album detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ndNormalize.album({ ...albumRes.body.data, songs: songsData.body.data }, server);
|
return ndNormalize.album(
|
||||||
|
{ ...albumRes.body.data, songs: songsData.body.data },
|
||||||
|
apiClientProps.server,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumList = async (args: AlbumListArgs): Promise<AlbumListResponse> => {
|
const getAlbumList = async (args: AlbumListArgs): Promise<AlbumListResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).getAlbumList({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).getAlbumList({
|
|
||||||
query: {
|
query: {
|
||||||
_end: query.startIndex + (query.limit || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
|
@ -221,20 +210,18 @@ const getAlbumList = async (args: AlbumListArgs): Promise<AlbumListResponse> =>
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.data.map((album) => ndNormalize.album(album, server)),
|
items: res.body.data.map((album) => ndNormalize.album(album, apiClientProps.server)),
|
||||||
startIndex: query?.startIndex || 0,
|
startIndex: query?.startIndex || 0,
|
||||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSongList = async (args: SongListArgs): Promise<SongListResponse> => {
|
const getSongList = async (args: SongListArgs): Promise<SongListResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
console.log('query :>> ', query);
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).getSongList({
|
const res = await ndApiClient(apiClientProps).getSongList({
|
||||||
query: {
|
query: {
|
||||||
_end: query.startIndex + (query.limit || -1),
|
_end: query.startIndex + (query.limit || -1),
|
||||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
|
@ -252,20 +239,16 @@ const getSongList = async (args: SongListArgs): Promise<SongListResponse> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.data.map((song) => ndNormalize.song(song, server, '')),
|
items: res.body.data.map((song) => ndNormalize.song(song, apiClientProps.server, '')),
|
||||||
startIndex: query?.startIndex || 0,
|
startIndex: query?.startIndex || 0,
|
||||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSongDetail = async (args: SongDetailArgs): Promise<SongDetailResponse> => {
|
const getSongDetail = async (args: SongDetailArgs): Promise<SongDetailResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).getSongDetail({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).getSongDetail({
|
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
|
@ -275,23 +258,19 @@ const getSongDetail = async (args: SongDetailArgs): Promise<SongDetailResponse>
|
||||||
throw new Error('Failed to get song detail');
|
throw new Error('Failed to get song detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ndNormalize.song(res.body.data, server, '');
|
return ndNormalize.song(res.body.data, apiClientProps.server, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistResponse> => {
|
const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistResponse> => {
|
||||||
const { body, server } = args;
|
const { body, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).createPlaylist({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server }).createPlaylist({
|
|
||||||
body: {
|
body: {
|
||||||
comment: body.comment,
|
comment: body.comment,
|
||||||
name: body.name,
|
name: body.name,
|
||||||
public: body._custom.navidrome?.public,
|
public: body._custom?.navidrome?.public,
|
||||||
rules: body._custom.navidrome?.rules,
|
rules: body._custom?.navidrome?.rules,
|
||||||
sync: body._custom.navidrome?.sync,
|
sync: body._custom?.navidrome?.sync,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -306,19 +285,15 @@ const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistR
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatePlaylist = async (args: UpdatePlaylistArgs): Promise<UpdatePlaylistResponse> => {
|
const updatePlaylist = async (args: UpdatePlaylistArgs): Promise<UpdatePlaylistResponse> => {
|
||||||
const { query, body, server, signal } = args;
|
const { query, body, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).updatePlaylist({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).updatePlaylist({
|
|
||||||
body: {
|
body: {
|
||||||
comment: body.comment || '',
|
comment: body.comment || '',
|
||||||
name: body.name,
|
name: body.name,
|
||||||
public: body.ndParams?.public || false,
|
public: body._custom?.navidrome?.public || false,
|
||||||
rules: body.ndParams?.rules ? body.ndParams?.rules : undefined,
|
rules: body._custom?.navidrome?.rules ? body._custom.navidrome.rules : undefined,
|
||||||
sync: body.ndParams?.sync || undefined,
|
sync: body._custom?.navidrome?.sync || undefined,
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
|
@ -335,13 +310,9 @@ const updatePlaylist = async (args: UpdatePlaylistArgs): Promise<UpdatePlaylistR
|
||||||
};
|
};
|
||||||
|
|
||||||
const deletePlaylist = async (args: DeletePlaylistArgs): Promise<DeletePlaylistResponse> => {
|
const deletePlaylist = async (args: DeletePlaylistArgs): Promise<DeletePlaylistResponse> => {
|
||||||
const { query, server } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).deletePlaylist({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server }).deletePlaylist({
|
|
||||||
body: null,
|
body: null,
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
|
@ -356,13 +327,9 @@ const deletePlaylist = async (args: DeletePlaylistArgs): Promise<DeletePlaylistR
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPlaylistList = async (args: PlaylistListArgs): Promise<PlaylistListResponse> => {
|
const getPlaylistList = async (args: PlaylistListArgs): Promise<PlaylistListResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).getPlaylistList({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).getPlaylistList({
|
|
||||||
query: {
|
query: {
|
||||||
_end: query.startIndex + (query.limit || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
|
@ -377,20 +344,16 @@ const getPlaylistList = async (args: PlaylistListArgs): Promise<PlaylistListResp
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.data.map((item) => ndNormalize.playlist(item, server)),
|
items: res.body.data.map((item) => ndNormalize.playlist(item, apiClientProps.server)),
|
||||||
startIndex: query?.startIndex || 0,
|
startIndex: query?.startIndex || 0,
|
||||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPlaylistDetail = async (args: PlaylistDetailArgs): Promise<PlaylistDetailResponse> => {
|
const getPlaylistDetail = async (args: PlaylistDetailArgs): Promise<PlaylistDetailResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).getPlaylistDetail({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).getPlaylistDetail({
|
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
|
@ -400,19 +363,15 @@ const getPlaylistDetail = async (args: PlaylistDetailArgs): Promise<PlaylistDeta
|
||||||
throw new Error('Failed to get playlist detail');
|
throw new Error('Failed to get playlist detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ndNormalize.playlist(res.body.data, server);
|
return ndNormalize.playlist(res.body.data, apiClientProps.server);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPlaylistSongList = async (
|
const getPlaylistSongList = async (
|
||||||
args: PlaylistSongListArgs,
|
args: PlaylistSongListArgs,
|
||||||
): Promise<PlaylistSongListResponse> => {
|
): Promise<PlaylistSongListResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).getPlaylistSongList({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).getPlaylistSongList({
|
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
|
@ -429,20 +388,16 @@ const getPlaylistSongList = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.data.map((item) => ndNormalize.song(item, server, '')),
|
items: res.body.data.map((item) => ndNormalize.song(item, apiClientProps.server, '')),
|
||||||
startIndex: query?.startIndex || 0,
|
startIndex: query?.startIndex || 0,
|
||||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const addToPlaylist = async (args: AddToPlaylistArgs): Promise<AddToPlaylistResponse> => {
|
const addToPlaylist = async (args: AddToPlaylistArgs): Promise<AddToPlaylistResponse> => {
|
||||||
const { body, query, server } = args;
|
const { body, query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).addToPlaylist({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server }).addToPlaylist({
|
|
||||||
body: {
|
body: {
|
||||||
ids: body.songId,
|
ids: body.songId,
|
||||||
},
|
},
|
||||||
|
@ -461,13 +416,9 @@ const addToPlaylist = async (args: AddToPlaylistArgs): Promise<AddToPlaylistResp
|
||||||
const removeFromPlaylist = async (
|
const removeFromPlaylist = async (
|
||||||
args: RemoveFromPlaylistArgs,
|
args: RemoveFromPlaylistArgs,
|
||||||
): Promise<RemoveFromPlaylistResponse> => {
|
): Promise<RemoveFromPlaylistResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!server) {
|
const res = await ndApiClient(apiClientProps).removeFromPlaylist({
|
||||||
throw new Error('No server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ndApiClient({ server, signal }).removeFromPlaylist({
|
|
||||||
body: null,
|
body: null,
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
|
|
|
@ -157,7 +157,7 @@ const normalizeAlbumArtist = (
|
||||||
lastPlayedAt: item.playDate.includes('0001-') ? null : item.playDate,
|
lastPlayedAt: item.playDate.includes('0001-') ? null : item.playDate,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
playCount: item.playCount,
|
playCount: item.playCount,
|
||||||
serverId: server?.id || '',
|
serverId: server.id,
|
||||||
serverType: ServerType.NAVIDROME,
|
serverType: ServerType.NAVIDROME,
|
||||||
similarArtists: null,
|
similarArtists: null,
|
||||||
// similarArtists:
|
// similarArtists:
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { z } from 'zod';
|
||||||
|
|
||||||
const sortOrderValues = ['ASC', 'DESC'] as const;
|
const sortOrderValues = ['ASC', 'DESC'] as const;
|
||||||
|
|
||||||
|
const error = z.string();
|
||||||
|
|
||||||
const paginationParameters = z.object({
|
const paginationParameters = z.object({
|
||||||
_end: z.number().optional(),
|
_end: z.number().optional(),
|
||||||
_order: z.enum(sortOrderValues),
|
_order: z.enum(sortOrderValues),
|
||||||
|
@ -339,6 +341,7 @@ export const ndType = {
|
||||||
authenticate,
|
authenticate,
|
||||||
createPlaylist,
|
createPlaylist,
|
||||||
deletePlaylist,
|
deletePlaylist,
|
||||||
|
error,
|
||||||
genre,
|
genre,
|
||||||
genreList,
|
genreList,
|
||||||
playlist,
|
playlist,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { initClient, initContract } from '@ts-rest/core';
|
import { initClient, initContract } from '@ts-rest/core';
|
||||||
import axios, { Method, AxiosError, isAxiosError, AxiosResponse } from 'axios';
|
import axios, { Method, AxiosError, isAxiosError, AxiosResponse } from 'axios';
|
||||||
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
||||||
|
import { ServerListItem } from '/@/renderer/api/types';
|
||||||
import { toast } from '/@/renderer/components';
|
import { toast } from '/@/renderer/components';
|
||||||
import { useAuthStore } from '/@/renderer/store';
|
|
||||||
|
|
||||||
const c = initContract();
|
const c = initContract();
|
||||||
|
|
||||||
|
@ -95,29 +95,24 @@ axiosClient.interceptors.response.use(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ssApiClient = (args: { serverId?: string; signal?: AbortSignal; url?: string }) => {
|
export const ssApiClient = (args: {
|
||||||
const { serverId, url, signal } = args;
|
server?: ServerListItem;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
url?: string;
|
||||||
|
}) => {
|
||||||
|
const { server, url, signal } = args;
|
||||||
|
|
||||||
return initClient(contract, {
|
return initClient(contract, {
|
||||||
api: async ({ path, method, headers, body }) => {
|
api: async ({ path, method, headers, body }) => {
|
||||||
let baseUrl: string | undefined;
|
let baseUrl: string | undefined;
|
||||||
const authParams: Record<string, any> = {};
|
const authParams: Record<string, any> = {};
|
||||||
|
|
||||||
if (serverId) {
|
if (server) {
|
||||||
const selectedServer = useAuthStore.getState().actions.getServer(serverId);
|
baseUrl = `${server.url}/rest`;
|
||||||
|
const token = server.credential;
|
||||||
if (!selectedServer) {
|
|
||||||
return {
|
|
||||||
body: { data: null, headers: null },
|
|
||||||
status: 500,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
baseUrl = `${selectedServer?.url}/rest`;
|
|
||||||
const token = selectedServer.credential;
|
|
||||||
const params = token.split(/&?\w=/gm);
|
const params = token.split(/&?\w=/gm);
|
||||||
|
|
||||||
authParams.u = selectedServer.username;
|
authParams.u = server.username;
|
||||||
if (params?.length === 4) {
|
if (params?.length === 4) {
|
||||||
authParams.s = params[2];
|
authParams.s = params[2];
|
||||||
authParams.t = params[3];
|
authParams.t = params[3];
|
||||||
|
|
|
@ -72,13 +72,9 @@ const authenticate = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMusicFolderList = async (args: MusicFolderListArgs): Promise<MusicFolderListResponse> => {
|
const getMusicFolderList = async (args: MusicFolderListArgs): Promise<MusicFolderListResponse> => {
|
||||||
const { signal, serverId } = args;
|
const { apiClientProps } = args;
|
||||||
|
|
||||||
if (!serverId) {
|
const res = await ssApiClient(apiClientProps).getMusicFolderList({});
|
||||||
throw new Error('No server id');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ssApiClient({ serverId, signal }).getMusicFolderList({});
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error('Failed to get music folder list');
|
throw new Error('Failed to get music folder list');
|
||||||
|
@ -198,13 +194,9 @@ const getMusicFolderList = async (args: MusicFolderListArgs): Promise<MusicFolde
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const createFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
const createFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
||||||
const { serverId, query, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!serverId) {
|
const res = await ssApiClient(apiClientProps).createFavorite({
|
||||||
throw new Error('No server id');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ssApiClient({ serverId, signal }).createFavorite({
|
|
||||||
query: {
|
query: {
|
||||||
albumId: query.type === LibraryItem.ALBUM ? query.id : undefined,
|
albumId: query.type === LibraryItem.ALBUM ? query.id : undefined,
|
||||||
artistId: query.type === LibraryItem.ALBUM_ARTIST ? query.id : undefined,
|
artistId: query.type === LibraryItem.ALBUM_ARTIST ? query.id : undefined,
|
||||||
|
@ -223,13 +215,9 @@ const createFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
const removeFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
||||||
const { serverId, query, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!serverId) {
|
const res = await ssApiClient(apiClientProps).removeFavorite({
|
||||||
throw new Error('No server id');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ssApiClient({ serverId, signal }).removeFavorite({
|
|
||||||
query: {
|
query: {
|
||||||
albumId: query.type === LibraryItem.ALBUM ? query.id : undefined,
|
albumId: query.type === LibraryItem.ALBUM ? query.id : undefined,
|
||||||
artistId: query.type === LibraryItem.ALBUM_ARTIST ? query.id : undefined,
|
artistId: query.type === LibraryItem.ALBUM_ARTIST ? query.id : undefined,
|
||||||
|
@ -248,16 +236,12 @@ const removeFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const setRating = async (args: RatingArgs): Promise<RatingResponse> => {
|
const setRating = async (args: RatingArgs): Promise<RatingResponse> => {
|
||||||
const { serverId, query, signal } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!serverId) {
|
|
||||||
throw new Error('No server id');
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemIds = query.item.map((item) => item.id);
|
const itemIds = query.item.map((item) => item.id);
|
||||||
|
|
||||||
for (const id of itemIds) {
|
for (const id of itemIds) {
|
||||||
await ssApiClient({ serverId, signal }).setRating({
|
await ssApiClient(apiClientProps).setRating({
|
||||||
query: {
|
query: {
|
||||||
id,
|
id,
|
||||||
rating: query.rating,
|
rating: query.rating,
|
||||||
|
@ -269,13 +253,9 @@ const setRating = async (args: RatingArgs): Promise<RatingResponse> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTopSongList = async (args: TopSongListArgs): Promise<SongListResponse> => {
|
const getTopSongList = async (args: TopSongListArgs): Promise<SongListResponse> => {
|
||||||
const { signal, serverId, query, server } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!serverId || !server) {
|
const res = await ssApiClient(apiClientProps).getTopSongsList({
|
||||||
throw new Error('No server id');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ssApiClient({ serverId, signal }).getTopSongsList({
|
|
||||||
query: {
|
query: {
|
||||||
artist: query.artist,
|
artist: query.artist,
|
||||||
count: query.limit,
|
count: query.limit,
|
||||||
|
@ -287,7 +267,7 @@ const getTopSongList = async (args: TopSongListArgs): Promise<SongListResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.topSongs.song.map((song) => ssNormalize.song(song, server, '')),
|
items: res.body.topSongs.song.map((song) => ssNormalize.song(song, apiClientProps.server, '')),
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: res.body.topSongs.song.length || 0,
|
totalRecordCount: res.body.topSongs.song.length || 0,
|
||||||
};
|
};
|
||||||
|
@ -296,13 +276,9 @@ const getTopSongList = async (args: TopSongListArgs): Promise<SongListResponse>
|
||||||
const getArtistInfo = async (
|
const getArtistInfo = async (
|
||||||
args: ArtistInfoArgs,
|
args: ArtistInfoArgs,
|
||||||
): Promise<z.infer<typeof ssType._response.artistInfo>> => {
|
): Promise<z.infer<typeof ssType._response.artistInfo>> => {
|
||||||
const { signal, serverId, query } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!serverId) {
|
const res = await ssApiClient(apiClientProps).getArtistInfo({
|
||||||
throw new Error('No server id');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ssApiClient({ serverId, signal }).getArtistInfo({
|
|
||||||
query: {
|
query: {
|
||||||
count: query.limit,
|
count: query.limit,
|
||||||
id: query.artistId,
|
id: query.artistId,
|
||||||
|
@ -317,13 +293,9 @@ const getArtistInfo = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrobble = async (args: ScrobbleArgs): Promise<ScrobbleResponse> => {
|
const scrobble = async (args: ScrobbleArgs): Promise<ScrobbleResponse> => {
|
||||||
const { signal, serverId, query } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
if (!serverId) {
|
const res = await ssApiClient(apiClientProps).scrobble({
|
||||||
throw new Error('No server id');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await ssApiClient({ serverId, signal }).scrobble({
|
|
||||||
query: {
|
query: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
submission: query.submission,
|
submission: query.submission,
|
||||||
|
|
Reference in a new issue