feat: auth & admin
This commit is contained in:
@@ -7,12 +7,27 @@ export const adminApi = api.injectEndpoints({
|
||||
query: () => '/admin/users',
|
||||
providesTags: ['User'],
|
||||
}),
|
||||
createUser: build.mutation<User, { username: string; password: string; email?: string; role: 'admin' | 'user' }>({
|
||||
createUser: build.mutation<
|
||||
User,
|
||||
{
|
||||
username: string;
|
||||
password: string;
|
||||
email?: string;
|
||||
role: 'admin' | 'user';
|
||||
}
|
||||
>({
|
||||
query: (body) => ({ url: '/admin/users', method: 'POST', body }),
|
||||
invalidatesTags: ['User'],
|
||||
}),
|
||||
updateUser: build.mutation<User, { id: string; role?: 'admin' | 'user'; email?: string }>({
|
||||
query: ({ id, ...body }) => ({ url: `/admin/users/${id}`, method: 'PATCH', body }),
|
||||
updateUser: build.mutation<
|
||||
User,
|
||||
{ id: string; role?: 'admin' | 'user'; email?: string }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/users/${id}`,
|
||||
method: 'PATCH',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: ['User'],
|
||||
}),
|
||||
deleteUser: build.mutation<void, string>({
|
||||
@@ -23,4 +38,9 @@ export const adminApi = api.injectEndpoints({
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
export const { useGetUsersQuery, useCreateUserMutation, useUpdateUserMutation, useDeleteUserMutation } = adminApi;
|
||||
export const {
|
||||
useGetUsersQuery,
|
||||
useCreateUserMutation,
|
||||
useUpdateUserMutation,
|
||||
useDeleteUserMutation,
|
||||
} = adminApi;
|
||||
|
||||
@@ -9,7 +9,10 @@ export const authApi = api.injectEndpoints({
|
||||
logout: build.mutation<void, void>({
|
||||
query: () => ({ url: '/auth/logout', method: 'POST' }),
|
||||
}),
|
||||
refreshToken: build.mutation<{ accessToken: string; refreshToken: string; expiresIn: number }, { refreshToken: string }>({
|
||||
refreshToken: build.mutation<
|
||||
{ accessToken: string; refreshToken: string; expiresIn: number },
|
||||
{ refreshToken: string }
|
||||
>({
|
||||
query: (body) => ({ url: '/auth/refresh', method: 'POST', body }),
|
||||
}),
|
||||
me: build.query<import('../types').User, void>({
|
||||
|
||||
@@ -3,11 +3,20 @@ import type { DownloadJob } from '../types';
|
||||
|
||||
export const downloadsApi = api.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
getDownloads: build.query<DownloadJob[], { status?: DownloadJob['status'] } | void>({
|
||||
getDownloads: build.query<
|
||||
DownloadJob[],
|
||||
{ status?: DownloadJob['status'] } | void
|
||||
>({
|
||||
query: (params) => ({ url: '/downloads', params: params ?? {} }),
|
||||
providesTags: ['Download'],
|
||||
}),
|
||||
addDownload: build.mutation<DownloadJob, { url: string; metadata?: { title?: string; artist?: string; album?: string } }>({
|
||||
addDownload: build.mutation<
|
||||
DownloadJob,
|
||||
{
|
||||
url: string;
|
||||
metadata?: { title?: string; artist?: string; album?: string };
|
||||
}
|
||||
>({
|
||||
query: (body) => ({ url: '/downloads', method: 'POST', body }),
|
||||
invalidatesTags: ['Download'],
|
||||
}),
|
||||
@@ -23,4 +32,9 @@ export const downloadsApi = api.injectEndpoints({
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
export const { useGetDownloadsQuery, useAddDownloadMutation, useCancelDownloadMutation, useRetryDownloadMutation } = downloadsApi;
|
||||
export const {
|
||||
useGetDownloadsQuery,
|
||||
useAddDownloadMutation,
|
||||
useCancelDownloadMutation,
|
||||
useRetryDownloadMutation,
|
||||
} = downloadsApi;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { api } from '../index';
|
||||
import type { Track, Album, Artist, PaginatedResponse, LibraryFilters } from '../types';
|
||||
import type {
|
||||
Track,
|
||||
Album,
|
||||
Artist,
|
||||
PaginatedResponse,
|
||||
LibraryFilters,
|
||||
} from '../types';
|
||||
|
||||
export const libraryApi = api.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
@@ -7,18 +13,32 @@ export const libraryApi = api.injectEndpoints({
|
||||
query: (filters) => ({ url: '/library/tracks', params: filters ?? {} }),
|
||||
providesTags: (result) =>
|
||||
result
|
||||
? [...result.items.map(({ id }) => ({ type: 'Track' as const, id })), 'Track']
|
||||
? [
|
||||
...result.items.map(({ id }) => ({ type: 'Track' as const, id })),
|
||||
'Track',
|
||||
]
|
||||
: ['Track'],
|
||||
}),
|
||||
getTrack: build.query<Track, string>({
|
||||
query: (id) => `/library/tracks/${id}`,
|
||||
providesTags: (_r, _e, id) => [{ type: 'Track', id }],
|
||||
}),
|
||||
getAlbums: build.query<PaginatedResponse<Album>, { search?: string; artistId?: string; page?: number; pageSize?: number } | void>({
|
||||
getAlbums: build.query<
|
||||
PaginatedResponse<Album>,
|
||||
{
|
||||
search?: string;
|
||||
artistId?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
} | void
|
||||
>({
|
||||
query: (params) => ({ url: '/library/albums', params: params ?? {} }),
|
||||
providesTags: (result) =>
|
||||
result
|
||||
? [...result.items.map(({ id }) => ({ type: 'Album' as const, id })), 'Album']
|
||||
? [
|
||||
...result.items.map(({ id }) => ({ type: 'Album' as const, id })),
|
||||
'Album',
|
||||
]
|
||||
: ['Album'],
|
||||
}),
|
||||
getAlbum: build.query<Album, string>({
|
||||
@@ -27,13 +47,25 @@ export const libraryApi = api.injectEndpoints({
|
||||
}),
|
||||
getAlbumTracks: build.query<Track[], string>({
|
||||
query: (albumId) => `/library/albums/${albumId}/tracks`,
|
||||
providesTags: (_r, _e, albumId) => [{ type: 'Album', id: albumId }, 'Track'],
|
||||
providesTags: (_r, _e, albumId) => [
|
||||
{ type: 'Album', id: albumId },
|
||||
'Track',
|
||||
],
|
||||
}),
|
||||
getArtists: build.query<PaginatedResponse<Artist>, { search?: string; page?: number; pageSize?: number } | void>({
|
||||
getArtists: build.query<
|
||||
PaginatedResponse<Artist>,
|
||||
{ search?: string; page?: number; pageSize?: number } | void
|
||||
>({
|
||||
query: (params) => ({ url: '/library/artists', params: params ?? {} }),
|
||||
providesTags: (result) =>
|
||||
result
|
||||
? [...result.items.map(({ id }) => ({ type: 'Artist' as const, id })), 'Artist']
|
||||
? [
|
||||
...result.items.map(({ id }) => ({
|
||||
type: 'Artist' as const,
|
||||
id,
|
||||
})),
|
||||
'Artist',
|
||||
]
|
||||
: ['Artist'],
|
||||
}),
|
||||
getArtist: build.query<Artist, string>({
|
||||
@@ -42,9 +74,15 @@ export const libraryApi = api.injectEndpoints({
|
||||
}),
|
||||
getArtistAlbums: build.query<Album[], string>({
|
||||
query: (artistId) => `/library/artists/${artistId}/albums`,
|
||||
providesTags: (_r, _e, artistId) => [{ type: 'Artist', id: artistId }, 'Album'],
|
||||
providesTags: (_r, _e, artistId) => [
|
||||
{ type: 'Artist', id: artistId },
|
||||
'Album',
|
||||
],
|
||||
}),
|
||||
searchLibrary: build.query<{ tracks: Track[]; albums: Album[]; artists: Artist[] }, string>({
|
||||
searchLibrary: build.query<
|
||||
{ tracks: Track[]; albums: Album[]; artists: Artist[] },
|
||||
string
|
||||
>({
|
||||
query: (q) => ({ url: '/library/search', params: { q } }),
|
||||
providesTags: ['Track', 'Album', 'Artist'],
|
||||
}),
|
||||
|
||||
@@ -7,7 +7,10 @@ export const likesApi = api.injectEndpoints({
|
||||
invalidatesTags: (_r, _e, id) => ['Like', { type: 'Track', id }],
|
||||
}),
|
||||
unlikeTrack: build.mutation<void, string>({
|
||||
query: (trackId) => ({ url: `/likes/tracks/${trackId}`, method: 'DELETE' }),
|
||||
query: (trackId) => ({
|
||||
url: `/likes/tracks/${trackId}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: (_r, _e, id) => ['Like', { type: 'Track', id }],
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -15,25 +15,52 @@ export const playlistsApi = api.injectEndpoints({
|
||||
query: (id) => `/playlists/${id}/tracks`,
|
||||
providesTags: (_r, _e, id) => [{ type: 'Playlist', id }, 'Track'],
|
||||
}),
|
||||
createPlaylist: build.mutation<Playlist, { name: string; description?: string; isPublic?: boolean }>({
|
||||
createPlaylist: build.mutation<
|
||||
Playlist,
|
||||
{ name: string; description?: string; isPublic?: boolean }
|
||||
>({
|
||||
query: (body) => ({ url: '/playlists', method: 'POST', body }),
|
||||
invalidatesTags: ['Playlist'],
|
||||
}),
|
||||
updatePlaylist: build.mutation<Playlist, { id: string; name?: string; description?: string; isPublic?: boolean }>({
|
||||
query: ({ id, ...body }) => ({ url: `/playlists/${id}`, method: 'PATCH', body }),
|
||||
updatePlaylist: build.mutation<
|
||||
Playlist,
|
||||
{ id: string; name?: string; description?: string; isPublic?: boolean }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/playlists/${id}`,
|
||||
method: 'PATCH',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, _e, { id }) => [{ type: 'Playlist', id }],
|
||||
}),
|
||||
deletePlaylist: build.mutation<void, string>({
|
||||
query: (id) => ({ url: `/playlists/${id}`, method: 'DELETE' }),
|
||||
invalidatesTags: ['Playlist'],
|
||||
}),
|
||||
addTrackToPlaylist: build.mutation<void, { playlistId: string; trackId: string }>({
|
||||
query: ({ playlistId, trackId }) => ({ url: `/playlists/${playlistId}/tracks`, method: 'POST', body: { trackId } }),
|
||||
invalidatesTags: (_r, _e, { playlistId }) => [{ type: 'Playlist', id: playlistId }],
|
||||
addTrackToPlaylist: build.mutation<
|
||||
void,
|
||||
{ playlistId: string; trackId: string }
|
||||
>({
|
||||
query: ({ playlistId, trackId }) => ({
|
||||
url: `/playlists/${playlistId}/tracks`,
|
||||
method: 'POST',
|
||||
body: { trackId },
|
||||
}),
|
||||
invalidatesTags: (_r, _e, { playlistId }) => [
|
||||
{ type: 'Playlist', id: playlistId },
|
||||
],
|
||||
}),
|
||||
removeTrackFromPlaylist: build.mutation<void, { playlistId: string; trackId: string; position: number }>({
|
||||
query: ({ playlistId, position }) => ({ url: `/playlists/${playlistId}/tracks/${position}`, method: 'DELETE' }),
|
||||
invalidatesTags: (_r, _e, { playlistId }) => [{ type: 'Playlist', id: playlistId }],
|
||||
removeTrackFromPlaylist: build.mutation<
|
||||
void,
|
||||
{ playlistId: string; trackId: string; position: number }
|
||||
>({
|
||||
query: ({ playlistId, position }) => ({
|
||||
url: `/playlists/${playlistId}/tracks/${position}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: (_r, _e, { playlistId }) => [
|
||||
{ type: 'Playlist', id: playlistId },
|
||||
],
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
|
||||
@@ -12,11 +12,18 @@ export const storageApi = api.injectEndpoints({
|
||||
invalidatesTags: ['Storage', 'Track', 'Album', 'Artist'],
|
||||
}),
|
||||
deleteTrackFile: build.mutation<void, string>({
|
||||
query: (trackId) => ({ url: `/storage/tracks/${trackId}`, method: 'DELETE' }),
|
||||
query: (trackId) => ({
|
||||
url: `/storage/tracks/${trackId}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: ['Storage', { type: 'Track', id: undefined }],
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
export const { useGetStorageStatsQuery, useScanStorageMutation, useDeleteTrackFileMutation } = storageApi;
|
||||
export const {
|
||||
useGetStorageStatsQuery,
|
||||
useScanStorageMutation,
|
||||
useDeleteTrackFileMutation,
|
||||
} = storageApi;
|
||||
|
||||
@@ -7,7 +7,8 @@ export function getStreamUrl(trackId: string, token: string): string {
|
||||
|
||||
export function getCoverUrl(artUrl: string | undefined): string | undefined {
|
||||
if (!artUrl) return undefined;
|
||||
if (artUrl.startsWith('http://') || artUrl.startsWith('https://')) return artUrl;
|
||||
if (artUrl.startsWith('http://') || artUrl.startsWith('https://'))
|
||||
return artUrl;
|
||||
const base = getApiBaseUrl();
|
||||
return `${base}${artUrl}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user