mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
Add initial API controller
- Migrate from axios to ky
This commit is contained in:
Generated
+16
-47
@@ -23,7 +23,6 @@
|
|||||||
"@vitejs/plugin-react": "^2.2.0",
|
"@vitejs/plugin-react": "^2.2.0",
|
||||||
"ag-grid-community": "^28.2.1",
|
"ag-grid-community": "^28.2.1",
|
||||||
"ag-grid-react": "^28.2.1",
|
"ag-grid-react": "^28.2.1",
|
||||||
"axios": "^0.27.2",
|
|
||||||
"electron-localshortcut": "^3.2.1",
|
"electron-localshortcut": "^3.2.1",
|
||||||
"electron-store": "^8.1.0",
|
"electron-store": "^8.1.0",
|
||||||
"electron-updater": "5.3.0",
|
"electron-updater": "5.3.0",
|
||||||
@@ -3731,7 +3730,8 @@
|
|||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/at-least-node": {
|
"node_modules/at-least-node": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -3824,15 +3824,6 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
|
||||||
"version": "0.27.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
|
||||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"follow-redirects": "^1.14.9",
|
|
||||||
"form-data": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||||
@@ -4868,6 +4859,7 @@
|
|||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
},
|
},
|
||||||
@@ -5355,6 +5347,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
@@ -8031,25 +8024,6 @@
|
|||||||
"integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
|
"integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
|
||||||
"version": "1.15.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
|
||||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"debug": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/for-in": {
|
"node_modules/for-in": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||||
@@ -8063,6 +8037,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
@@ -11097,6 +11072,7 @@
|
|||||||
"version": "1.51.0",
|
"version": "1.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
|
||||||
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
|
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -11105,6 +11081,7 @@
|
|||||||
"version": "2.1.34",
|
"version": "2.1.34",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
|
||||||
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
|
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime-db": "1.51.0"
|
"mime-db": "1.51.0"
|
||||||
},
|
},
|
||||||
@@ -20133,7 +20110,8 @@
|
|||||||
"asynckit": {
|
"asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"at-least-node": {
|
"at-least-node": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -20196,15 +20174,6 @@
|
|||||||
"integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==",
|
"integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"axios": {
|
|
||||||
"version": "0.27.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
|
||||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
|
||||||
"requires": {
|
|
||||||
"follow-redirects": "^1.14.9",
|
|
||||||
"form-data": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"axobject-query": {
|
"axobject-query": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||||
@@ -20985,6 +20954,7 @@
|
|||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
}
|
}
|
||||||
@@ -21340,7 +21310,8 @@
|
|||||||
"delayed-stream": {
|
"delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"detect-newline": {
|
"detect-newline": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@@ -23256,11 +23227,6 @@
|
|||||||
"integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
|
"integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"follow-redirects": {
|
|
||||||
"version": "1.15.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
|
||||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
|
||||||
},
|
|
||||||
"for-in": {
|
"for-in": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||||
@@ -23271,6 +23237,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
@@ -25555,12 +25522,14 @@
|
|||||||
"mime-db": {
|
"mime-db": {
|
||||||
"version": "1.51.0",
|
"version": "1.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
|
||||||
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
|
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"mime-types": {
|
"mime-types": {
|
||||||
"version": "2.1.34",
|
"version": "2.1.34",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
|
||||||
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
|
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"mime-db": "1.51.0"
|
"mime-db": "1.51.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,6 @@
|
|||||||
"@vitejs/plugin-react": "^2.2.0",
|
"@vitejs/plugin-react": "^2.2.0",
|
||||||
"ag-grid-community": "^28.2.1",
|
"ag-grid-community": "^28.2.1",
|
||||||
"ag-grid-react": "^28.2.1",
|
"ag-grid-react": "^28.2.1",
|
||||||
"axios": "^0.27.2",
|
|
||||||
"electron-localshortcut": "^3.2.1",
|
"electron-localshortcut": "^3.2.1",
|
||||||
"electron-store": "^8.1.0",
|
"electron-store": "^8.1.0",
|
||||||
"electron-updater": "5.3.0",
|
"electron-updater": "5.3.0",
|
||||||
|
|||||||
@@ -1,63 +1,173 @@
|
|||||||
import { useAuthStore } from '../store/auth.store';
|
import { useAuthStore } from '/@/store';
|
||||||
import { navidromeApi } from './navidrome.api';
|
import { navidromeApi } from '/@/api/navidrome.api';
|
||||||
|
import { toast } from '/@/components';
|
||||||
import type {
|
import type {
|
||||||
AlbumDetailQuery,
|
AlbumDetailArgs,
|
||||||
AlbumDetailResponse,
|
RawAlbumDetailResponse,
|
||||||
AlbumListParams,
|
RawAlbumListResponse,
|
||||||
AlbumListResponse,
|
AlbumListArgs,
|
||||||
} from './types';
|
SongListArgs,
|
||||||
|
RawSongListResponse,
|
||||||
|
SongDetailArgs,
|
||||||
|
RawSongDetailResponse,
|
||||||
|
AlbumArtistDetailArgs,
|
||||||
|
RawAlbumArtistDetailResponse,
|
||||||
|
AlbumArtistListArgs,
|
||||||
|
RawAlbumArtistListResponse,
|
||||||
|
RatingArgs,
|
||||||
|
RawRatingResponse,
|
||||||
|
FavoriteArgs,
|
||||||
|
RawFavoriteResponse,
|
||||||
|
GenreListArgs,
|
||||||
|
RawGenreListResponse,
|
||||||
|
CreatePlaylistArgs,
|
||||||
|
RawCreatePlaylistResponse,
|
||||||
|
DeletePlaylistArgs,
|
||||||
|
RawDeletePlaylistResponse,
|
||||||
|
PlaylistDetailArgs,
|
||||||
|
RawPlaylistDetailResponse,
|
||||||
|
PlaylistListArgs,
|
||||||
|
RawPlaylistListResponse,
|
||||||
|
} from '/@/api/types';
|
||||||
|
import { subsonicApi } from '/@/api/subsonic.api';
|
||||||
|
|
||||||
export const getServerType = () => {
|
export type ControllerEndpoint = Partial<{
|
||||||
const server = useAuthStore.getState().currentServer;
|
clearPlaylist: () => void;
|
||||||
|
createFavorite: (args: FavoriteArgs) => Promise<RawFavoriteResponse>;
|
||||||
|
createPlaylist: (args: CreatePlaylistArgs) => Promise<RawCreatePlaylistResponse>;
|
||||||
|
deleteFavorite: (args: FavoriteArgs) => Promise<RawFavoriteResponse>;
|
||||||
|
deletePlaylist: (args: DeletePlaylistArgs) => Promise<RawDeletePlaylistResponse>;
|
||||||
|
getAlbumArtistDetail: (args: AlbumArtistDetailArgs) => Promise<RawAlbumArtistDetailResponse>;
|
||||||
|
getAlbumArtistList: (args: AlbumArtistListArgs) => Promise<RawAlbumArtistListResponse>;
|
||||||
|
getAlbumDetail: (args: AlbumDetailArgs) => Promise<RawAlbumDetailResponse>;
|
||||||
|
getAlbumList: (args: AlbumListArgs) => Promise<RawAlbumListResponse>;
|
||||||
|
getArtistDetail: () => void;
|
||||||
|
getArtistList: () => void;
|
||||||
|
getFavoritesList: () => void;
|
||||||
|
getFolderItemList: () => void;
|
||||||
|
getFolderList: () => void;
|
||||||
|
getFolderSongs: () => void;
|
||||||
|
getGenreList: (args: GenreListArgs) => Promise<RawGenreListResponse>;
|
||||||
|
getMusicFolderList: () => void;
|
||||||
|
getPlaylistDetail: (args: PlaylistDetailArgs) => Promise<RawPlaylistDetailResponse>;
|
||||||
|
getPlaylistList: (args: PlaylistListArgs) => Promise<RawPlaylistListResponse>;
|
||||||
|
getSongDetail: (args: SongDetailArgs) => Promise<RawSongDetailResponse>;
|
||||||
|
getSongList: (args: SongListArgs) => Promise<RawSongListResponse>;
|
||||||
|
updatePlaylist: () => void;
|
||||||
|
updateRating: (args: RatingArgs) => Promise<RawRatingResponse>;
|
||||||
|
}>;
|
||||||
|
|
||||||
if (!server) {
|
type ApiController = {
|
||||||
return null;
|
jellyfin: ControllerEndpoint;
|
||||||
}
|
navidrome: ControllerEndpoint;
|
||||||
|
subsonic: ControllerEndpoint;
|
||||||
return server.type;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumDetail = async (
|
const endpoints: ApiController = {
|
||||||
query: AlbumDetailQuery,
|
jellyfin: {
|
||||||
signal?: AbortSignal,
|
clearPlaylist: undefined,
|
||||||
): Promise<AlbumDetailResponse> => {
|
createFavorite: undefined,
|
||||||
const serverType = getServerType();
|
createPlaylist: undefined,
|
||||||
if (!serverType) return null;
|
deleteFavorite: undefined,
|
||||||
|
deletePlaylist: undefined,
|
||||||
const functions = {
|
getAlbumArtistDetail: undefined,
|
||||||
jellyfin: null,
|
getAlbumArtistList: undefined,
|
||||||
navidrome: navidromeApi.getAlbumDetail,
|
getAlbumDetail: undefined,
|
||||||
subsonic: null,
|
getAlbumList: undefined,
|
||||||
};
|
getArtistDetail: undefined,
|
||||||
|
getArtistList: undefined,
|
||||||
if (functions[serverType] === null) {
|
getFavoritesList: undefined,
|
||||||
return null;
|
getFolderItemList: undefined,
|
||||||
}
|
getFolderList: undefined,
|
||||||
|
getFolderSongs: undefined,
|
||||||
return functions[serverType]?.(query, signal);
|
getGenreList: undefined,
|
||||||
|
getMusicFolderList: undefined,
|
||||||
|
getPlaylistDetail: undefined,
|
||||||
|
getPlaylistList: undefined,
|
||||||
|
getSongList: undefined,
|
||||||
|
updatePlaylist: undefined,
|
||||||
|
updateRating: undefined,
|
||||||
|
},
|
||||||
|
navidrome: {
|
||||||
|
clearPlaylist: undefined,
|
||||||
|
createFavorite: subsonicApi.createFavorite,
|
||||||
|
createPlaylist: navidromeApi.createPlaylist,
|
||||||
|
deleteFavorite: subsonicApi.deleteFavorite,
|
||||||
|
deletePlaylist: navidromeApi.deletePlaylist,
|
||||||
|
getAlbumArtistDetail: navidromeApi.getAlbumArtistDetail,
|
||||||
|
getAlbumArtistList: navidromeApi.getAlbumArtistList,
|
||||||
|
getAlbumDetail: navidromeApi.getAlbumDetail,
|
||||||
|
getAlbumList: navidromeApi.getAlbumList,
|
||||||
|
getArtistDetail: undefined,
|
||||||
|
getArtistList: undefined,
|
||||||
|
getFavoritesList: undefined,
|
||||||
|
getFolderItemList: undefined,
|
||||||
|
getFolderList: undefined,
|
||||||
|
getFolderSongs: undefined,
|
||||||
|
getGenreList: navidromeApi.getGenreList,
|
||||||
|
getMusicFolderList: undefined,
|
||||||
|
getPlaylistDetail: navidromeApi.getPlaylistDetail,
|
||||||
|
getPlaylistList: navidromeApi.getPlaylistList,
|
||||||
|
getSongList: navidromeApi.getSongList,
|
||||||
|
updatePlaylist: undefined,
|
||||||
|
updateRating: subsonicApi.updateRating,
|
||||||
|
},
|
||||||
|
subsonic: {
|
||||||
|
clearPlaylist: undefined,
|
||||||
|
createFavorite: subsonicApi.createFavorite,
|
||||||
|
createPlaylist: undefined,
|
||||||
|
deleteFavorite: subsonicApi.deleteFavorite,
|
||||||
|
deletePlaylist: undefined,
|
||||||
|
getAlbumArtistDetail: subsonicApi.getAlbumArtistDetail,
|
||||||
|
getAlbumArtistList: subsonicApi.getAlbumArtistList,
|
||||||
|
getAlbumDetail: subsonicApi.getAlbumDetail,
|
||||||
|
getAlbumList: subsonicApi.getAlbumList,
|
||||||
|
getArtistDetail: undefined,
|
||||||
|
getArtistList: undefined,
|
||||||
|
getFavoritesList: undefined,
|
||||||
|
getFolderItemList: undefined,
|
||||||
|
getFolderList: undefined,
|
||||||
|
getFolderSongs: undefined,
|
||||||
|
getGenreList: undefined,
|
||||||
|
getMusicFolderList: undefined,
|
||||||
|
getPlaylistDetail: undefined,
|
||||||
|
getPlaylistList: undefined,
|
||||||
|
getSongList: undefined,
|
||||||
|
updatePlaylist: undefined,
|
||||||
|
updateRating: undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumList = async (
|
const apiController = (endpoint: keyof ControllerEndpoint) => {
|
||||||
params: AlbumListParams,
|
const serverType = useAuthStore.getState().currentServer?.type;
|
||||||
signal?: AbortSignal,
|
|
||||||
): Promise<AlbumListResponse> => {
|
|
||||||
const serverType = getServerType();
|
|
||||||
if (!serverType) return null;
|
|
||||||
|
|
||||||
const functions = {
|
if (!serverType) {
|
||||||
jellyfin: null,
|
toast.error({ message: 'No server selected', title: 'Unable to route request' });
|
||||||
navidrome: navidromeApi.getAlbumList,
|
return () => undefined;
|
||||||
subsonic: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (functions[serverType] === null) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return functions[serverType]?.(params, signal);
|
const controllerFn = endpoints[serverType][endpoint];
|
||||||
|
|
||||||
|
if (typeof controllerFn !== 'function') {
|
||||||
|
toast.error({
|
||||||
|
message: `Endpoint ${endpoint} is not implemented for ${serverType}`,
|
||||||
|
title: 'Unable to route request',
|
||||||
|
});
|
||||||
|
return () => undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoints[serverType][endpoint];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const apiController = {
|
const getAlbumList = async (args: AlbumListArgs) => {
|
||||||
|
return (apiController('getAlbumList') as ControllerEndpoint['getAlbumList'])?.(args);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAlbumDetail = async (args: AlbumDetailArgs) => {
|
||||||
|
return (apiController('getAlbumDetail') as ControllerEndpoint['getAlbumDetail'])?.(args);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const controller = {
|
||||||
getAlbumDetail,
|
getAlbumDetail,
|
||||||
getAlbumList,
|
getAlbumList,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,433 @@
|
|||||||
|
export type JFBaseResponse = {
|
||||||
|
StartIndex: number;
|
||||||
|
TotalRecordCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface JFMusicFoldersResponse extends JFBaseResponse {
|
||||||
|
Items: JFMusicFolder[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JFGenreResponse extends JFBaseResponse {
|
||||||
|
Items: JFGenre[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JFAlbumArtistsResponse extends JFBaseResponse {
|
||||||
|
Items: JFAlbumArtist[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JFArtistsResponse extends JFBaseResponse {
|
||||||
|
Items: JFAlbumArtist[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JFAlbumsResponse extends JFBaseResponse {
|
||||||
|
Items: JFAlbum[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JFSongsResponse extends JFBaseResponse {
|
||||||
|
Items: JFSong[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JFRequestParams = {
|
||||||
|
albumArtistIds?: string;
|
||||||
|
artistIds?: string;
|
||||||
|
enableImageTypes?: string;
|
||||||
|
enableTotalRecordCount?: boolean;
|
||||||
|
enableUserData?: boolean;
|
||||||
|
excludeItemTypes?: string;
|
||||||
|
fields?: string;
|
||||||
|
imageTypeLimit?: number;
|
||||||
|
includeItemTypes?: string;
|
||||||
|
isFavorite?: boolean;
|
||||||
|
limit?: number;
|
||||||
|
parentId?: string;
|
||||||
|
recursive?: boolean;
|
||||||
|
searchTerm?: string;
|
||||||
|
sortBy?: string;
|
||||||
|
sortOrder?: 'Ascending' | 'Descending';
|
||||||
|
startIndex?: number;
|
||||||
|
userId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JFMusicFolder = {
|
||||||
|
BackdropImageTags: string[];
|
||||||
|
ChannelId: null;
|
||||||
|
CollectionType: string;
|
||||||
|
Id: string;
|
||||||
|
ImageBlurHashes: ImageBlurHashes;
|
||||||
|
ImageTags: ImageTags;
|
||||||
|
IsFolder: boolean;
|
||||||
|
LocationType: string;
|
||||||
|
Name: string;
|
||||||
|
ServerId: string;
|
||||||
|
Type: string;
|
||||||
|
UserData: UserData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JFGenre = {
|
||||||
|
BackdropImageTags: any[];
|
||||||
|
ChannelId: null;
|
||||||
|
Id: string;
|
||||||
|
ImageBlurHashes: any;
|
||||||
|
ImageTags: ImageTags;
|
||||||
|
LocationType: string;
|
||||||
|
Name: string;
|
||||||
|
ServerId: string;
|
||||||
|
Type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JFAlbumArtist = {
|
||||||
|
BackdropImageTags: string[];
|
||||||
|
ChannelId: null;
|
||||||
|
DateCreated: string;
|
||||||
|
ExternalUrls: ExternalURL[];
|
||||||
|
GenreItems: GenreItem[];
|
||||||
|
Genres: string[];
|
||||||
|
Id: string;
|
||||||
|
ImageBlurHashes: any;
|
||||||
|
ImageTags: ImageTags;
|
||||||
|
LocationType: string;
|
||||||
|
Name: string;
|
||||||
|
Overview?: string;
|
||||||
|
RunTimeTicks: number;
|
||||||
|
ServerId: string;
|
||||||
|
Type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JFArtist = {
|
||||||
|
BackdropImageTags: string[];
|
||||||
|
ChannelId: null;
|
||||||
|
DateCreated: string;
|
||||||
|
ExternalUrls: ExternalURL[];
|
||||||
|
GenreItems: GenreItem[];
|
||||||
|
Genres: string[];
|
||||||
|
Id: string;
|
||||||
|
ImageBlurHashes: any;
|
||||||
|
ImageTags: string[];
|
||||||
|
LocationType: string;
|
||||||
|
Name: string;
|
||||||
|
Overview?: string;
|
||||||
|
RunTimeTicks: number;
|
||||||
|
ServerId: string;
|
||||||
|
Type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JFAlbum = {
|
||||||
|
AlbumArtist: string;
|
||||||
|
AlbumArtists: JFGenericItem[];
|
||||||
|
ArtistItems: JFGenericItem[];
|
||||||
|
Artists: string[];
|
||||||
|
ChannelId: null;
|
||||||
|
DateCreated: string;
|
||||||
|
ExternalUrls: ExternalURL[];
|
||||||
|
GenreItems: JFGenericItem[];
|
||||||
|
Genres: string[];
|
||||||
|
Id: string;
|
||||||
|
ImageBlurHashes: ImageBlurHashes;
|
||||||
|
ImageTags: ImageTags;
|
||||||
|
IsFolder: boolean;
|
||||||
|
LocationType: string;
|
||||||
|
Name: string;
|
||||||
|
ParentLogoImageTag: string;
|
||||||
|
ParentLogoItemId: string;
|
||||||
|
PremiereDate?: string;
|
||||||
|
ProductionYear: number;
|
||||||
|
RunTimeTicks: number;
|
||||||
|
ServerId: string;
|
||||||
|
Type: string;
|
||||||
|
} & {
|
||||||
|
songs?: JFSong[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JFSong = {
|
||||||
|
Album: string;
|
||||||
|
AlbumArtist: string;
|
||||||
|
AlbumArtists: JFGenericItem[];
|
||||||
|
AlbumId: string;
|
||||||
|
AlbumPrimaryImageTag: string;
|
||||||
|
ArtistItems: JFGenericItem[];
|
||||||
|
Artists: string[];
|
||||||
|
BackdropImageTags: string[];
|
||||||
|
ChannelId: null;
|
||||||
|
DateCreated: string;
|
||||||
|
ExternalUrls: ExternalURL[];
|
||||||
|
GenreItems: JFGenericItem[];
|
||||||
|
Genres: string[];
|
||||||
|
Id: string;
|
||||||
|
ImageBlurHashes: ImageBlurHashes;
|
||||||
|
ImageTags: ImageTags;
|
||||||
|
IndexNumber: number;
|
||||||
|
IsFolder: boolean;
|
||||||
|
LocationType: string;
|
||||||
|
MediaSources: MediaSources[];
|
||||||
|
MediaType: string;
|
||||||
|
Name: string;
|
||||||
|
ParentIndexNumber: number;
|
||||||
|
PremiereDate?: string;
|
||||||
|
ProductionYear: number;
|
||||||
|
RunTimeTicks: number;
|
||||||
|
ServerId: string;
|
||||||
|
SortName: string;
|
||||||
|
Type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ImageBlurHashes = {
|
||||||
|
Backdrop?: any;
|
||||||
|
Logo?: any;
|
||||||
|
Primary?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ImageTags = {
|
||||||
|
Logo?: string;
|
||||||
|
Primary?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UserData = {
|
||||||
|
IsFavorite: boolean;
|
||||||
|
Key: string;
|
||||||
|
PlayCount: number;
|
||||||
|
PlaybackPositionTicks: number;
|
||||||
|
Played: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ExternalURL = {
|
||||||
|
Name: string;
|
||||||
|
Url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GenreItem = {
|
||||||
|
Id: string;
|
||||||
|
Name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JFGenericItem = {
|
||||||
|
Id: string;
|
||||||
|
Name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MediaSources = {
|
||||||
|
Bitrate: number;
|
||||||
|
Container: string;
|
||||||
|
DefaultAudioStreamIndex: number;
|
||||||
|
ETag: string;
|
||||||
|
Formats: any[];
|
||||||
|
GenPtsInput: boolean;
|
||||||
|
Id: string;
|
||||||
|
IgnoreDts: boolean;
|
||||||
|
IgnoreIndex: boolean;
|
||||||
|
IsInfiniteStream: boolean;
|
||||||
|
IsRemote: boolean;
|
||||||
|
MediaAttachments: any[];
|
||||||
|
MediaStreams: MediaStream[];
|
||||||
|
Name: string;
|
||||||
|
Path: string;
|
||||||
|
Protocol: string;
|
||||||
|
ReadAtNativeFramerate: boolean;
|
||||||
|
RequiredHttpHeaders: any;
|
||||||
|
RequiresClosing: boolean;
|
||||||
|
RequiresLooping: boolean;
|
||||||
|
RequiresOpening: boolean;
|
||||||
|
RunTimeTicks: number;
|
||||||
|
Size: number;
|
||||||
|
SupportsDirectPlay: boolean;
|
||||||
|
SupportsDirectStream: boolean;
|
||||||
|
SupportsProbing: boolean;
|
||||||
|
SupportsTranscoding: boolean;
|
||||||
|
Type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MediaStream = {
|
||||||
|
AspectRatio?: string;
|
||||||
|
BitDepth?: number;
|
||||||
|
BitRate?: number;
|
||||||
|
ChannelLayout?: string;
|
||||||
|
Channels?: number;
|
||||||
|
Codec: string;
|
||||||
|
CodecTimeBase: string;
|
||||||
|
ColorSpace?: string;
|
||||||
|
Comment?: string;
|
||||||
|
DisplayTitle?: string;
|
||||||
|
Height?: number;
|
||||||
|
Index: number;
|
||||||
|
IsDefault: boolean;
|
||||||
|
IsExternal: boolean;
|
||||||
|
IsForced: boolean;
|
||||||
|
IsInterlaced: boolean;
|
||||||
|
IsTextSubtitleStream: boolean;
|
||||||
|
Level: number;
|
||||||
|
PixelFormat?: string;
|
||||||
|
Profile?: string;
|
||||||
|
RealFrameRate?: number;
|
||||||
|
RefFrames?: number;
|
||||||
|
SampleRate?: number;
|
||||||
|
SupportsExternalStream: boolean;
|
||||||
|
TimeBase: string;
|
||||||
|
Type: string;
|
||||||
|
Width?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum JFExternalType {
|
||||||
|
MUSICBRAINZ = 'MusicBrainz',
|
||||||
|
THEAUDIODB = 'TheAudioDb',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum JFImageType {
|
||||||
|
LOGO = 'Logo',
|
||||||
|
PRIMARY = 'Primary',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum JFItemType {
|
||||||
|
AUDIO = 'Audio',
|
||||||
|
MUSICALBUM = 'MusicAlbum',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum JFCollectionType {
|
||||||
|
MUSIC = 'music',
|
||||||
|
PLAYLISTS = 'playlists',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JFAuthenticate {
|
||||||
|
AccessToken: string;
|
||||||
|
ServerId: string;
|
||||||
|
SessionInfo: SessionInfo;
|
||||||
|
User: User;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionInfo = {
|
||||||
|
AdditionalUsers: any[];
|
||||||
|
ApplicationVersion: string;
|
||||||
|
Capabilities: Capabilities;
|
||||||
|
Client: string;
|
||||||
|
DeviceId: string;
|
||||||
|
DeviceName: string;
|
||||||
|
HasCustomDeviceName: boolean;
|
||||||
|
Id: string;
|
||||||
|
IsActive: boolean;
|
||||||
|
LastActivityDate: string;
|
||||||
|
LastPlaybackCheckIn: string;
|
||||||
|
NowPlayingQueue: any[];
|
||||||
|
NowPlayingQueueFullItems: any[];
|
||||||
|
PlayState: PlayState;
|
||||||
|
PlayableMediaTypes: any[];
|
||||||
|
RemoteEndPoint: string;
|
||||||
|
ServerId: string;
|
||||||
|
SupportedCommands: any[];
|
||||||
|
SupportsMediaControl: boolean;
|
||||||
|
SupportsRemoteControl: boolean;
|
||||||
|
UserId: string;
|
||||||
|
UserName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Capabilities = {
|
||||||
|
PlayableMediaTypes: any[];
|
||||||
|
SupportedCommands: any[];
|
||||||
|
SupportsContentUploading: boolean;
|
||||||
|
SupportsMediaControl: boolean;
|
||||||
|
SupportsPersistentIdentifier: boolean;
|
||||||
|
SupportsSync: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PlayState = {
|
||||||
|
CanSeek: boolean;
|
||||||
|
IsMuted: boolean;
|
||||||
|
IsPaused: boolean;
|
||||||
|
RepeatMode: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
Configuration: Configuration;
|
||||||
|
EnableAutoLogin: boolean;
|
||||||
|
HasConfiguredEasyPassword: boolean;
|
||||||
|
HasConfiguredPassword: boolean;
|
||||||
|
HasPassword: boolean;
|
||||||
|
Id: string;
|
||||||
|
LastActivityDate: string;
|
||||||
|
LastLoginDate: string;
|
||||||
|
Name: string;
|
||||||
|
Policy: Policy;
|
||||||
|
ServerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Configuration = {
|
||||||
|
DisplayCollectionsView: boolean;
|
||||||
|
DisplayMissingEpisodes: boolean;
|
||||||
|
EnableLocalPassword: boolean;
|
||||||
|
EnableNextEpisodeAutoPlay: boolean;
|
||||||
|
GroupedFolders: any[];
|
||||||
|
HidePlayedInLatest: boolean;
|
||||||
|
LatestItemsExcludes: any[];
|
||||||
|
MyMediaExcludes: any[];
|
||||||
|
OrderedViews: any[];
|
||||||
|
PlayDefaultAudioTrack: boolean;
|
||||||
|
RememberAudioSelections: boolean;
|
||||||
|
RememberSubtitleSelections: boolean;
|
||||||
|
SubtitleLanguagePreference: string;
|
||||||
|
SubtitleMode: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Policy = {
|
||||||
|
AccessSchedules: any[];
|
||||||
|
AuthenticationProviderId: string;
|
||||||
|
BlockUnratedItems: any[];
|
||||||
|
BlockedChannels: any[];
|
||||||
|
BlockedMediaFolders: any[];
|
||||||
|
BlockedTags: any[];
|
||||||
|
EnableAllChannels: boolean;
|
||||||
|
EnableAllDevices: boolean;
|
||||||
|
EnableAllFolders: boolean;
|
||||||
|
EnableAudioPlaybackTranscoding: boolean;
|
||||||
|
EnableContentDeletion: boolean;
|
||||||
|
EnableContentDeletionFromFolders: any[];
|
||||||
|
EnableContentDownloading: boolean;
|
||||||
|
EnableLiveTvAccess: boolean;
|
||||||
|
EnableLiveTvManagement: boolean;
|
||||||
|
EnableMediaConversion: boolean;
|
||||||
|
EnableMediaPlayback: boolean;
|
||||||
|
EnablePlaybackRemuxing: boolean;
|
||||||
|
EnablePublicSharing: boolean;
|
||||||
|
EnableRemoteAccess: boolean;
|
||||||
|
EnableRemoteControlOfOtherUsers: boolean;
|
||||||
|
EnableSharedDeviceControl: boolean;
|
||||||
|
EnableSyncTranscoding: boolean;
|
||||||
|
EnableUserPreferenceAccess: boolean;
|
||||||
|
EnableVideoPlaybackTranscoding: boolean;
|
||||||
|
EnabledChannels: any[];
|
||||||
|
EnabledDevices: any[];
|
||||||
|
EnabledFolders: any[];
|
||||||
|
ForceRemoteSourceTranscoding: boolean;
|
||||||
|
InvalidLoginAttemptCount: number;
|
||||||
|
IsAdministrator: boolean;
|
||||||
|
IsDisabled: boolean;
|
||||||
|
IsHidden: boolean;
|
||||||
|
LoginAttemptsBeforeLockout: number;
|
||||||
|
MaxActiveSessions: number;
|
||||||
|
PasswordResetProviderId: string;
|
||||||
|
RemoteClientBitrateLimit: number;
|
||||||
|
SyncPlayAccess: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum JFSortOrder {
|
||||||
|
ASC = 'Ascending',
|
||||||
|
DESC = 'Descending',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum JFAlbumListSort {
|
||||||
|
ALBUM_ARTIST = 'AlbumArtist,SortName',
|
||||||
|
CRITIC_RATING = 'CriticRating,SortName',
|
||||||
|
NAME = 'SortName',
|
||||||
|
RANDOM = 'Random,SortName',
|
||||||
|
RATING = 'CommunityRating,SortName',
|
||||||
|
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||||
|
RELEASE_DATE = 'ProductionYear,PremiereDate,SortName',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JFAlbumListParams = {
|
||||||
|
enableImageTypes: JFImageType[];
|
||||||
|
imageTypeLimit: number;
|
||||||
|
includeItemTypes: 'MusicAlbum';
|
||||||
|
limit?: number;
|
||||||
|
parentId: string;
|
||||||
|
recursive: boolean;
|
||||||
|
sortBy: JFAlbumListSort;
|
||||||
|
sortOrder: JFSortOrder;
|
||||||
|
startIndex: number;
|
||||||
|
};
|
||||||
@@ -1,111 +1,253 @@
|
|||||||
import ky from 'ky';
|
|
||||||
import { nanoid } from 'nanoid/non-secure';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import type { ServerListItem } from '../store';
|
import ky from 'ky';
|
||||||
import { useAuthStore } from '../store';
|
|
||||||
import { ServerType } from '../types';
|
|
||||||
import type {
|
import type {
|
||||||
NDAlbumListResponse,
|
|
||||||
NDGenreListResponse,
|
NDGenreListResponse,
|
||||||
NDAlbumListParams,
|
|
||||||
NDGenreListParams,
|
|
||||||
NDSongListParams,
|
|
||||||
NDArtistListResponse,
|
NDArtistListResponse,
|
||||||
NDAuthenticate,
|
NDAlbumDetail,
|
||||||
NDAlbum,
|
|
||||||
NDAlbumListSort,
|
|
||||||
NDAlbumDetailResponse,
|
|
||||||
NDSong,
|
|
||||||
NDSongListResponse,
|
NDSongListResponse,
|
||||||
} from './navidrome.types';
|
NDAlbumListParams,
|
||||||
import { NDSortOrder } from './navidrome.types';
|
NDAlbumList,
|
||||||
|
NDSongDetailResponse,
|
||||||
|
NDAlbum,
|
||||||
|
NDSong,
|
||||||
|
NDAuthenticationResponse,
|
||||||
|
NDAlbumDetailResponse,
|
||||||
|
NDSongList,
|
||||||
|
NDSongDetail,
|
||||||
|
NDGenreList,
|
||||||
|
NDAlbumArtistListParams,
|
||||||
|
NDAlbumArtistListSort,
|
||||||
|
NDAlbumArtistDetail,
|
||||||
|
NDAlbumListResponse,
|
||||||
|
NDAlbumArtistDetailResponse,
|
||||||
|
NDAlbumArtistList,
|
||||||
|
NDSongListParams,
|
||||||
|
NDSongListSort,
|
||||||
|
NDCreatePlaylist,
|
||||||
|
NDCreatePlaylistParams,
|
||||||
|
NDCreatePlaylistResponse,
|
||||||
|
NDDeletePlaylist,
|
||||||
|
NDDeletePlaylistResponse,
|
||||||
|
NDPlaylistListParams,
|
||||||
|
NDPlaylistDetail,
|
||||||
|
NDPlaylistList,
|
||||||
|
NDPlaylistListSort,
|
||||||
|
NDPlaylistListResponse,
|
||||||
|
NDPlaylistDetailResponse,
|
||||||
|
} from '/@/api/navidrome.types';
|
||||||
|
import { NDAlbumListSort } from '/@/api/navidrome.types';
|
||||||
|
import { NDSortOrder } from '/@/api/navidrome.types';
|
||||||
import type {
|
import type {
|
||||||
Album,
|
Album,
|
||||||
AlbumDetailQuery,
|
|
||||||
AlbumDetailResponse,
|
|
||||||
AlbumListParams,
|
|
||||||
AlbumListResponse,
|
|
||||||
Song,
|
Song,
|
||||||
} from './types';
|
AuthenticationResponse,
|
||||||
import { SortOrder } from './types';
|
AlbumDetailArgs,
|
||||||
|
GenreListArgs,
|
||||||
|
AlbumListArgs,
|
||||||
|
AlbumArtistListArgs,
|
||||||
|
AlbumArtistDetailArgs,
|
||||||
|
SongListArgs,
|
||||||
|
SongDetailArgs,
|
||||||
|
CreatePlaylistArgs,
|
||||||
|
DeletePlaylistArgs,
|
||||||
|
PlaylistListArgs,
|
||||||
|
PlaylistDetailArgs,
|
||||||
|
} from '/@/api/types';
|
||||||
|
import { SortOrder } from '/@/api/types';
|
||||||
|
import { toast } from '/@/components';
|
||||||
|
import type { ServerListItem } from '/@/store';
|
||||||
|
import { useAuthStore } from '/@/store';
|
||||||
|
import { ServerType } from '/@/types';
|
||||||
|
|
||||||
const api = ky.create({
|
const api = ky.create({
|
||||||
hooks: {
|
hooks: {
|
||||||
afterResponse: [
|
afterResponse: [
|
||||||
(request, _options, response) => {
|
async (_request, _options, response) => {
|
||||||
// const serverId = request.headers.get('--local-id');
|
const serverId = useAuthStore.getState().currentServer?.id;
|
||||||
|
|
||||||
|
if (serverId) {
|
||||||
|
useAuthStore.getState().actions.updateServer(serverId, {
|
||||||
|
ndCredential: response.headers.get('x-nd-authorization') as string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
beforeRequest: [
|
beforeError: [
|
||||||
(request, options) => {
|
(error) => {
|
||||||
const { headers } = options;
|
if (error.response && error.response.status === 401) {
|
||||||
|
toast.error({
|
||||||
|
message: 'Your session has expired.',
|
||||||
|
});
|
||||||
|
|
||||||
console.log('headers', headers);
|
const serverId = useAuthStore.getState().currentServer?.id;
|
||||||
const { currentServer } = useAuthStore.getState();
|
|
||||||
const { ndCredential } = currentServer || {};
|
|
||||||
|
|
||||||
if (ndCredential) {
|
if (serverId) {
|
||||||
request.headers.set('x-nd-authorization', `Bearer ${ndCredential}`);
|
useAuthStore.getState().actions.setCurrentServer(null);
|
||||||
request.headers.set('--local-id', currentServer?.id || '');
|
useAuthStore.getState().actions.updateServer(serverId, { ndCredential: undefined });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const authenticate = async (options: { password: string; url: string; username: string }) => {
|
// api.interceptors.request.use(
|
||||||
const { password, url, username } = options;
|
// (config) => {
|
||||||
|
// const server = useAuthStore.getState().currentServer;
|
||||||
|
|
||||||
|
// config.baseURL = server?.url;
|
||||||
|
// config.headers = {
|
||||||
|
// 'Content-Type': 'application/json',
|
||||||
|
// 'x-nd-authorization': `Bearer ${server?.ndCredential}`,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return config;
|
||||||
|
// },
|
||||||
|
// (err) => {
|
||||||
|
// return Promise.reject(err);
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
|
||||||
|
// api.interceptors.response.use(
|
||||||
|
// (res) => {
|
||||||
|
// const serverId = useAuthStore.getState().currentServer?.id;
|
||||||
|
|
||||||
|
// if (serverId) {
|
||||||
|
// useAuthStore.getState().actions.updateServer(serverId, {
|
||||||
|
// ndCredential: res.headers['x-nd-authorization'] as string,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return res;
|
||||||
|
// },
|
||||||
|
// async (err) => {
|
||||||
|
// if (!err.response) {
|
||||||
|
// return Promise.reject(err);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (err.response && err.response.status === 401) {
|
||||||
|
// toast.error({
|
||||||
|
// message: 'Your session has expired.',
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const serverId = useAuthStore.getState().currentServer?.id;
|
||||||
|
|
||||||
|
// if (serverId) {
|
||||||
|
// useAuthStore.getState().actions.setCurrentServer(null);
|
||||||
|
// useAuthStore.getState().actions.updateServer(serverId, { ndCredential: undefined });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return Promise.reject(err);
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
|
||||||
|
const authenticate = async (
|
||||||
|
url: string,
|
||||||
|
body: { password: string; username: string },
|
||||||
|
): Promise<AuthenticationResponse> => {
|
||||||
const cleanServerUrl = url.replace(/\/$/, '');
|
const cleanServerUrl = url.replace(/\/$/, '');
|
||||||
|
|
||||||
const data = await ky
|
const data = await ky
|
||||||
.post(`${cleanServerUrl}/auth/login`, {
|
.post(`${cleanServerUrl}/auth/login`, {
|
||||||
json: {
|
json: {
|
||||||
password,
|
password: body.password,
|
||||||
username,
|
username: body.username,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.json<NDAuthenticate>();
|
.json<NDAuthenticationResponse>();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
credential: `u=${options.username}&s=${data.subsonicSalt}&t=${data.subsonicToken}`,
|
credential: `u=${body.username}&s=${data.subsonicSalt}&t=${data.subsonicToken}`,
|
||||||
ndCredential: data.token,
|
ndCredential: data.token,
|
||||||
|
userId: data.id,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getGenreList = async (params?: NDGenreListParams) => {
|
const getGenreList = async (args: GenreListArgs): Promise<NDGenreList> => {
|
||||||
|
const { server, signal } = args;
|
||||||
|
|
||||||
const data = await api
|
const data = await api
|
||||||
.get('api/genre', {
|
.get('api/genre', {
|
||||||
prefixUrl: useAuthStore.getState().currentServer?.url,
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
searchParams: params,
|
prefixUrl: server?.url,
|
||||||
|
signal,
|
||||||
})
|
})
|
||||||
.json<NDGenreListResponse>();
|
.json<NDGenreListResponse>();
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getArtistList = async (params?: NDGenreListParams) => {
|
const getAlbumArtistDetail = async (args: AlbumArtistDetailArgs): Promise<NDAlbumArtistDetail> => {
|
||||||
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
|
const data = await api
|
||||||
|
.get(`api/artist/${query.id}`, {
|
||||||
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
prefixUrl: server?.url,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<NDAlbumArtistDetailResponse>();
|
||||||
|
|
||||||
|
const albumsData = await api
|
||||||
|
.get('api/album', {
|
||||||
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
prefixUrl: server?.url,
|
||||||
|
searchParams: {
|
||||||
|
_end: 0,
|
||||||
|
_order: NDSortOrder.ASC,
|
||||||
|
_sort: NDAlbumListSort.YEAR,
|
||||||
|
_start: 0,
|
||||||
|
artist_id: query.id,
|
||||||
|
} as NDAlbumListParams,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<NDAlbumListResponse>();
|
||||||
|
|
||||||
|
return { ...data, albums: albumsData };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<NDAlbumArtistList> => {
|
||||||
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
|
const searchParams: NDAlbumArtistListParams = {
|
||||||
|
_end: query.startIndex + (query.limit || 0),
|
||||||
|
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
||||||
|
_sort: query.sortBy as NDAlbumArtistListSort,
|
||||||
|
_start: query.startIndex,
|
||||||
|
...query.ndParams,
|
||||||
|
};
|
||||||
|
|
||||||
const data = await api
|
const data = await api
|
||||||
.get('api/artist', {
|
.get('api/artist', {
|
||||||
prefixUrl: useAuthStore.getState().currentServer?.url,
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
searchParams: params,
|
searchParams,
|
||||||
|
signal,
|
||||||
})
|
})
|
||||||
.json<NDArtistListResponse>();
|
.json<NDArtistListResponse>();
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumDetail = async (query: AlbumDetailQuery, signal?: AbortSignal) => {
|
const getAlbumDetail = async (args: AlbumDetailArgs): Promise<NDAlbumDetail> => {
|
||||||
const albumDetail = await api
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
|
const data = await api
|
||||||
.get(`api/album/${query.id}`, {
|
.get(`api/album/${query.id}`, {
|
||||||
prefixUrl: useAuthStore.getState().currentServer?.url,
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
prefixUrl: server?.url,
|
||||||
signal,
|
signal,
|
||||||
})
|
})
|
||||||
.json<NDAlbumDetailResponse>();
|
.json<NDAlbumDetailResponse>();
|
||||||
|
|
||||||
const albumSongs = await api
|
const songsData = await api
|
||||||
.get('api/song/', {
|
.get('api/song', {
|
||||||
prefixUrl: useAuthStore.getState().currentServer?.url,
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
prefixUrl: server?.url,
|
||||||
searchParams: {
|
searchParams: {
|
||||||
_end: 0,
|
_end: 0,
|
||||||
_order: NDSortOrder.ASC,
|
_order: NDSortOrder.ASC,
|
||||||
@@ -113,57 +255,153 @@ const getAlbumDetail = async (query: AlbumDetailQuery, signal?: AbortSignal) =>
|
|||||||
_start: 0,
|
_start: 0,
|
||||||
album_id: query.id,
|
album_id: query.id,
|
||||||
},
|
},
|
||||||
|
signal,
|
||||||
})
|
})
|
||||||
.json<NDSongListResponse>();
|
.json<NDSongListResponse>();
|
||||||
|
|
||||||
return { ...albumDetail, songs: albumSongs } as AlbumDetailResponse;
|
return { ...data, songs: songsData };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumList = async (params: AlbumListParams, signal?: AbortSignal) => {
|
const getAlbumList = async (args: AlbumListArgs): Promise<NDAlbumList> => {
|
||||||
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
const searchParams: NDAlbumListParams = {
|
const searchParams: NDAlbumListParams = {
|
||||||
_end: params._skip + (params._take || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
_order: params.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
||||||
_sort: params.sortBy as NDAlbumListSort,
|
_sort: query.sortBy as NDAlbumListSort,
|
||||||
_start: params._skip,
|
_start: query.startIndex,
|
||||||
...params.nd,
|
...query.ndParams,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await api.get('api/album', {
|
const res = await api.get('api/album', {
|
||||||
prefixUrl: useAuthStore.getState().currentServer?.url,
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
prefixUrl: server?.url,
|
||||||
searchParams,
|
searchParams,
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
const itemCount = res.headers.get('X-Total-Count');
|
|
||||||
const data = await res.json<NDAlbumListResponse>();
|
const data = await res.json<NDAlbumListResponse>();
|
||||||
|
const itemCount = res.headers.get('x-total-count');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: data,
|
items: data,
|
||||||
pagination: {
|
startIndex: query?.startIndex || 0,
|
||||||
startIndex: params?._skip || 0,
|
totalRecordCount: Number(itemCount),
|
||||||
totalEntries: Number(itemCount),
|
};
|
||||||
},
|
|
||||||
} as AlbumListResponse;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSongList = async (params?: NDSongListParams) => {
|
const getSongList = async (args: SongListArgs): Promise<NDSongList> => {
|
||||||
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
|
const searchParams: NDSongListParams = {
|
||||||
|
_end: query.startIndex + (query.limit || 0),
|
||||||
|
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
||||||
|
_sort: query.sortBy as NDSongListSort,
|
||||||
|
_start: query.startIndex,
|
||||||
|
...query.ndParams,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await api.get('api/song', {
|
||||||
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
prefixUrl: server?.url,
|
||||||
|
searchParams,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json<NDSongListResponse>();
|
||||||
|
const itemCount = res.headers.get('x-total-count');
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: data,
|
||||||
|
startIndex: query?.startIndex || 0,
|
||||||
|
totalRecordCount: Number(itemCount),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSongDetail = async (args: SongDetailArgs): Promise<NDSongDetail> => {
|
||||||
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
const data = await api
|
const data = await api
|
||||||
.get('api/song', {
|
.get(`api/song/${query.id}`, {
|
||||||
prefixUrl: useAuthStore.getState().currentServer?.url,
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
searchParams: params,
|
signal,
|
||||||
})
|
})
|
||||||
.json<NDSongListResponse>();
|
.json<NDSongDetailResponse>();
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const navidromeApi = {
|
const createPlaylist = async (args: CreatePlaylistArgs): Promise<NDCreatePlaylist> => {
|
||||||
authenticate,
|
const { query, server, signal } = args;
|
||||||
getAlbumDetail,
|
|
||||||
getAlbumList,
|
const json: NDCreatePlaylistParams = {
|
||||||
getArtistList,
|
comment: query.comment,
|
||||||
getGenreList,
|
name: query.name,
|
||||||
getSongList,
|
public: query.public || false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await api
|
||||||
|
.post('api/playlist', {
|
||||||
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
json,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<NDCreatePlaylistResponse>();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletePlaylist = async (args: DeletePlaylistArgs): Promise<NDDeletePlaylist> => {
|
||||||
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
|
const data = await api
|
||||||
|
.delete(`api/playlist/${query.id}`, {
|
||||||
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<NDDeletePlaylistResponse>();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPlaylistList = async (args: PlaylistListArgs): Promise<NDPlaylistList> => {
|
||||||
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
|
const searchParams: NDPlaylistListParams = {
|
||||||
|
_end: query.startIndex + (query.limit || 0),
|
||||||
|
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
||||||
|
_sort: query.sortBy as NDPlaylistListSort,
|
||||||
|
_start: query.startIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await api.get('api/song', {
|
||||||
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
prefixUrl: server?.url,
|
||||||
|
searchParams,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json<NDPlaylistListResponse>();
|
||||||
|
const itemCount = res.headers.get('x-total-count');
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: data,
|
||||||
|
startIndex: query?.startIndex || 0,
|
||||||
|
totalRecordCount: Number(itemCount),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPlaylistDetail = async (args: PlaylistDetailArgs): Promise<NDPlaylistDetail> => {
|
||||||
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
|
const data = await api
|
||||||
|
.get(`api/song/${query.id}`, {
|
||||||
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<NDPlaylistDetailResponse>();
|
||||||
|
|
||||||
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCoverArtUrl = (args: {
|
const getCoverArtUrl = (args: {
|
||||||
@@ -264,6 +502,21 @@ const normalizeSong = (
|
|||||||
} as Song;
|
} as Song;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const navidromeApi = {
|
||||||
|
authenticate,
|
||||||
|
createPlaylist,
|
||||||
|
deletePlaylist,
|
||||||
|
getAlbumArtistDetail,
|
||||||
|
getAlbumArtistList,
|
||||||
|
getAlbumDetail,
|
||||||
|
getAlbumList,
|
||||||
|
getGenreList,
|
||||||
|
getPlaylistDetail,
|
||||||
|
getPlaylistList,
|
||||||
|
getSongDetail,
|
||||||
|
getSongList,
|
||||||
|
};
|
||||||
|
|
||||||
export const ndNormalize = {
|
export const ndNormalize = {
|
||||||
album: normalizeAlbum,
|
album: normalizeAlbum,
|
||||||
song: normalizeSong,
|
song: normalizeSong,
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export type NDSong = {
|
|||||||
year: number;
|
year: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NDArtist = {
|
export type NDAlbumArtist = {
|
||||||
albumCount: number;
|
albumCount: number;
|
||||||
biography: string;
|
biography: string;
|
||||||
externalInfoUpdatedAt: string;
|
externalInfoUpdatedAt: string;
|
||||||
@@ -115,15 +115,43 @@ export type NDArtist = {
|
|||||||
starredAt: string;
|
starredAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NDAuthenticationResponse = NDAuthenticate;
|
||||||
|
|
||||||
|
export type NDAlbumArtistList = NDAlbumArtist[];
|
||||||
|
|
||||||
|
export type NDAlbumArtistDetail = NDAlbumArtist & { albums: NDAlbumListResponse };
|
||||||
|
|
||||||
|
export type NDAlbumArtistDetailResponse = NDAlbumArtist;
|
||||||
|
|
||||||
|
export type NDGenreList = NDGenre[];
|
||||||
|
|
||||||
export type NDGenreListResponse = NDGenre[];
|
export type NDGenreListResponse = NDGenre[];
|
||||||
|
|
||||||
export type NDAlbumDetailResponse = NDAlbum;
|
export type NDAlbumDetailResponse = NDAlbum;
|
||||||
|
|
||||||
|
export type NDAlbumDetail = NDAlbum & { songs: NDSongListResponse };
|
||||||
|
|
||||||
export type NDAlbumListResponse = NDAlbum[];
|
export type NDAlbumListResponse = NDAlbum[];
|
||||||
|
|
||||||
|
export type NDAlbumList = {
|
||||||
|
items: NDAlbum[];
|
||||||
|
startIndex: number;
|
||||||
|
totalRecordCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NDSongDetail = NDSong;
|
||||||
|
|
||||||
|
export type NDSongDetailResponse = NDSong;
|
||||||
|
|
||||||
export type NDSongListResponse = NDSong[];
|
export type NDSongListResponse = NDSong[];
|
||||||
|
|
||||||
export type NDArtistListResponse = NDArtist[];
|
export type NDSongList = {
|
||||||
|
items: NDSong[];
|
||||||
|
startIndex: number;
|
||||||
|
totalRecordCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NDArtistListResponse = NDAlbumArtist[];
|
||||||
|
|
||||||
export type NDPagination = {
|
export type NDPagination = {
|
||||||
_end?: number;
|
_end?: number;
|
||||||
@@ -177,8 +205,107 @@ export type NDAlbumListParams = {
|
|||||||
} & NDPagination &
|
} & NDPagination &
|
||||||
NDOrder;
|
NDOrder;
|
||||||
|
|
||||||
|
export enum NDSongListSort {
|
||||||
|
ALBUM = 'album',
|
||||||
|
ALBUM_ARTIST = 'albumArtist',
|
||||||
|
ARTIST = 'artist',
|
||||||
|
BPM = 'bpm',
|
||||||
|
CHANNELS = 'channels',
|
||||||
|
COMMENT = 'comment',
|
||||||
|
DURATION = 'duration',
|
||||||
|
GENRE = 'genre',
|
||||||
|
NAME = 'name',
|
||||||
|
PLAY_COUNT = 'playCount',
|
||||||
|
PLAY_DATE = 'playDate',
|
||||||
|
RATING = 'rating',
|
||||||
|
TRACK = 'track',
|
||||||
|
YEAR = 'year',
|
||||||
|
}
|
||||||
|
|
||||||
export type NDSongListParams = {
|
export type NDSongListParams = {
|
||||||
|
_sort?: NDSongListSort;
|
||||||
genre_id?: string;
|
genre_id?: string;
|
||||||
starred?: boolean;
|
starred?: boolean;
|
||||||
} & NDPagination &
|
} & NDPagination &
|
||||||
NDOrder;
|
NDOrder;
|
||||||
|
|
||||||
|
export enum NDAlbumArtistListSort {
|
||||||
|
ALBUM_COUNT = 'albumCount',
|
||||||
|
NAME = 'name',
|
||||||
|
PLAY_COUNT = 'playCount',
|
||||||
|
RATING = 'rating',
|
||||||
|
SONG_COUNT = 'songCount',
|
||||||
|
STARRED = 'starred ASC, starredAt ASC',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NDAlbumArtistListParams = {
|
||||||
|
_sort?: NDAlbumArtistListSort;
|
||||||
|
genre_id?: string;
|
||||||
|
starred?: boolean;
|
||||||
|
} & NDPagination &
|
||||||
|
NDOrder;
|
||||||
|
|
||||||
|
export type NDCreatePlaylistParams = {
|
||||||
|
comment?: string;
|
||||||
|
name: string;
|
||||||
|
public: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NDCreatePlaylistResponse = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NDCreatePlaylist = NDCreatePlaylistResponse;
|
||||||
|
|
||||||
|
export type NDDeletePlaylistParams = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NDDeletePlaylistResponse = null;
|
||||||
|
|
||||||
|
export type NDDeletePlaylist = NDDeletePlaylistResponse;
|
||||||
|
|
||||||
|
export type NDPlaylist = {
|
||||||
|
comment: string;
|
||||||
|
createdAt: string;
|
||||||
|
duration: number;
|
||||||
|
evaluatedAt: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
ownerId: string;
|
||||||
|
ownerName: string;
|
||||||
|
path: string;
|
||||||
|
public: boolean;
|
||||||
|
rules: null;
|
||||||
|
size: number;
|
||||||
|
songCount: number;
|
||||||
|
sync: boolean;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NDPlaylistDetail = NDPlaylist;
|
||||||
|
|
||||||
|
export type NDPlaylistDetailResponse = NDPlaylist;
|
||||||
|
|
||||||
|
export type NDPlaylistList = {
|
||||||
|
items: NDPlaylist[];
|
||||||
|
startIndex: number;
|
||||||
|
totalRecordCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NDPlaylistListResponse = NDPlaylist[];
|
||||||
|
|
||||||
|
export enum NDPlaylistListSort {
|
||||||
|
DURATION = 'duration',
|
||||||
|
NAME = 'name',
|
||||||
|
OWNER = 'owner',
|
||||||
|
PUBLIC = 'public',
|
||||||
|
SONG_COUNT = 'songCount',
|
||||||
|
UPDATED_AT = 'updatedAt',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NDPlaylistListParams = {
|
||||||
|
_sort?: NDPlaylistListSort;
|
||||||
|
owner_id?: string;
|
||||||
|
} & NDPagination &
|
||||||
|
NDOrder;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { AlbumListParams } from './types';
|
import type { AlbumListQuery } from './types';
|
||||||
import type { AlbumDetailQuery } from './types';
|
import type { AlbumDetailQuery } from './types';
|
||||||
|
|
||||||
export const queryKeys = {
|
export const queryKeys = {
|
||||||
albums: {
|
albums: {
|
||||||
detail: (serverId: string, query: AlbumDetailQuery) => ['albums', serverId, query] as const,
|
detail: (serverId: string, query: AlbumDetailQuery) => ['albums', serverId, query] as const,
|
||||||
list: (serverId: string, params: AlbumListParams) =>
|
list: (serverId: string, params: AlbumListQuery) =>
|
||||||
[serverId, 'albums', 'list', serverId, params] as const,
|
[serverId, 'albums', 'list', serverId, params] as const,
|
||||||
root: ['albums'],
|
root: ['albums'],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,17 +1,40 @@
|
|||||||
import axios from 'axios';
|
import ky from 'ky';
|
||||||
import md5 from 'md5';
|
import md5 from 'md5';
|
||||||
import { randomString } from '../utils/random-string';
|
import { randomString } from '/@/utils';
|
||||||
import type {
|
import type {
|
||||||
SSAlbumListEntry,
|
|
||||||
SSAlbumListResponse,
|
SSAlbumListResponse,
|
||||||
SSAlbumResponse,
|
SSAlbumDetailResponse,
|
||||||
SSAlbumsParams,
|
|
||||||
SSArtistIndex,
|
SSArtistIndex,
|
||||||
SSArtistInfoResponse,
|
SSAlbumArtistList,
|
||||||
SSArtistsResponse,
|
SSAlbumArtistListResponse,
|
||||||
SSGenresResponse,
|
SSGenreListResponse,
|
||||||
SSMusicFoldersResponse,
|
SSMusicFolderList,
|
||||||
} from './subsonic.types';
|
SSMusicFolderListResponse,
|
||||||
|
SSGenreList,
|
||||||
|
SSAlbumDetail,
|
||||||
|
SSAlbumList,
|
||||||
|
SSAlbumArtistDetail,
|
||||||
|
SSAlbumArtistDetailResponse,
|
||||||
|
SSFavoriteParams,
|
||||||
|
SSFavorite,
|
||||||
|
SSFavoriteResponse,
|
||||||
|
SSRatingParams,
|
||||||
|
SSRatingResponse,
|
||||||
|
SSAlbumArtistDetailParams,
|
||||||
|
SSAlbumArtistListParams,
|
||||||
|
} from '/@/api/subsonic.types';
|
||||||
|
import type {
|
||||||
|
AlbumArtistDetailArgs,
|
||||||
|
AlbumArtistListArgs,
|
||||||
|
AlbumDetailArgs,
|
||||||
|
AlbumListArgs,
|
||||||
|
AuthenticationResponse,
|
||||||
|
FavoriteArgs,
|
||||||
|
GenreListArgs,
|
||||||
|
RatingArgs,
|
||||||
|
} from '/@/api/types';
|
||||||
|
import { useAuthStore } from '/@/store';
|
||||||
|
import { toast } from '/@/components';
|
||||||
|
|
||||||
const getCoverArtUrl = (args: {
|
const getCoverArtUrl = (args: {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@@ -35,136 +58,235 @@ const getCoverArtUrl = (args: {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const api = axios.create({
|
const api = ky.create({
|
||||||
validateStatus: (status) => status >= 200,
|
hooks: {
|
||||||
|
afterResponse: [
|
||||||
|
async (_request, _options, response) => {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data['subsonic-response'].status !== 'ok') {
|
||||||
|
toast.warn({ message: 'Issue from Subsonic API' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(data['subsonic-response']), { status: 200 });
|
||||||
|
},
|
||||||
|
],
|
||||||
|
beforeRequest: [
|
||||||
|
(request) => {
|
||||||
|
const server = useAuthStore.getState().currentServer;
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
|
||||||
|
if (server) {
|
||||||
|
const authParams = server.credential.split(/&?\w=/gm);
|
||||||
|
|
||||||
|
searchParams.set('u', server.username);
|
||||||
|
searchParams.set('v', '1.13.0');
|
||||||
|
searchParams.set('c', 'Feishin');
|
||||||
|
searchParams.set('f', 'json');
|
||||||
|
|
||||||
|
if (authParams?.length === 4) {
|
||||||
|
searchParams.set('s', authParams[2]);
|
||||||
|
searchParams.set('t', authParams[3]);
|
||||||
|
} else if (authParams?.length === 3) {
|
||||||
|
searchParams.set('p', authParams[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ky(request, { searchParams });
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
api.interceptors.response.use(
|
const authenticate = async (
|
||||||
(res: any) => {
|
url: string,
|
||||||
res.data = res.data['subsonic-response'];
|
body: {
|
||||||
return res;
|
legacy?: boolean;
|
||||||
|
password: string;
|
||||||
|
username: string;
|
||||||
},
|
},
|
||||||
(err: any) => {
|
): Promise<AuthenticationResponse> => {
|
||||||
return Promise.reject(err);
|
let credential;
|
||||||
},
|
const cleanServerUrl = url.replace(/\/$/, '');
|
||||||
);
|
|
||||||
|
|
||||||
const authenticate = async (options: {
|
if (body.legacy) {
|
||||||
legacy?: boolean;
|
credential = `u=${body.username}&p=${body.password}`;
|
||||||
password: string;
|
|
||||||
url: string;
|
|
||||||
username: string;
|
|
||||||
}) => {
|
|
||||||
let token;
|
|
||||||
|
|
||||||
const cleanServerUrl = options.url.replace(/\/$/, '');
|
|
||||||
|
|
||||||
if (options.legacy) {
|
|
||||||
token = `u=${options.username}&p=${options.password}`;
|
|
||||||
} else {
|
} else {
|
||||||
const salt = randomString(12);
|
const salt = randomString(12);
|
||||||
const hash = md5(options.password + salt);
|
const hash = md5(body.password + salt);
|
||||||
token = `u=${options.username}&s=${salt}&t=${hash}`;
|
credential = `u=${body.username}&s=${salt}&t=${hash}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await api.get(
|
await ky.get(`${cleanServerUrl}/rest/ping.view?v=1.13.0&c=Feishin&f=json&${credential}`);
|
||||||
`${cleanServerUrl}/rest/ping.view?v=1.13.0&c=Feishin&f=json&${token}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return { token, ...data };
|
return {
|
||||||
|
credential,
|
||||||
|
userId: null,
|
||||||
|
username: body.username,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMusicFolders = async (server: Partial<Server>) => {
|
const getMusicFolderList = async (
|
||||||
const { data } = await api.get<SSMusicFoldersResponse>(
|
server: any,
|
||||||
`${server.url}/rest/getMusicFolders.view?v=1.13.0&c=Feishin&f=json&${server.token}`,
|
signal?: AbortSignal,
|
||||||
);
|
): Promise<SSMusicFolderList> => {
|
||||||
|
const data = await api
|
||||||
|
.get('rest/getMusicFolders.view', {
|
||||||
|
prefixUrl: server.url,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<SSMusicFolderListResponse>();
|
||||||
|
|
||||||
return data.musicFolders.musicFolder;
|
return data.musicFolders.musicFolder;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getArtists = async (server: Server, musicFolderId: string) => {
|
export const getAlbumArtistDetail = async (
|
||||||
const { data } = await api.get<SSArtistsResponse>(
|
args: AlbumArtistDetailArgs,
|
||||||
`${server.url}/rest/getArtists.view?v=1.13.0&c=Feishin&f=json&${server.token}`,
|
): Promise<SSAlbumArtistDetail> => {
|
||||||
{ params: { musicFolderId } },
|
const { signal, query } = args;
|
||||||
);
|
|
||||||
|
const searchParams: SSAlbumArtistDetailParams = {
|
||||||
|
id: query.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await api
|
||||||
|
.get('/getArtist.view', {
|
||||||
|
searchParams,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<SSAlbumArtistDetailResponse>();
|
||||||
|
|
||||||
|
return data.artist;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<SSAlbumArtistList> => {
|
||||||
|
const { signal, query } = args;
|
||||||
|
|
||||||
|
const searchParams: SSAlbumArtistListParams = {
|
||||||
|
musicFolderId: query.musicFolderId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await api
|
||||||
|
.get('/rest/getArtists.view', {
|
||||||
|
searchParams,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<SSAlbumArtistListResponse>();
|
||||||
|
|
||||||
const artists = (data.artists?.index || []).flatMap((index: SSArtistIndex) => index.artist);
|
const artists = (data.artists?.index || []).flatMap((index: SSArtistIndex) => index.artist);
|
||||||
|
|
||||||
return artists;
|
return artists;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getGenres = async (server: Server) => {
|
const getGenreList = async (args: GenreListArgs): Promise<SSGenreList> => {
|
||||||
const { data: genres } = await api.get<SSGenresResponse>(
|
const { signal } = args;
|
||||||
`${server.url}/rest/getGenres.view?v=1.13.0&c=Feishin&f=json&${server.token}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return genres;
|
const data = await api
|
||||||
};
|
.get('/rest/getGenres.view', {
|
||||||
|
signal,
|
||||||
const getAlbum = async (server: Server, id: string) => {
|
|
||||||
const { data: album } = await api.get<SSAlbumResponse>(
|
|
||||||
`${server.url}/rest/getAlbum.view?v=1.13.0&c=Feishin&f=json&${server.token}`,
|
|
||||||
{ params: { id } },
|
|
||||||
);
|
|
||||||
|
|
||||||
return album;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAlbums = async (server: Server, params: SSAlbumsParams, recursiveData: any[] = []) => {
|
|
||||||
const albums: any = api
|
|
||||||
.get<SSAlbumListResponse>(
|
|
||||||
`${server.url}/rest/getAlbumList2.view?v=1.13.0&c=Feishin&f=json&${server.token}`,
|
|
||||||
{ params },
|
|
||||||
)
|
|
||||||
.then((res) => {
|
|
||||||
if (!res.data.albumList2?.album || res.data.albumList2?.album?.length === 0) {
|
|
||||||
// Flatten and return once there are no more albums left
|
|
||||||
return recursiveData.flatMap((album) => album);
|
|
||||||
}
|
|
||||||
|
|
||||||
// On every iteration, push the existing combined album array and increase the offset
|
|
||||||
recursiveData.push(res.data.albumList2.album);
|
|
||||||
return getAlbums(
|
|
||||||
server,
|
|
||||||
{
|
|
||||||
musicFolderId: params.musicFolderId,
|
|
||||||
offset: (params.offset || 0) + (params.size || 0),
|
|
||||||
size: params.size,
|
|
||||||
type: 'newest',
|
|
||||||
},
|
|
||||||
|
|
||||||
recursiveData,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((err) => console.log(err));
|
.json<SSGenreListResponse>();
|
||||||
|
|
||||||
return albums as SSAlbumListEntry[];
|
return data.genres.genre;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getArtistInfo = async (server: Server, id: string) => {
|
const getAlbumDetail = async (args: AlbumDetailArgs): Promise<SSAlbumDetail> => {
|
||||||
const { data: artistInfo } = await api.get<SSArtistInfoResponse>(
|
const { query, signal } = args;
|
||||||
`${server.url}/rest/getArtistInfo2.view?v=1.13.0&c=Feishin&f=json&${server.token}`,
|
|
||||||
{ params: { id } },
|
const data = await api
|
||||||
);
|
.get('/rest/getAlbum.view', {
|
||||||
|
searchParams: { id: query.id },
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<SSAlbumDetailResponse>();
|
||||||
|
|
||||||
|
return data.album;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAlbumList = async (args: AlbumListArgs): Promise<SSAlbumList> => {
|
||||||
|
const { query, signal } = args;
|
||||||
|
|
||||||
|
const normalizedParams = {};
|
||||||
|
const data = await api
|
||||||
|
.get('/rest/getAlbumList2.view', {
|
||||||
|
searchParams: normalizedParams,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<SSAlbumListResponse>();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...artistInfo,
|
items: data.albumList2.album,
|
||||||
artistInfo2: {
|
startIndex: query.startIndex,
|
||||||
...artistInfo.artistInfo2,
|
totalRecordCount: null,
|
||||||
biography: artistInfo.artistInfo2.biography
|
|
||||||
.replaceAll(/<a target.*<\/a>/gm, '')
|
|
||||||
.replace('Biography not available', ''),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createFavorite = async (args: FavoriteArgs): Promise<SSFavorite> => {
|
||||||
|
const { query, signal } = args;
|
||||||
|
|
||||||
|
const searchParams: SSFavoriteParams = {
|
||||||
|
albumId: query.type === 'album' ? query.id : undefined,
|
||||||
|
artistId: query.type === 'albumArtist' ? query.id : undefined,
|
||||||
|
id: query.type === 'song' ? query.id : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await api
|
||||||
|
.get('/rest/star.view', {
|
||||||
|
searchParams,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<SSFavoriteResponse>();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFavorite = async (args: FavoriteArgs) => {
|
||||||
|
const { query, signal } = args;
|
||||||
|
|
||||||
|
const searchParams: SSFavoriteParams = {
|
||||||
|
albumId: query.type === 'album' ? query.id : undefined,
|
||||||
|
artistId: query.type === 'albumArtist' ? query.id : undefined,
|
||||||
|
id: query.type === 'song' ? query.id : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await api
|
||||||
|
.get('/rest/unstar.view', {
|
||||||
|
searchParams,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<SSFavoriteResponse>();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateRating = async (args: RatingArgs) => {
|
||||||
|
const { query, signal } = args;
|
||||||
|
|
||||||
|
const searchParams: SSRatingParams = {
|
||||||
|
id: query.id,
|
||||||
|
rating: query.rating,
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await api
|
||||||
|
.get('/rest/setRating.view', {
|
||||||
|
searchParams,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
.json<SSRatingResponse>();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
export const subsonicApi = {
|
export const subsonicApi = {
|
||||||
authenticate,
|
authenticate,
|
||||||
getAlbum,
|
createFavorite,
|
||||||
getAlbums,
|
deleteFavorite,
|
||||||
getArtistInfo,
|
getAlbumArtistDetail,
|
||||||
getArtists,
|
getAlbumArtistList,
|
||||||
|
getAlbumDetail,
|
||||||
|
getAlbumList,
|
||||||
getCoverArtUrl,
|
getCoverArtUrl,
|
||||||
getGenres,
|
getGenreList,
|
||||||
getMusicFolders,
|
getMusicFolderList,
|
||||||
|
updateRating,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,78 +1,104 @@
|
|||||||
export interface SSBaseResponse {
|
export type SSBaseResponse = {
|
||||||
serverVersion?: 'string';
|
serverVersion?: 'string';
|
||||||
status: 'string';
|
status: 'string';
|
||||||
type?: 'string';
|
type?: 'string';
|
||||||
version: 'string';
|
version: 'string';
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSMusicFoldersResponse extends SSBaseResponse {
|
export type SSMusicFolderList = SSMusicFolder[];
|
||||||
|
|
||||||
|
export type SSMusicFolderListResponse = {
|
||||||
musicFolders: {
|
musicFolders: {
|
||||||
musicFolder: SSMusicFolder[];
|
musicFolder: SSMusicFolder[];
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSGenresResponse extends SSBaseResponse {
|
export type SSGenreList = SSGenre[];
|
||||||
|
|
||||||
|
export type SSGenreListResponse = {
|
||||||
genres: {
|
genres: {
|
||||||
genre: SSGenre[];
|
genre: SSGenre[];
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSArtistsResponse extends SSBaseResponse {
|
export type SSAlbumArtistDetailParams = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SSAlbumArtistDetail = SSAlbumArtistListEntry & { album: SSAlbumListEntry[] };
|
||||||
|
|
||||||
|
export type SSAlbumArtistDetailResponse = {
|
||||||
|
artist: SSAlbumArtistListEntry & {
|
||||||
|
album: SSAlbumListEntry[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SSAlbumArtistList = SSAlbumArtistListEntry[];
|
||||||
|
|
||||||
|
export type SSAlbumArtistListResponse = {
|
||||||
artists: {
|
artists: {
|
||||||
ignoredArticles: string;
|
ignoredArticles: string;
|
||||||
index: SSArtistIndex[];
|
index: SSArtistIndex[];
|
||||||
lastModified: number;
|
lastModified: number;
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSAlbumListResponse extends SSBaseResponse {
|
export type SSAlbumList = {
|
||||||
|
items: SSAlbumListEntry[];
|
||||||
|
startIndex: number;
|
||||||
|
totalRecordCount: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SSAlbumListResponse = {
|
||||||
albumList2: {
|
albumList2: {
|
||||||
album: SSAlbumListEntry[];
|
album: SSAlbumListEntry[];
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSAlbumResponse extends SSBaseResponse {
|
export type SSAlbumDetail = SSAlbum;
|
||||||
|
|
||||||
|
export type SSAlbumDetailResponse = {
|
||||||
album: SSAlbum;
|
album: SSAlbum;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSArtistInfoResponse extends SSBaseResponse {
|
export type SSArtistInfoResponse = {
|
||||||
artistInfo2: SSArtistInfo;
|
artistInfo2: SSArtistInfo;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSArtistInfo {
|
export type SSArtistInfo = {
|
||||||
biography: string;
|
biography: string;
|
||||||
largeImageUrl?: string;
|
largeImageUrl?: string;
|
||||||
lastFmUrl?: string;
|
lastFmUrl?: string;
|
||||||
mediumImageUrl?: string;
|
mediumImageUrl?: string;
|
||||||
musicBrainzId?: string;
|
musicBrainzId?: string;
|
||||||
smallImageUrl?: string;
|
smallImageUrl?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSMusicFolder {
|
export type SSMusicFolder = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSGenre {
|
export type SSGenre = {
|
||||||
albumCount?: number;
|
albumCount?: number;
|
||||||
songCount?: number;
|
songCount?: number;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSArtistIndex {
|
export type SSArtistIndex = {
|
||||||
artist: SSArtistListEntry[];
|
artist: SSAlbumArtistListEntry[];
|
||||||
name: string;
|
name: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSArtistListEntry {
|
export type SSAlbumArtistListEntry = {
|
||||||
albumCount: string;
|
albumCount: string;
|
||||||
artistImageUrl?: string;
|
artistImageUrl?: string;
|
||||||
coverArt?: string;
|
coverArt?: string;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSAlbumListEntry {
|
export type SSAlbumListEntry = {
|
||||||
album: string;
|
album: string;
|
||||||
artist: string;
|
artist: string;
|
||||||
artistId: string;
|
artistId: string;
|
||||||
@@ -90,13 +116,13 @@ export interface SSAlbumListEntry {
|
|||||||
title: string;
|
title: string;
|
||||||
userRating?: number;
|
userRating?: number;
|
||||||
year: number;
|
year: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSAlbum extends SSAlbumListEntry {
|
export type SSAlbum = {
|
||||||
song: SSSong[];
|
song: SSSong[];
|
||||||
}
|
} & SSAlbumListEntry;
|
||||||
|
|
||||||
export interface SSSong {
|
export type SSSong = {
|
||||||
album: string;
|
album: string;
|
||||||
albumId: string;
|
albumId: string;
|
||||||
artist: string;
|
artist: string;
|
||||||
@@ -122,9 +148,9 @@ export interface SSSong {
|
|||||||
type: string;
|
type: string;
|
||||||
userRating?: number;
|
userRating?: number;
|
||||||
year: number;
|
year: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSAlbumsParams {
|
export type SSAlbumListParams = {
|
||||||
fromYear?: number;
|
fromYear?: number;
|
||||||
genre?: string;
|
genre?: string;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string;
|
||||||
@@ -132,8 +158,27 @@ export interface SSAlbumsParams {
|
|||||||
size?: number;
|
size?: number;
|
||||||
toYear?: number;
|
toYear?: number;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SSArtistsParams {
|
export type SSAlbumArtistListParams = {
|
||||||
musicFolderId?: number;
|
musicFolderId?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type SSFavoriteParams = {
|
||||||
|
albumId?: string;
|
||||||
|
artistId?: string;
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SSFavorite = null;
|
||||||
|
|
||||||
|
export type SSFavoriteResponse = null;
|
||||||
|
|
||||||
|
export type SSRatingParams = {
|
||||||
|
id: string;
|
||||||
|
rating: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SSRating = null;
|
||||||
|
|
||||||
|
export type SSRatingResponse = null;
|
||||||
|
|||||||
+211
-101
@@ -1,7 +1,30 @@
|
|||||||
import type { ServerListItem } from '../store';
|
import type { ServerListItem } from '/@/store';
|
||||||
import type { ServerType } from '../types';
|
import type { ServerType } from '/@//types';
|
||||||
import type { JFAlbum, JFAlbumListSort, JFSortOrder } from './jellyfin.types';
|
import type { JFAlbumListSort, JFSortOrder } from '/@/api/jellyfin.types';
|
||||||
import type { NDAlbum, NDAlbumListSort, NDOrder } from './navidrome.types';
|
import type {
|
||||||
|
NDAlbumArtistDetail,
|
||||||
|
NDAlbumArtistList,
|
||||||
|
NDAlbumArtistListSort,
|
||||||
|
NDAlbumDetail,
|
||||||
|
NDAlbumList,
|
||||||
|
NDAlbumListSort,
|
||||||
|
NDCreatePlaylist,
|
||||||
|
NDDeletePlaylist,
|
||||||
|
NDGenreList,
|
||||||
|
NDOrder,
|
||||||
|
NDPlaylistDetail,
|
||||||
|
NDPlaylistList,
|
||||||
|
NDPlaylistListSort,
|
||||||
|
NDSongDetail,
|
||||||
|
NDSongList,
|
||||||
|
NDSongListSort,
|
||||||
|
} from '/@/api/navidrome.types';
|
||||||
|
import type {
|
||||||
|
SSAlbumArtistDetail,
|
||||||
|
SSAlbumArtistList,
|
||||||
|
SSAlbumDetail,
|
||||||
|
SSAlbumList,
|
||||||
|
} from '/@/api/subsonic.types';
|
||||||
|
|
||||||
export enum SortOrder {
|
export enum SortOrder {
|
||||||
ASC = 'ASC',
|
ASC = 'ASC',
|
||||||
@@ -27,33 +50,15 @@ export enum ImageType {
|
|||||||
SCREENSHOT = 'SCREENSHOT',
|
SCREENSHOT = 'SCREENSHOT',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TaskType {
|
|
||||||
FULL_SCAN = 'FULL_SCAN',
|
|
||||||
LASTFM = 'LASTFM',
|
|
||||||
MUSICBRAINZ = 'MUSICBRAINZ',
|
|
||||||
QUICK_SCAN = 'QUICK_SCAN',
|
|
||||||
REFRESH = 'REFRESH',
|
|
||||||
SPOTIFY = 'SPOTIFY',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EndpointDetails = {
|
export type EndpointDetails = {
|
||||||
server: ServerListItem;
|
server: ServerListItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
// export interface BaseResponse<T> {
|
|
||||||
// error?: string | any;
|
|
||||||
// items: T;
|
|
||||||
// response: 'Success' | 'Error';
|
|
||||||
// statusCode: number;
|
|
||||||
// }
|
|
||||||
|
|
||||||
export interface BasePaginatedResponse<T> {
|
export interface BasePaginatedResponse<T> {
|
||||||
error?: string | any;
|
error?: string | any;
|
||||||
items: T;
|
items: T;
|
||||||
pagination?: {
|
startIndex: number;
|
||||||
startIndex: number;
|
totalRecordCount: number;
|
||||||
totalEntries: number;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiError = {
|
export type ApiError = {
|
||||||
@@ -66,47 +71,14 @@ export type ApiError = {
|
|||||||
statusCode: number;
|
statusCode: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AuthResponse = {
|
export type AuthenticationResponse = {
|
||||||
credential: string;
|
credential: string;
|
||||||
ndCredential?: string;
|
ndCredential?: string;
|
||||||
|
userId: string | null;
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// export type NullResponse = BaseResponse<null>;
|
|
||||||
|
|
||||||
export type PaginationParams = {
|
|
||||||
skip: number;
|
|
||||||
take: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RelatedServerFolder = {
|
|
||||||
enabled: boolean;
|
|
||||||
id: string;
|
|
||||||
lastScannedAt: string | null;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ServerFolder = {
|
|
||||||
createdAt: string;
|
|
||||||
enabled: boolean;
|
|
||||||
id: string;
|
|
||||||
lastScannedAt: string | null;
|
|
||||||
name: string;
|
|
||||||
serverId: string;
|
|
||||||
updatedAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Genre = {
|
export type Genre = {
|
||||||
albumArtistCount: number;
|
|
||||||
albumCount: number;
|
|
||||||
artistCount: number;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
songCount: number;
|
|
||||||
totalCount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RelatedGenre = {
|
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
@@ -116,7 +88,7 @@ export type Album = {
|
|||||||
artists: RelatedArtist[];
|
artists: RelatedArtist[];
|
||||||
backdropImageUrl: string | null;
|
backdropImageUrl: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
genres: RelatedGenre[];
|
genres: Genre[];
|
||||||
id: string;
|
id: string;
|
||||||
imagePlaceholderUrl: string | null;
|
imagePlaceholderUrl: string | null;
|
||||||
imageUrl: string | null;
|
imageUrl: string | null;
|
||||||
@@ -147,7 +119,7 @@ export type Song = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
discNumber: number;
|
discNumber: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
genres: RelatedGenre[];
|
genres: Genre[];
|
||||||
id: string;
|
id: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
isFavorite: boolean;
|
isFavorite: boolean;
|
||||||
@@ -193,69 +165,207 @@ export type RelatedArtist = {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RelatedServer = {
|
export type MusicFolder = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: ServerType;
|
|
||||||
url: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RelatedUser = {
|
export type Playlist = {
|
||||||
enabled: boolean;
|
duration?: number;
|
||||||
id: string;
|
id: string;
|
||||||
isAdmin: boolean;
|
name: string;
|
||||||
|
public?: boolean;
|
||||||
|
size?: number;
|
||||||
|
songCount?: number;
|
||||||
|
userId: string;
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Task = {
|
export type GenresResponse = Genre[];
|
||||||
createdAt: string;
|
|
||||||
id: string;
|
|
||||||
isCompleted: boolean;
|
|
||||||
isError: boolean;
|
|
||||||
message: string;
|
|
||||||
server: RelatedServer | null;
|
|
||||||
type: TaskType;
|
|
||||||
updatedAt: string;
|
|
||||||
user: RelatedUser | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AlbumListSort = NDAlbumListSort | JFAlbumListSort;
|
export type MusicFoldersResponse = MusicFolder[];
|
||||||
|
|
||||||
export type ListSortOrder = NDOrder | JFSortOrder;
|
export type ListSortOrder = NDOrder | JFSortOrder;
|
||||||
|
|
||||||
export type AlbumListParams = {
|
type BaseEndpointArgs = {
|
||||||
_skip: number;
|
server: ServerListItem | null;
|
||||||
_take?: number;
|
signal?: AbortSignal;
|
||||||
musicFolderId: string | null;
|
};
|
||||||
nd?: {
|
|
||||||
|
// Genre List ---------------------------------------------------------------------------
|
||||||
|
export type RawGenreListResponse = NDGenreList | undefined;
|
||||||
|
|
||||||
|
export type GenreListResponse = BasePaginatedResponse<Genre[]> | null | undefined;
|
||||||
|
|
||||||
|
export type GenreListArgs = { query: GenreListQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
export type GenreListQuery = null;
|
||||||
|
|
||||||
|
// Album List ---------------------------------------------------------------------------
|
||||||
|
export type RawAlbumListResponse = NDAlbumList | SSAlbumList | undefined;
|
||||||
|
|
||||||
|
export type AlbumListResponse = BasePaginatedResponse<Album[]> | null | undefined;
|
||||||
|
|
||||||
|
export type AlbumListSort = NDAlbumListSort | JFAlbumListSort;
|
||||||
|
|
||||||
|
export type AlbumListQuery = {
|
||||||
|
limit?: number;
|
||||||
|
musicFolderId?: string;
|
||||||
|
ndParams?: {
|
||||||
|
artist_id?: string;
|
||||||
|
compilation?: boolean;
|
||||||
|
genre_id?: string;
|
||||||
|
has_rating?: boolean;
|
||||||
|
name?: string;
|
||||||
|
starred?: boolean;
|
||||||
|
year?: number;
|
||||||
|
};
|
||||||
|
sortBy: AlbumListSort;
|
||||||
|
sortOrder: SortOrder;
|
||||||
|
startIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AlbumListArgs = { query: AlbumListQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
// Album Detail -------------------------------------------------------------------------
|
||||||
|
export type RawAlbumDetailResponse = NDAlbumDetail | SSAlbumDetail | undefined;
|
||||||
|
|
||||||
|
export type AlbumDetailResponse = Album | null | undefined;
|
||||||
|
|
||||||
|
export type AlbumDetailQuery = { id: string };
|
||||||
|
|
||||||
|
export type AlbumDetailArgs = { query: AlbumDetailQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
// Song List ----------------------------------------------------------------------------
|
||||||
|
export type RawSongListResponse = NDSongList | undefined;
|
||||||
|
|
||||||
|
export type SongListResponse = BasePaginatedResponse<Song[]>;
|
||||||
|
|
||||||
|
export type SongListSort = NDSongListSort;
|
||||||
|
|
||||||
|
export type SongListQuery = {
|
||||||
|
limit?: number;
|
||||||
|
musicFolderId?: string;
|
||||||
|
ndParams?: {
|
||||||
artist_id?: string;
|
artist_id?: string;
|
||||||
compilation?: boolean;
|
compilation?: boolean;
|
||||||
genre_id?: string;
|
genre_id?: string;
|
||||||
has_rating?: boolean;
|
has_rating?: boolean;
|
||||||
starred?: boolean;
|
starred?: boolean;
|
||||||
|
title?: string;
|
||||||
year?: number;
|
year?: number;
|
||||||
};
|
};
|
||||||
sortBy: NDAlbumListSort | JFAlbumListSort;
|
sortBy: SongListSort;
|
||||||
sortOrder: SortOrder;
|
sortOrder: SortOrder;
|
||||||
|
startIndex: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AlbumListResponse =
|
export type SongListArgs = { query: SongListQuery } & BaseEndpointArgs;
|
||||||
| BasePaginatedResponse<Album[] | NDAlbum[] | JFAlbum[]>
|
|
||||||
| null
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
export type AlbumDetailQuery = {
|
// Song Detail -------------------------------------------------------------------------
|
||||||
|
export type RawSongDetailResponse = NDSongDetail | undefined;
|
||||||
|
|
||||||
|
export type SongDetailResponse = Song | null | undefined;
|
||||||
|
|
||||||
|
export type SongDetailQuery = { id: string };
|
||||||
|
|
||||||
|
export type SongDetailArgs = { query: SongDetailQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
// Album Artist List -------------------------------------------------------------------
|
||||||
|
export type RawAlbumArtistListResponse = NDAlbumArtistList | SSAlbumArtistList | undefined;
|
||||||
|
|
||||||
|
export type AlbumArtistListResponse = BasePaginatedResponse<AlbumArtist[]>;
|
||||||
|
|
||||||
|
export type AlbumArtistListSort = NDAlbumArtistListSort;
|
||||||
|
|
||||||
|
export type AlbumArtistListQuery = {
|
||||||
|
limit?: number;
|
||||||
|
musicFolderId?: string;
|
||||||
|
ndParams?: {
|
||||||
|
genre_id?: string;
|
||||||
|
name?: string;
|
||||||
|
starred?: boolean;
|
||||||
|
};
|
||||||
|
sortBy: AlbumArtistListSort;
|
||||||
|
sortOrder: SortOrder;
|
||||||
|
startIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AlbumArtistListArgs = { query: AlbumArtistListQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
// Album Artist Detail -----------------------------------------------------------------
|
||||||
|
export type RawAlbumArtistDetailResponse = NDAlbumArtistDetail | SSAlbumArtistDetail | undefined;
|
||||||
|
|
||||||
|
export type AlbumArtistDetailResponse = BasePaginatedResponse<AlbumArtist[]>;
|
||||||
|
|
||||||
|
export type AlbumArtistDetailQuery = { id: string };
|
||||||
|
|
||||||
|
export type AlbumArtistDetailArgs = { query: AlbumArtistDetailQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
// Artist List -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Artist Detail -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Favorite ----------------------------------------------------------------------------
|
||||||
|
export type RawFavoriteResponse = null | undefined;
|
||||||
|
|
||||||
|
export type FavoriteResponse = null;
|
||||||
|
|
||||||
|
export type FavoriteQuery = { id: string; type?: 'song' | 'album' | 'albumArtist' };
|
||||||
|
|
||||||
|
export type FavoriteArgs = { query: FavoriteQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
// Rating -------------------------------------------------------------------------------
|
||||||
|
export type RawRatingResponse = null | undefined;
|
||||||
|
|
||||||
|
export type RatingResponse = null;
|
||||||
|
|
||||||
|
export type RatingQuery = { id: string; rating: number };
|
||||||
|
|
||||||
|
export type RatingArgs = { query: RatingQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
// Create Playlist -----------------------------------------------------------------------
|
||||||
|
export type RawCreatePlaylistResponse = NDCreatePlaylist | undefined;
|
||||||
|
|
||||||
|
export type CreatePlaylistResponse = null;
|
||||||
|
|
||||||
|
export type CreatePlaylistQuery = { comment?: string; name: string; public?: boolean };
|
||||||
|
|
||||||
|
export type CreatePlaylistArgs = { query: CreatePlaylistQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
// Delete Playlist -----------------------------------------------------------------------
|
||||||
|
export type RawDeletePlaylistResponse = NDDeletePlaylist | undefined;
|
||||||
|
|
||||||
|
export type DeletePlaylistResponse = null;
|
||||||
|
|
||||||
|
export type DeletePlaylistQuery = { id: string };
|
||||||
|
|
||||||
|
export type DeletePlaylistArgs = { query: DeletePlaylistQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
// Playlist List -------------------------------------------------------------------------
|
||||||
|
export type RawPlaylistListResponse = NDPlaylistList | undefined;
|
||||||
|
|
||||||
|
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]>;
|
||||||
|
|
||||||
|
export type PlaylistListSort = NDPlaylistListSort;
|
||||||
|
|
||||||
|
export type PlaylistListQuery = {
|
||||||
|
limit?: number;
|
||||||
|
musicFolderId?: string;
|
||||||
|
sortBy: PlaylistListSort;
|
||||||
|
sortOrder: SortOrder;
|
||||||
|
startIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PlaylistListArgs = { query: PlaylistListQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
|
// Playlist Detail -----------------------------------------------------------------------
|
||||||
|
export type RawPlaylistDetailResponse = NDPlaylistDetail | undefined;
|
||||||
|
|
||||||
|
export type PlaylistDetailResponse = BasePaginatedResponse<Playlist[]>;
|
||||||
|
|
||||||
|
export type PlaylistDetailQuery = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AlbumDetailResponse = Album | NDAlbum | JFAlbum | null | undefined;
|
export type PlaylistDetailArgs = { query: PlaylistDetailQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
export type Count = {
|
|
||||||
artists?: number;
|
|
||||||
externals?: number;
|
|
||||||
favorites?: number;
|
|
||||||
genres?: number;
|
|
||||||
images?: number;
|
|
||||||
ratings?: number;
|
|
||||||
songs?: number;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { queryKeys } from '/@/api/query-keys';
|
import { queryKeys } from '/@/api/query-keys';
|
||||||
import type { QueryOptions } from '/@/lib/react-query';
|
import type { QueryOptions } from '/@/lib/react-query';
|
||||||
import { useCurrentServer } from '../../../store/auth.store';
|
import { useCurrentServer } from '../../../store/auth.store';
|
||||||
import { apiController } from '/@/api/controller';
|
|
||||||
import type { AlbumDetailQuery } from '/@/api/types';
|
import type { AlbumDetailQuery } from '/@/api/types';
|
||||||
|
import { controller } from '/@/api/controller';
|
||||||
|
|
||||||
export const useAlbumDetail = (query: AlbumDetailQuery, options: QueryOptions) => {
|
export const useAlbumDetail = (query: AlbumDetailQuery, options: QueryOptions) => {
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryFn: ({ signal }) => apiController.getAlbumDetail(query, signal),
|
queryFn: ({ signal }) => controller.getAlbumDetail({ query, server, signal }),
|
||||||
queryKey: queryKeys.albums.detail(server?.id || '', query),
|
queryKey: queryKeys.albums.detail(server?.id || '', query),
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { apiController } from '/@/api/controller';
|
import { controller } from '/@/api/controller';
|
||||||
import { queryKeys } from '/@/api/query-keys';
|
import { queryKeys } from '/@/api/query-keys';
|
||||||
import type { AlbumListParams, AlbumListResponse } from '/@/api/types';
|
import type { AlbumListQuery, RawAlbumListResponse } from '/@/api/types';
|
||||||
import type { QueryOptions } from '/@/lib/react-query';
|
import type { QueryOptions } from '/@/lib/react-query';
|
||||||
import { useCurrentServer, useCurrentServerId } from '/@/store';
|
import { useCurrentServer } from '/@/store';
|
||||||
import { ndNormalize } from '/@/api/navidrome.api';
|
import { ndNormalize } from '/@/api/navidrome.api';
|
||||||
import type { NDAlbum } from '/@/api/navidrome.types';
|
import type { NDAlbum } from '/@/api/navidrome.types';
|
||||||
|
|
||||||
export const useAlbumList = (params: AlbumListParams, options?: QueryOptions) => {
|
export const useAlbumList = (query: AlbumListQuery, options?: QueryOptions) => {
|
||||||
const serverId = useCurrentServerId();
|
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
enabled: !!serverId,
|
enabled: !!server?.id,
|
||||||
queryFn: ({ signal }) => apiController.getAlbumList(params, signal),
|
queryFn: ({ signal }) => controller.getAlbumList({ query, server, signal }),
|
||||||
queryKey: queryKeys.albums.list(serverId, params),
|
queryKey: queryKeys.albums.list(server?.id || '', query),
|
||||||
select: useCallback(
|
select: useCallback(
|
||||||
(data: AlbumListResponse) => {
|
(data: RawAlbumListResponse | undefined) => {
|
||||||
let albums;
|
let albums;
|
||||||
switch (server?.type) {
|
switch (server?.type) {
|
||||||
case 'jellyfin':
|
case 'jellyfin':
|
||||||
@@ -30,8 +29,9 @@ export const useAlbumList = (params: AlbumListParams, options?: QueryOptions) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data,
|
|
||||||
items: albums,
|
items: albums,
|
||||||
|
startIndex: data?.startIndex,
|
||||||
|
totalRecordCount: data?.totalRecordCount,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[server],
|
[server],
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { useAlbumList } from '../queries/album-list-query';
|
|||||||
import { JFAlbumListSort } from '/@/api/jellyfin.types';
|
import { JFAlbumListSort } from '/@/api/jellyfin.types';
|
||||||
import type { NDAlbum } from '/@/api/navidrome.types';
|
import type { NDAlbum } from '/@/api/navidrome.types';
|
||||||
import { NDAlbumListSort } from '/@/api/navidrome.types';
|
import { NDAlbumListSort } from '/@/api/navidrome.types';
|
||||||
import { apiController } from '/@/api/controller';
|
import { controller } from '/@/api/controller';
|
||||||
import { ndNormalize } from '/@/api/navidrome.api';
|
import { ndNormalize } from '/@/api/navidrome.api';
|
||||||
|
|
||||||
const FILTERS = {
|
const FILTERS = {
|
||||||
@@ -70,28 +70,30 @@ export const AlbumListRoute = () => {
|
|||||||
const filters = page.list.filter;
|
const filters = page.list.filter;
|
||||||
|
|
||||||
const albumListQuery = useAlbumList({
|
const albumListQuery = useAlbumList({
|
||||||
_skip: 0,
|
limit: 1,
|
||||||
_take: 1,
|
|
||||||
musicFolderId: null,
|
|
||||||
sortBy: filters.sortBy,
|
sortBy: filters.sortBy,
|
||||||
sortOrder: filters.sortOrder,
|
sortOrder: filters.sortOrder,
|
||||||
|
startIndex: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetch = useCallback(
|
const fetch = useCallback(
|
||||||
async ({ skip, take }: { skip: number; take: number }) => {
|
async ({ skip, take }: { skip: number; take: number }) => {
|
||||||
const queryKey = queryKeys.albums.list(server?.id || '', {
|
const queryKey = queryKeys.albums.list(server?.id || '', {
|
||||||
_skip: skip,
|
limit: take,
|
||||||
_take: take,
|
startIndex: skip,
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
const albums = await queryClient.fetchQuery(queryKey, async () =>
|
const albums = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
|
||||||
apiController.getAlbumList({
|
controller.getAlbumList({
|
||||||
_skip: skip,
|
query: {
|
||||||
_take: take,
|
limit: take,
|
||||||
musicFolderId: null,
|
sortBy: filters.sortBy,
|
||||||
sortBy: filters.sortBy,
|
sortOrder: filters.sortOrder,
|
||||||
sortOrder: filters.sortOrder,
|
startIndex: skip,
|
||||||
|
},
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -110,10 +112,8 @@ export const AlbumListRoute = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
pagination: {
|
startIndex: skip,
|
||||||
startIndex: skip,
|
totalRecordCount: albums?.totalRecordCount || 0,
|
||||||
totalEntries: albums?.pagination?.totalEntries || 0,
|
|
||||||
},
|
|
||||||
} as AlbumListResponse;
|
} as AlbumListResponse;
|
||||||
},
|
},
|
||||||
[filters, queryClient, server],
|
[filters, queryClient, server],
|
||||||
@@ -445,7 +445,7 @@ export const AlbumListRoute = () => {
|
|||||||
fetchFn={fetch}
|
fetchFn={fetch}
|
||||||
height={height}
|
height={height}
|
||||||
initialScrollOffset={page.list?.gridScrollOffset || 0}
|
initialScrollOffset={page.list?.gridScrollOffset || 0}
|
||||||
itemCount={albumListQuery?.data?.pagination?.totalEntries || 0}
|
itemCount={albumListQuery?.data?.totalRecordCount || 0}
|
||||||
itemGap={20}
|
itemGap={20}
|
||||||
itemSize={150 + page.list?.size}
|
itemSize={150 + page.list?.size}
|
||||||
itemType={LibraryItem.ALBUM}
|
itemType={LibraryItem.ALBUM}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type LibraryPageProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ListFilter = {
|
type ListFilter = {
|
||||||
musicFolderId: string | null;
|
musicFolderId?: string;
|
||||||
sortBy: AlbumListSort;
|
sortBy: AlbumListSort;
|
||||||
sortOrder: SortOrder;
|
sortOrder: SortOrder;
|
||||||
};
|
};
|
||||||
@@ -89,7 +89,7 @@ export const useAppStore = create<AppSlice>()(
|
|||||||
...state.albums.list,
|
...state.albums.list,
|
||||||
filter: {
|
filter: {
|
||||||
...state.albums.list.filter,
|
...state.albums.list.filter,
|
||||||
musicFolderId: null,
|
musicFolderId: undefined,
|
||||||
},
|
},
|
||||||
gridScrollOffset: 0,
|
gridScrollOffset: 0,
|
||||||
listScrollOffset: 0,
|
listScrollOffset: 0,
|
||||||
@@ -118,7 +118,7 @@ export const useAppStore = create<AppSlice>()(
|
|||||||
},
|
},
|
||||||
display: CardDisplayType.CARD,
|
display: CardDisplayType.CARD,
|
||||||
filter: {
|
filter: {
|
||||||
musicFolderId: null,
|
musicFolderId: undefined,
|
||||||
sortBy: JFAlbumListSort.NAME,
|
sortBy: JFAlbumListSort.NAME,
|
||||||
sortOrder: SortOrder.DESC,
|
sortOrder: SortOrder.DESC,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user