Add all relevant subsonic endpoints to ts-rest

This commit is contained in:
jeffvli
2023-12-03 16:45:30 -08:00
parent 7b639b45f7
commit ca695ca155
3 changed files with 1258 additions and 186 deletions
+370 -37
View File
@@ -1,93 +1,426 @@
import { initClient, initContract } from '@ts-rest/core'; import { initClient, initContract } from '@ts-rest/core';
import axios, { Method, AxiosError, isAxiosError, AxiosResponse } from 'axios'; import axios, { AxiosError, AxiosResponse, Method, isAxiosError } from 'axios';
import omitBy from 'lodash/omitBy'; import omitBy from 'lodash/omitBy';
import qs from 'qs'; import qs from 'qs';
import { z } from 'zod'; import i18n from '/@/i18n/i18n';
import { ssType } from '/@/renderer/api/subsonic/subsonic-types'; import { SubsonicApi } from '/@/renderer/api/subsonic/subsonic-types';
import { ServerListItem } from '/@/renderer/api/types'; import { ServerListItem } from '/@/renderer/api/types';
import { toast } from '/@/renderer/components/toast/index'; import { toast } from '/@/renderer/components/toast/index';
import i18n from '/@/i18n/i18n';
const c = initContract(); const c = initContract();
export const contract = c.router({ export const contract = c.router({
authenticate: { changePassword: {
method: 'GET', method: 'GET',
path: 'ping.view', path: 'changePassword.view',
query: ssType._parameters.authenticate, query: SubsonicApi.changePassword.parameters,
responses: { responses: {
200: ssType._response.authenticate, 200: SubsonicApi.changePassword.response,
}, },
}, },
createFavorite: { createInternetRadioStation: {
method: 'GET', method: 'GET',
path: 'star.view', path: 'createInternetRadioStation.view',
query: ssType._parameters.createFavorite, query: SubsonicApi.createInternetRadioStation.parameters,
responses: { responses: {
200: ssType._response.createFavorite, 200: SubsonicApi.createInternetRadioStation.response,
},
},
createPlaylist: {
method: 'GET',
path: 'createPlaylist.view',
query: SubsonicApi.createPlaylist.parameters,
responses: {
200: SubsonicApi.createPlaylist.response,
},
},
createShare: {
method: 'GET',
path: 'createShare.view',
query: SubsonicApi.createShare.parameters,
responses: {
200: SubsonicApi.createShare.response,
},
},
createUser: {
method: 'GET',
path: 'createUser.view',
query: SubsonicApi.createUser.parameters,
responses: {
200: SubsonicApi.createUser.response,
},
},
deleteInternetRadioStation: {
method: 'GET',
path: 'deleteInternetRadioStation.view',
query: SubsonicApi.deleteInternetRadioStation.parameters,
responses: {
200: SubsonicApi.deleteInternetRadioStation.response,
},
},
deletePlaylist: {
method: 'GET',
path: 'deletePlaylist.view',
query: SubsonicApi.deletePlaylist.parameters,
responses: {
200: SubsonicApi.deletePlaylist.response,
},
},
deleteShare: {
method: 'GET',
path: 'deleteShare.view',
query: SubsonicApi.deleteShare.parameters,
responses: {
200: SubsonicApi.deleteShare.response,
},
},
deleteUser: {
method: 'GET',
path: 'deleteUser.view',
query: SubsonicApi.deleteUser.parameters,
responses: {
200: SubsonicApi.deleteUser.response,
},
},
getAlbum: {
method: 'GET',
path: 'getAlbum.view',
query: SubsonicApi.getAlbum.parameters,
responses: {
200: SubsonicApi.getAlbum.response,
},
},
getAlbumInfo: {
method: 'GET',
path: 'getAlbumInfo.view',
query: SubsonicApi.getAlbumInfo.parameters,
responses: {
200: SubsonicApi.getAlbumInfo.response,
},
},
getAlbumInfo2: {
method: 'GET',
path: 'getAlbumInfo2.view',
query: SubsonicApi.getAlbumInfo2.parameters,
responses: {
200: SubsonicApi.getAlbumInfo2.response,
},
},
getAlbumList: {
method: 'GET',
path: 'getAlbumList.view',
query: SubsonicApi.getAlbumList.parameters,
responses: {
200: SubsonicApi.getAlbumList.response,
},
},
getAlbumList2: {
method: 'GET',
path: 'getAlbumList2.view',
query: SubsonicApi.getAlbumList2.parameters,
responses: {
200: SubsonicApi.getAlbumList2.response,
},
},
getArtist: {
method: 'GET',
path: 'getArtist.view',
query: SubsonicApi.getArtist.parameters,
responses: {
200: SubsonicApi.getArtist.response,
}, },
}, },
getArtistInfo: { getArtistInfo: {
method: 'GET', method: 'GET',
path: 'getArtistInfo.view', path: 'getArtistInfo.view',
query: ssType._parameters.artistInfo, query: SubsonicApi.getArtistInfo.parameters,
responses: { responses: {
200: ssType._response.artistInfo, 200: SubsonicApi.getArtistInfo.response,
}, },
}, },
getMusicFolderList: { getArtistInfo2: {
method: 'GET',
path: 'getArtistInfo2.view',
query: SubsonicApi.getArtistInfo2.parameters,
responses: {
200: SubsonicApi.getArtistInfo2.response,
},
},
getArtists: {
method: 'GET',
path: 'getArtists.view',
query: SubsonicApi.getArtists.parameters,
responses: {
200: SubsonicApi.getArtists.response,
},
},
getGenres: {
method: 'GET',
path: 'getGenres.view',
query: SubsonicApi.getGenres.parameters,
responses: {
200: SubsonicApi.getGenres.response,
},
},
getIndexes: {
method: 'GET',
path: 'getIndexes.view',
query: SubsonicApi.getIndexes.parameters,
responses: {
200: SubsonicApi.getIndexes.response,
},
},
getInternetRadioStations: {
method: 'GET',
path: 'getInternetRadioStations.view',
query: SubsonicApi.getInternetRadioStations.parameters,
responses: {
200: SubsonicApi.getInternetRadioStations.response,
},
},
getLicense: {
method: 'GET',
path: 'getLicense.view',
query: SubsonicApi.getLicense.parameters,
responses: {
200: SubsonicApi.getLicense.response,
},
},
getLyrics: {
method: 'GET',
path: 'getLyrics.view',
query: SubsonicApi.getLyrics.parameters,
responses: {
200: SubsonicApi.getLyrics.response,
},
},
getMusicDirectory: {
method: 'GET',
path: 'getMusicDirectory.view',
query: SubsonicApi.getMusicDirectory.parameters,
responses: {
200: SubsonicApi.getMusicDirectory.response,
},
},
getMusicFolders: {
method: 'GET', method: 'GET',
path: 'getMusicFolders.view', path: 'getMusicFolders.view',
responses: { responses: {
200: ssType._response.musicFolderList, 200: SubsonicApi.getMusicFolders.response,
}, },
}, },
getRandomSongList: { getNowPlaying: {
method: 'GET',
path: 'getNowPlaying.view',
query: SubsonicApi.getNowPlaying.parameters,
responses: {
200: SubsonicApi.getNowPlaying.response,
},
},
getOpenSubsonicExtensions: {
method: 'GET',
path: 'getOpenSubsonicExtensions.view',
query: SubsonicApi.getOpenSubsonicExtensions.parameters,
responses: {
200: SubsonicApi.getOpenSubsonicExtensions.response,
},
},
getPlaylist: {
method: 'GET',
path: 'getPlaylist.view',
query: SubsonicApi.getPlaylist.parameters,
responses: {
200: SubsonicApi.getPlaylist.response,
},
},
getPlaylists: {
method: 'GET',
path: 'getPlaylists.view',
query: SubsonicApi.getPlaylists.parameters,
responses: {
200: SubsonicApi.getPlaylists.response,
},
},
getRandomSongs: {
method: 'GET', method: 'GET',
path: 'getRandomSongs.view', path: 'getRandomSongs.view',
query: ssType._parameters.randomSongList, query: SubsonicApi.getRandomSongs.parameters,
responses: { responses: {
200: ssType._response.randomSongList, 200: SubsonicApi.getRandomSongs.response,
}, },
}, },
getTopSongsList: { getScanStatus: {
method: 'GET',
path: 'getScanStatus.view',
responses: {
200: SubsonicApi.getScanStatus.response,
},
},
getShares: {
method: 'GET',
path: 'getShares.view',
query: SubsonicApi.getShares.parameters,
responses: {
200: SubsonicApi.getShares.response,
},
},
getSimilarSongs: {
method: 'GET',
path: 'getSimilarSongs.view',
query: SubsonicApi.getSimilarSongs.parameters,
responses: {
200: SubsonicApi.getSimilarSongs.response,
},
},
getSimilarSongs2: {
method: 'GET',
path: 'getSimilarSongs2.view',
query: SubsonicApi.getSimilarSongs2.parameters,
responses: {
200: SubsonicApi.getSimilarSongs2.response,
},
},
getSong: {
method: 'GET',
path: 'getSong.view',
query: SubsonicApi.getSong.parameters,
responses: {
200: SubsonicApi.getSong.response,
},
},
getSongsByGenre: {
method: 'GET',
path: 'getSongsByGenre.view',
query: SubsonicApi.getSongsByGenre.parameters,
responses: {
200: SubsonicApi.getSongsByGenre.response,
},
},
getStarred: {
method: 'GET',
path: 'getStarred.view',
query: SubsonicApi.getStarred.parameters,
responses: {
200: SubsonicApi.getStarred.response,
},
},
getStarred2: {
method: 'GET',
path: 'getStarred2.view',
query: SubsonicApi.getStarred2.parameters,
responses: {
200: SubsonicApi.getStarred2.response,
},
},
getTopSongs: {
method: 'GET', method: 'GET',
path: 'getTopSongs.view', path: 'getTopSongs.view',
query: ssType._parameters.topSongsList, query: SubsonicApi.getTopSongs.parameters,
responses: { responses: {
200: ssType._response.topSongsList, 200: SubsonicApi.getTopSongs.response,
}, },
}, },
removeFavorite: { getUser: {
method: 'GET', method: 'GET',
path: 'unstar.view', path: 'getUser.view',
query: ssType._parameters.removeFavorite, query: SubsonicApi.getUser.parameters,
responses: { responses: {
200: ssType._response.removeFavorite, 200: SubsonicApi.getUser.response,
},
},
getUsers: {
method: 'GET',
path: 'getUsers.view',
query: SubsonicApi.getUsers.parameters,
responses: {
200: SubsonicApi.getUsers.response,
},
},
ping: {
method: 'GET',
path: 'ping.view',
query: SubsonicApi.ping.parameters,
responses: {
200: SubsonicApi.ping.response,
}, },
}, },
scrobble: { scrobble: {
method: 'GET', method: 'GET',
path: 'scrobble.view', path: 'scrobble.view',
query: ssType._parameters.scrobble, query: SubsonicApi.scrobble.parameters,
responses: { responses: {
200: ssType._response.scrobble, 200: SubsonicApi.scrobble.response,
}, },
}, },
search3: { search3: {
method: 'GET', method: 'GET',
path: 'search3.view', path: 'search3.view',
query: ssType._parameters.search3, query: SubsonicApi.search3.parameters,
responses: { responses: {
200: ssType._response.search3, 200: SubsonicApi.search3.response,
}, },
}, },
setRating: { setRating: {
method: 'GET', method: 'GET',
path: 'setRating.view', path: 'setRating.view',
query: ssType._parameters.setRating, query: SubsonicApi.setRating.parameters,
responses: { responses: {
200: ssType._response.setRating, 200: SubsonicApi.setRating.response,
},
},
star: {
method: 'GET',
path: 'star.view',
query: SubsonicApi.star.parameters,
responses: {
200: SubsonicApi.star.response,
},
},
startScan: {
method: 'GET',
path: 'startScan.view',
responses: {
200: SubsonicApi.startScan.response,
},
},
unstar: {
method: 'GET',
path: 'unstar.view',
query: SubsonicApi.unstar.parameters,
responses: {
200: SubsonicApi.unstar.response,
},
},
updateInternetRadioStation: {
method: 'GET',
path: 'updateInternetRadioStation.view',
query: SubsonicApi.updateInternetRadioStation.parameters,
responses: {
200: SubsonicApi.updateInternetRadioStation.response,
},
},
updatePlaylist: {
method: 'GET',
path: 'updatePlaylist.view',
query: SubsonicApi.updatePlaylist.parameters,
responses: {
200: SubsonicApi.updatePlaylist.response,
},
},
updateShare: {
method: 'GET',
path: 'updateShare.view',
query: SubsonicApi.updateShare.parameters,
responses: {
200: SubsonicApi.updateShare.response,
},
},
updateUser: {
method: 'GET',
path: 'updateUser.view',
query: SubsonicApi.updateUser.parameters,
responses: {
200: SubsonicApi.updateUser.response,
}, },
}, },
}); });
@@ -110,6 +443,8 @@ axiosClient.interceptors.response.use(
title: i18n.t('error.genericError', { postProcess: 'sentenceCase' }) as string, title: i18n.t('error.genericError', { postProcess: 'sentenceCase' }) as string,
}); });
} }
return Promise.reject(data['subsonic-response'].error);
} }
return response; return response;
@@ -131,7 +466,7 @@ const parsePath = (fullPath: string) => {
}; };
}; };
export const ssApiClient = (args: { export const subsonicApiClient = (args: {
server: ServerListItem | null; server: ServerListItem | null;
signal?: AbortSignal; signal?: AbortSignal;
url?: string; url?: string;
@@ -162,9 +497,7 @@ export const ssApiClient = (args: {
} }
try { try {
const result = await axiosClient.request< const result = await axiosClient.request({
z.infer<typeof ssType._response.baseResponse>
>({
data: body, data: body,
headers, headers,
method: method as Method, method: method as Method,
@@ -180,7 +513,7 @@ export const ssApiClient = (args: {
}); });
return { return {
body: result.data['subsonic-response'], body: result.data,
headers: result.headers as any, headers: result.headers as any,
status: result.status, status: result.status,
}; };
@@ -1,7 +1,7 @@
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { z } from 'zod'; import { z } from 'zod';
import { ssType } from '/@/renderer/api/subsonic/subsonic-types'; import { SubsonicApi } from '/@/renderer/api/subsonic/subsonic-types';
import { QueueSong, LibraryItem, AlbumArtist, Album } from '/@/renderer/api/types'; import { QueueSong, LibraryItem, AlbumArtist, Album, Genre } from '/@/renderer/api/types';
import { ServerListItem, ServerType } from '/@/renderer/types'; import { ServerListItem, ServerType } from '/@/renderer/types';
const getCoverArtUrl = (args: { const getCoverArtUrl = (args: {
@@ -27,7 +27,7 @@ const getCoverArtUrl = (args: {
}; };
const normalizeSong = ( const normalizeSong = (
item: z.infer<typeof ssType._response.song>, item: z.infer<typeof SubsonicApi._baseTypes.song>,
server: ServerListItem | null, server: ServerListItem | null,
deviceId: string, deviceId: string,
): QueueSong => { ): QueueSong => {
@@ -105,7 +105,7 @@ const normalizeSong = (
}; };
const normalizeAlbumArtist = ( const normalizeAlbumArtist = (
item: z.infer<typeof ssType._response.albumArtist>, item: z.infer<typeof SubsonicApi._baseTypes.artist>,
server: ServerListItem | null, server: ServerListItem | null,
): AlbumArtist => { ): AlbumArtist => {
const imageUrl = const imageUrl =
@@ -138,7 +138,9 @@ const normalizeAlbumArtist = (
}; };
const normalizeAlbum = ( const normalizeAlbum = (
item: z.infer<typeof ssType._response.album>, item:
| z.infer<typeof SubsonicApi._baseTypes.album>
| z.infer<typeof SubsonicApi._baseTypes.albumListEntry>,
server: ServerListItem | null, server: ServerListItem | null,
): Album => { ): Album => {
const imageUrl = const imageUrl =
@@ -189,8 +191,20 @@ const normalizeAlbum = (
}; };
}; };
export const ssNormalize = { const normalizeGenre = (item: z.infer<typeof SubsonicApi._baseTypes.genre>): Genre => {
return {
albumCount: item.albumCount,
id: item.value,
imageUrl: null,
itemType: LibraryItem.GENRE,
name: item.value,
songCount: item.songCount,
};
};
export const subsonicNormalize = {
album: normalizeAlbum, album: normalizeAlbum,
albumArtist: normalizeAlbumArtist, albumArtist: normalizeAlbumArtist,
genre: normalizeGenre,
song: normalizeSong, song: normalizeSong,
}; };
File diff suppressed because it is too large Load Diff