mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 12:30:12 +02:00
Update frontend API structure
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { ax } from '@/renderer/lib/axios';
|
||||
import { SortOrder } from '@/types';
|
||||
import {
|
||||
AlbumDetailResponse,
|
||||
AlbumListResponse,
|
||||
PaginationParams,
|
||||
} from './types';
|
||||
|
||||
export enum AlbumSort {
|
||||
DATE_ADDED = 'added',
|
||||
DATE_ADDED_REMOTE = 'addedRemote',
|
||||
DATE_RELEASED = 'released',
|
||||
DATE_RELEASED_YEAR = 'year',
|
||||
FAVORITE = 'favorite',
|
||||
NAME = 'name',
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
}
|
||||
|
||||
export type AlbumListParams = PaginationParams & {
|
||||
orderBy: SortOrder;
|
||||
serverFolderId?: string[];
|
||||
serverUrlId?: string;
|
||||
sortBy: AlbumSort;
|
||||
};
|
||||
|
||||
const getAlbumDetail = async (
|
||||
query: { albumId: number; serverId: string },
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
const { data } = await ax.get<AlbumDetailResponse>(
|
||||
`/servers/${query.serverId}/albums/${query.albumId}`,
|
||||
{ signal }
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const getAlbumList = async (
|
||||
query: { serverId: string },
|
||||
params: AlbumListParams,
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
const { data } = await ax.get<AlbumListResponse>(
|
||||
`/servers/${query.serverId}/albums`,
|
||||
{
|
||||
params,
|
||||
signal,
|
||||
}
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const albumsApi = {
|
||||
getAlbumDetail,
|
||||
getAlbumList,
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { api } from '../lib';
|
||||
import { AlbumResponse, AlbumsResponse, BasePaginationRequest } from './types';
|
||||
|
||||
export interface AlbumsRequest extends BasePaginationRequest {
|
||||
orderBy: string;
|
||||
serverFolderIds?: string;
|
||||
sortBy: string;
|
||||
}
|
||||
|
||||
const getAlbum = async (params: { id: number }, signal?: AbortSignal) => {
|
||||
const { data } = await api.get<AlbumResponse>(`/albums/${params.id}`, {
|
||||
signal,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
const getAlbums = async (params: AlbumsRequest, signal?: AbortSignal) => {
|
||||
const { data } = await api.get<AlbumsResponse>(`/albums`, {
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const albumsApi = {
|
||||
getAlbum,
|
||||
getAlbums,
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { albumsApi } from './albums.api';
|
||||
import { authApi } from './auth.api';
|
||||
import { serversApi } from './servers.api';
|
||||
import { usersApi } from './users.api';
|
||||
|
||||
export const api = {
|
||||
albums: albumsApi,
|
||||
auth: authApi,
|
||||
servers: serversApi,
|
||||
users: usersApi,
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
import { useQuery } from 'react-query';
|
||||
import { albumsApi } from '../albumsApi';
|
||||
import { queryKeys } from '../queryKeys';
|
||||
|
||||
export const useAlbum = (albumId: number) => {
|
||||
return useQuery({
|
||||
queryFn: () => albumsApi.getAlbum(albumId),
|
||||
queryKey: queryKeys.album(albumId),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import { AlbumListParams } from './albums.api';
|
||||
|
||||
export const queryKeys = {
|
||||
albums: {
|
||||
detail: (albumId: string) => ['albums', albumId] as const,
|
||||
list: (params: AlbumListParams) => ['albums', 'list', params] as const,
|
||||
root: ['albums'],
|
||||
songList: (albumId: string) => ['albums', albumId, 'songs'] as const,
|
||||
},
|
||||
ping: (url: string) => ['ping', url] as const,
|
||||
servers: {
|
||||
list: () => ['servers', 'list'] as const,
|
||||
},
|
||||
users: {
|
||||
detail: (userId: string) => ['users', userId] as const,
|
||||
list: (params: any) => ['users', 'list', params] as const,
|
||||
root: ['users'],
|
||||
},
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
import { AlbumsRequest } from './albumsApi';
|
||||
|
||||
export const queryKeys = {
|
||||
album: (albumId: number) => ['album', albumId] as const,
|
||||
albums: (params: AlbumsRequest) => ['albums', params] as const,
|
||||
ping: (url: string) => ['ping', url] as const,
|
||||
servers: ['servers'] as const,
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
BaseResponse,
|
||||
NullResponse,
|
||||
Server,
|
||||
ServerType,
|
||||
ServerUrl,
|
||||
} from '@/renderer/api/types';
|
||||
import { ax } from '@/renderer/lib/axios';
|
||||
|
||||
export type ServerListResponse = BaseResponse<Server[]>;
|
||||
|
||||
const getServerList = async (signal?: AbortSignal) => {
|
||||
const { data } = await ax.get<ServerListResponse>('/servers', { signal });
|
||||
return data;
|
||||
};
|
||||
|
||||
export type CreateServerBody = {
|
||||
legacy?: boolean;
|
||||
name: string;
|
||||
password: string;
|
||||
type: ServerType;
|
||||
url: string;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export type ServerResponse = BaseResponse<Server>;
|
||||
|
||||
const createServer = async (body: CreateServerBody) => {
|
||||
const { data } = await ax.post<ServerResponse>('/servers', body);
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateServer = async (
|
||||
query: { serverId: string },
|
||||
body: Partial<CreateServerBody>
|
||||
) => {
|
||||
const { data } = await ax.patch<ServerResponse>(
|
||||
`/servers/${query.serverId}`,
|
||||
body
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export type CreateUrlBody = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type UrlResponse = BaseResponse<ServerUrl>;
|
||||
|
||||
const createUrl = async (query: { serverId: string }, body: CreateUrlBody) => {
|
||||
const { data } = await ax.post<UrlResponse>(
|
||||
`/servers/${query.serverId}/url`,
|
||||
body
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const deleteUrl = async (query: { serverId: string; urlId: string }) => {
|
||||
const { data } = await ax.delete<NullResponse>(
|
||||
`/servers/${query.serverId}/url/${query.urlId}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const enableUrl = async (query: { serverId: string; urlId: string }) => {
|
||||
const { data } = await ax.post<NullResponse>(
|
||||
`/servers/${query.serverId}/url/${query.urlId}/enable`,
|
||||
{}
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const disableUrl = async (query: { serverId: string; urlId: string }) => {
|
||||
const { data } = await ax.post<NullResponse>(
|
||||
`/servers/${query.serverId}/url/${query.urlId}/disable`,
|
||||
{}
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const serversApi = {
|
||||
createServer,
|
||||
createUrl,
|
||||
deleteUrl,
|
||||
disableUrl,
|
||||
enableUrl,
|
||||
getServerList,
|
||||
updateServer,
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { api } from '../lib';
|
||||
|
||||
const getServers = async () => {
|
||||
const { data } = await api.get<any[]>('/servers');
|
||||
return data;
|
||||
};
|
||||
|
||||
const createServer = async (body: {
|
||||
name: string;
|
||||
remoteUserId: string;
|
||||
token: string;
|
||||
url: string;
|
||||
username: string;
|
||||
}) => {
|
||||
const { data } = await api.post<any>('/servers', body);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const serversApi = {
|
||||
createServer,
|
||||
getServers,
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
import axios from 'axios';
|
||||
import md5 from 'md5';
|
||||
import { ServerType } from '@/renderer/api/types';
|
||||
import { randomString } from '@/renderer/utils';
|
||||
|
||||
type JFAuthenticate = {
|
||||
AccessToken: string;
|
||||
ServerId: string;
|
||||
SessionInfo: any;
|
||||
User: any;
|
||||
};
|
||||
|
||||
export const jfAuthenticate = async (options: {
|
||||
password: string;
|
||||
url: string;
|
||||
username: string;
|
||||
}) => {
|
||||
const { password, url, username } = options;
|
||||
const cleanServerUrl = url.replace(/\/$/, '');
|
||||
|
||||
const { data } = await axios.post<JFAuthenticate>(
|
||||
`${cleanServerUrl}/users/authenticatebyname`,
|
||||
{ pw: password, username },
|
||||
{
|
||||
headers: {
|
||||
'X-Emby-Authorization': `MediaBrowser Client="Sonixd", Device="PC", DeviceId="Sonixd", Version="1.0.0-alpha1"`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
type NDAuthenticate = {
|
||||
id: string;
|
||||
isAdmin: boolean;
|
||||
name: string;
|
||||
subsonicSalt: string;
|
||||
subsonicToken: string;
|
||||
token: string;
|
||||
username: string;
|
||||
};
|
||||
|
||||
const ndAuthenticate = async (options: {
|
||||
password: string;
|
||||
url: string;
|
||||
username: string;
|
||||
}) => {
|
||||
const { password, url, username } = options;
|
||||
const cleanServerUrl = url.replace(/\/$/, '');
|
||||
|
||||
const { data } = await axios.post<NDAuthenticate>(
|
||||
`${cleanServerUrl}/auth/login`,
|
||||
{ password, username }
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const ssAuthenticate = async (options: {
|
||||
legacy?: boolean;
|
||||
password: string;
|
||||
url: string;
|
||||
username: string;
|
||||
}) => {
|
||||
let token;
|
||||
|
||||
const cleanServerUrl = options.url.replace(/\/$/, '');
|
||||
|
||||
if (options.legacy) {
|
||||
token = `u=${options.username}&p=${options.password}`;
|
||||
} else {
|
||||
const salt = randomString();
|
||||
const hash = md5(options.password + salt);
|
||||
token = `u=${options.username}&s=${salt}&t=${hash}`;
|
||||
}
|
||||
|
||||
const { data } = await axios.get(
|
||||
`${cleanServerUrl}/rest/ping.view?v=1.13.0&c=sonixd&f=json&${token}`
|
||||
);
|
||||
|
||||
return { token, ...data };
|
||||
};
|
||||
|
||||
export const remoteServerLogin = async (options: {
|
||||
legacy?: boolean;
|
||||
password: string;
|
||||
type: ServerType;
|
||||
url: string;
|
||||
username: string;
|
||||
}) => {
|
||||
if (options.type === ServerType.JELLYFIN) {
|
||||
try {
|
||||
const res = await jfAuthenticate({
|
||||
password: options.password,
|
||||
url: options.url,
|
||||
username: options.username,
|
||||
});
|
||||
|
||||
return {
|
||||
remoteUserId: res.User.Id,
|
||||
token: res.AccessToken,
|
||||
type: ServerType.JELLYFIN,
|
||||
url: options.url,
|
||||
username: options.username,
|
||||
};
|
||||
} catch (err: any) {
|
||||
return { message: err.message, type: 'error' };
|
||||
}
|
||||
}
|
||||
|
||||
if (options.type === ServerType.SUBSONIC) {
|
||||
const res = await ssAuthenticate({
|
||||
legacy: options.legacy,
|
||||
password: options.password,
|
||||
url: options.url,
|
||||
username: options.username,
|
||||
});
|
||||
|
||||
if (res.status === 'failed') {
|
||||
return {
|
||||
message: 'Could not validate username and password',
|
||||
type: 'error',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
remoteUserId: '',
|
||||
token: res.token,
|
||||
type: ServerType.SUBSONIC,
|
||||
url: options.url,
|
||||
username: options.username,
|
||||
};
|
||||
}
|
||||
|
||||
if (options.type === ServerType.NAVIDROME) {
|
||||
try {
|
||||
const res = await ndAuthenticate({
|
||||
password: options.password,
|
||||
url: options.url,
|
||||
username: options.username,
|
||||
});
|
||||
|
||||
return {
|
||||
remoteUserId: res.id,
|
||||
token: `u=${res.name}&s=${res.subsonicSalt}&t=${res.subsonicToken}`,
|
||||
// token: res.token,
|
||||
type: ServerType.NAVIDROME,
|
||||
url: options.url,
|
||||
username: options.username,
|
||||
};
|
||||
} catch (err: any) {
|
||||
return { message: err.message, type: 'error' };
|
||||
}
|
||||
}
|
||||
|
||||
return { message: 'Not found', type: 'error' };
|
||||
};
|
||||
+208
-32
@@ -1,4 +1,42 @@
|
||||
import { Album } from '../../types';
|
||||
export enum ServerType {
|
||||
JELLYFIN = 'JELLYFIN',
|
||||
NAVIDROME = 'NAVIDROME',
|
||||
SUBSONIC = 'SUBSONIC',
|
||||
}
|
||||
|
||||
export enum ServerPermissionType {
|
||||
ADMIN = 'ADMIN',
|
||||
EDITOR = 'EDITOR',
|
||||
VIEWER = 'VIEWER',
|
||||
}
|
||||
|
||||
export enum ExternalSource {
|
||||
LASTFM = 'LASTFM',
|
||||
MUSICBRAINZ = 'MUSICBRAINZ',
|
||||
SPOTIFY = 'SPOTIFY',
|
||||
THEAUDIODB = 'THEAUDIODB',
|
||||
}
|
||||
|
||||
export enum ExternalType {
|
||||
ID = 'ID',
|
||||
LINK = 'LINK',
|
||||
}
|
||||
|
||||
export enum ImageType {
|
||||
BACKDROP = 'BACKDROP',
|
||||
LOGO = 'LOGO',
|
||||
PRIMARY = 'PRIMARY',
|
||||
SCREENSHOT = 'SCREENSHOT',
|
||||
}
|
||||
|
||||
export enum TaskType {
|
||||
FULL_SCAN = 'FULL_SCAN',
|
||||
LASTFM = 'LASTFM',
|
||||
MUSICBRAINZ = 'MUSICBRAINZ',
|
||||
QUICK_SCAN = 'QUICK_SCAN',
|
||||
REFRESH = 'REFRESH',
|
||||
SPOTIFY = 'SPOTIFY',
|
||||
}
|
||||
|
||||
export interface BaseResponse<T> {
|
||||
data: T;
|
||||
@@ -20,45 +58,108 @@ export interface BasePaginatedResponse<T> {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export interface BasePaginationRequest {
|
||||
export type ApiError = {
|
||||
error: {
|
||||
message: string;
|
||||
path: string;
|
||||
trace: string[];
|
||||
};
|
||||
response: string;
|
||||
statusCode: number;
|
||||
};
|
||||
|
||||
export type NullResponse = BaseResponse<null>;
|
||||
|
||||
export type PaginationParams = {
|
||||
skip: number;
|
||||
take: number;
|
||||
};
|
||||
|
||||
export enum SortOrder {
|
||||
ASC = 'asc',
|
||||
DESC = 'desc',
|
||||
}
|
||||
|
||||
export type ServerResponse = {
|
||||
export type Server = {
|
||||
createdAt: string;
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
remoteUserId: string;
|
||||
serverFolder?: ServerFolderResponse[];
|
||||
serverType: string;
|
||||
token: string;
|
||||
serverFolders?: RelatedServerFolder[];
|
||||
serverPermissions?: RelatedServerPermission[];
|
||||
serverUrls?: RelatedServerUrl[];
|
||||
token?: string;
|
||||
type: ServerType;
|
||||
updatedAt: string;
|
||||
url: string;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export type ServerFolderResponse = {
|
||||
createdAt: string;
|
||||
enabled: boolean;
|
||||
id: number;
|
||||
isPublic: boolean;
|
||||
export type RelatedServerFolder = {
|
||||
id: string;
|
||||
lastScannedAt: string | null;
|
||||
name: string;
|
||||
remoteId: string;
|
||||
serverId: number;
|
||||
};
|
||||
|
||||
export type ServerFolder = {
|
||||
createdAt: string;
|
||||
enabled: boolean;
|
||||
id: string;
|
||||
lastScannedAt: string | null;
|
||||
name: string;
|
||||
remoteId: string;
|
||||
serverId: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type ServerUrl = {
|
||||
createdAt: string;
|
||||
id: string;
|
||||
serverId: string;
|
||||
updatedAt: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type RelatedServerUrl = {
|
||||
enabled: boolean;
|
||||
id: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type RelatedServerPermission = {
|
||||
id: string;
|
||||
type: ServerPermissionType;
|
||||
};
|
||||
|
||||
export type User = {
|
||||
createdAt: string;
|
||||
enabled: boolean;
|
||||
id: number;
|
||||
flatServerPermissions: string[];
|
||||
id: string;
|
||||
isAdmin: boolean;
|
||||
password: string;
|
||||
password?: string;
|
||||
serverFolderPermissions: ServerFolderPermission[];
|
||||
serverPermissions: ServerPermission[];
|
||||
updatedAt: string;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export type ServerFolderPermission = {
|
||||
createdAt: string;
|
||||
id: string;
|
||||
serverFolderId: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type ServerPermission = {
|
||||
createdAt: string;
|
||||
id: string;
|
||||
serverId: string;
|
||||
type: ServerPermissionType;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type Login = {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
@@ -70,49 +171,124 @@ export type Ping = {
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type GenreResponse = {
|
||||
export type Genre = {
|
||||
createdAt: string;
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type ArtistResponse = {
|
||||
export type RelatedGenre = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type External = {
|
||||
createdAt: string;
|
||||
id: string;
|
||||
name: string;
|
||||
updatedAt: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type Image = {
|
||||
createdAt: string;
|
||||
id: string;
|
||||
name: string;
|
||||
updatedAt: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type Album = {
|
||||
albumArtists: RelatedArtist[];
|
||||
artists: RelatedArtist[];
|
||||
backdropImageUrl: string | null;
|
||||
createdAt: string;
|
||||
deleted: boolean;
|
||||
genres: RelatedGenre[];
|
||||
id: string;
|
||||
imageUrl: string | null;
|
||||
isFavorite: boolean;
|
||||
name: string;
|
||||
rating: number | null;
|
||||
releaseDate: string | null;
|
||||
releaseYear: number | null;
|
||||
remoteCreatedAt: string;
|
||||
remoteId: string;
|
||||
serverFolders: RelatedServerFolder[];
|
||||
songcount: number;
|
||||
songs?: Song[];
|
||||
sortName: string;
|
||||
type: ServerType;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type Song = {
|
||||
album: Album;
|
||||
artistName: string;
|
||||
artists: RelatedArtist[];
|
||||
bitRate: number;
|
||||
container: string;
|
||||
createdAt: string;
|
||||
deleted: boolean;
|
||||
discNumber: number;
|
||||
duration: number;
|
||||
genres: RelatedGenre[];
|
||||
id: string;
|
||||
imageUrl: string;
|
||||
name: string;
|
||||
releaseDate: string;
|
||||
releaseYear: string;
|
||||
remoteCreatedAt: string;
|
||||
remoteId: string;
|
||||
serverFolderId: string;
|
||||
serverId: string;
|
||||
streamUrl: string;
|
||||
trackNumber: number;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type AlbumArtist = {
|
||||
biography: string | null;
|
||||
createdAt: string;
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
remoteCreatedAt: string | null;
|
||||
remoteId: string;
|
||||
serverFolderId: number;
|
||||
serverFolderId: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type ExternalResponse = {
|
||||
createdAt: string;
|
||||
id: number;
|
||||
export type RelatedAlbumArtist = {
|
||||
id: string;
|
||||
name: string;
|
||||
updatedAt: string;
|
||||
url: string;
|
||||
remoteId: string;
|
||||
};
|
||||
|
||||
export type ImageResponse = {
|
||||
export type Artist = {
|
||||
biography: string | null;
|
||||
createdAt: string;
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
remoteCreatedAt: string | null;
|
||||
remoteId: string;
|
||||
serverFolderId: string;
|
||||
updatedAt: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type RelatedArtist = {
|
||||
id: string;
|
||||
name: string;
|
||||
remoteId: string;
|
||||
};
|
||||
|
||||
export type PingResponse = BaseResponse<Ping>;
|
||||
|
||||
export type LoginResponse = BaseResponse<Login>;
|
||||
|
||||
export type UserResponse = BaseResponse<User>;
|
||||
export type AlbumDetailResponse = BaseResponse<Album>;
|
||||
|
||||
export type AlbumResponse = BaseResponse<Album>;
|
||||
|
||||
export type AlbumsResponse = BasePaginatedResponse<Album[]>;
|
||||
export type AlbumListResponse = BasePaginatedResponse<Album[]>;
|
||||
|
||||
export type Count = {
|
||||
artists?: number;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { BaseResponse, User } from '@/renderer/api/types';
|
||||
import { ax } from '@/renderer/lib/axios';
|
||||
|
||||
export type UserDetailResponse = BaseResponse<User>;
|
||||
export type UserListResponse = BaseResponse<User[]>;
|
||||
|
||||
const getUserDetail = async (query: { userId: string }) => {
|
||||
const { data } = await ax.get<UserDetailResponse>(`/users/${query.userId}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
const getUserList = async () => {
|
||||
const { data } = await ax.get<UserListResponse>('/users');
|
||||
return data;
|
||||
};
|
||||
|
||||
export const usersApi = {
|
||||
getUserDetail,
|
||||
getUserList,
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
import { api } from '../lib';
|
||||
import { UserResponse } from './types';
|
||||
|
||||
const getUsers = async () => {
|
||||
const { data } = await api.get<UserResponse>('/users');
|
||||
return data;
|
||||
};
|
||||
|
||||
export const usersApi = {
|
||||
getUsers,
|
||||
};
|
||||
Reference in New Issue
Block a user