2023-05-18 13:25:19 +02:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) Contributors to the Suwayomi project
|
|
|
|
|
*
|
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
|
*/
|
|
|
|
|
|
2023-06-04 00:30:48 +02:00
|
|
|
import { AxiosInstance, AxiosRequestConfig } from 'axios';
|
2023-06-08 13:40:50 +02:00
|
|
|
import useSWR, { Middleware, SWRConfiguration, SWRResponse } from 'swr';
|
2023-05-18 13:25:19 +02:00
|
|
|
import useSWRInfinite, { SWRInfiniteConfiguration, SWRInfiniteResponse } from 'swr/infinite';
|
2023-09-01 20:21:59 +02:00
|
|
|
import {
|
|
|
|
|
FetchResult,
|
|
|
|
|
MutationHookOptions,
|
|
|
|
|
MutationOptions,
|
|
|
|
|
MutationTuple,
|
|
|
|
|
QueryHookOptions,
|
|
|
|
|
QueryResult,
|
|
|
|
|
TypedDocumentNode,
|
|
|
|
|
useMutation,
|
|
|
|
|
useQuery,
|
|
|
|
|
} from '@apollo/client';
|
|
|
|
|
import { OperationVariables } from '@apollo/client/core';
|
2023-05-18 13:25:19 +02:00
|
|
|
import {
|
|
|
|
|
BackupValidationResult,
|
|
|
|
|
ICategory,
|
|
|
|
|
IChapter,
|
|
|
|
|
IManga,
|
|
|
|
|
IMangaChapter,
|
|
|
|
|
IncludeInGlobalUpdate,
|
|
|
|
|
ISourceFilters,
|
|
|
|
|
IUpdateStatus,
|
|
|
|
|
PaginatedList,
|
2023-05-20 13:12:23 +02:00
|
|
|
PaginatedMangaList,
|
2023-05-18 13:25:19 +02:00
|
|
|
SourcePreferences,
|
|
|
|
|
SourceSearchResult,
|
2023-08-15 13:54:29 +02:00
|
|
|
} from '@/typings.ts';
|
2023-08-15 13:54:55 +02:00
|
|
|
import { HttpMethod as DefaultHttpMethod, IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts';
|
2023-08-15 13:54:29 +02:00
|
|
|
import storage from '@/util/localStorage.tsx';
|
2023-09-01 20:21:59 +02:00
|
|
|
import { GraphQLClient } from '@/lib/requests/client/GraphQLClient.ts';
|
2023-09-01 20:24:40 +02:00
|
|
|
import {
|
2023-09-01 20:26:06 +02:00
|
|
|
CheckForServerUpdatesQuery,
|
|
|
|
|
CheckForServerUpdatesQueryVariables,
|
2023-09-01 20:36:40 +02:00
|
|
|
DeleteDownloadedChapterMutation,
|
|
|
|
|
DeleteDownloadedChapterMutationVariables,
|
|
|
|
|
DeleteDownloadedChaptersMutation,
|
|
|
|
|
DeleteDownloadedChaptersMutationVariables,
|
2023-09-01 20:25:46 +02:00
|
|
|
GetAboutQuery,
|
|
|
|
|
GetAboutQueryVariables,
|
2023-09-01 20:28:23 +02:00
|
|
|
GetExtensionsFetchMutation,
|
|
|
|
|
GetExtensionsFetchMutationVariables,
|
|
|
|
|
GetExtensionsQuery,
|
|
|
|
|
GetExtensionsQueryVariables,
|
2023-09-01 20:24:40 +02:00
|
|
|
GetGlobalMetadatasQuery,
|
|
|
|
|
GetGlobalMetadatasQueryVariables,
|
2023-09-01 20:29:19 +02:00
|
|
|
GetSourceQuery,
|
|
|
|
|
GetSourceQueryVariables,
|
|
|
|
|
GetSourcesQuery,
|
|
|
|
|
GetSourcesQueryVariables,
|
2023-09-01 20:28:23 +02:00
|
|
|
InstallExternalExtensionMutation,
|
|
|
|
|
InstallExternalExtensionMutationVariables,
|
2023-09-01 20:36:40 +02:00
|
|
|
SetChapterMetadataMutation,
|
|
|
|
|
SetChapterMetadataMutationVariables,
|
2023-09-01 20:24:40 +02:00
|
|
|
SetGlobalMetadataMutation,
|
|
|
|
|
SetGlobalMetadataMutationVariables,
|
2023-09-01 20:33:35 +02:00
|
|
|
SetMangaMetadataMutation,
|
2023-09-01 20:36:40 +02:00
|
|
|
UpdateChapterMutation,
|
|
|
|
|
UpdateChapterMutationVariables,
|
|
|
|
|
UpdateChapterPatchInput,
|
|
|
|
|
UpdateChaptersMutation,
|
|
|
|
|
UpdateChaptersMutationVariables,
|
2023-09-01 20:28:23 +02:00
|
|
|
UpdateExtensionMutation,
|
|
|
|
|
UpdateExtensionMutationVariables,
|
|
|
|
|
UpdateExtensionPatchInput,
|
2023-09-01 20:33:35 +02:00
|
|
|
UpdateMangaCategoriesMutation,
|
|
|
|
|
UpdateMangaCategoriesMutationVariables,
|
|
|
|
|
UpdateMangaMutation,
|
|
|
|
|
UpdateMangaMutationVariables,
|
|
|
|
|
UpdateMangaPatchInput,
|
2023-09-01 20:24:40 +02:00
|
|
|
} from '@/lib/graphql/generated/graphql.ts';
|
|
|
|
|
import { GET_GLOBAL_METADATA, GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts';
|
|
|
|
|
import { SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts';
|
2023-09-01 20:26:06 +02:00
|
|
|
import { CHECK_FOR_SERVER_UPDATES, GET_ABOUT } from '@/lib/graphql/queries/ServerInfoQuery.ts';
|
2023-09-01 20:28:23 +02:00
|
|
|
import { GET_EXTENSION, GET_EXTENSIONS } from '@/lib/graphql/queries/ExtensionQuery.ts';
|
|
|
|
|
import {
|
|
|
|
|
GET_EXTENSIONS_FETCH,
|
|
|
|
|
INSTALL_EXTERNAL_EXTENSION,
|
|
|
|
|
UPDATE_EXTENSION,
|
|
|
|
|
} from '@/lib/graphql/mutations/ExtensionMutation.ts';
|
2023-09-01 20:29:19 +02:00
|
|
|
import { GET_SOURCE, GET_SOURCES } from '@/lib/graphql/queries/SourceQuery.ts';
|
2023-09-01 20:33:35 +02:00
|
|
|
import { SET_MANGA_METADATA, UPDATE_MANGA, UPDATE_MANGA_CATEGORIES } from '@/lib/graphql/mutations/MangaMutation.ts';
|
|
|
|
|
import { GET_MANGA, GET_MANGAS } from '@/lib/graphql/queries/MangaQuery.ts';
|
|
|
|
|
import { GET_CATEGORIES, GET_CATEGORY, GET_CATEGORY_MANGAS } from '@/lib/graphql/queries/CategoryQuery.ts';
|
|
|
|
|
import { GET_SOURCE_MANGAS_FETCH } from '@/lib/graphql/mutations/SourceMutation.ts';
|
2023-09-01 20:36:40 +02:00
|
|
|
import { DELETE_DOWNLOADED_CHAPTER, DELETE_DOWNLOADED_CHAPTERS } from '@/lib/graphql/mutations/DownloaderMutation.ts';
|
|
|
|
|
import { GET_CHAPTER, GET_CHAPTERS } from '@/lib/graphql/queries/ChapterQuery.ts';
|
|
|
|
|
import { SET_CHAPTER_METADATA, UPDATE_CHAPTER, UPDATE_CHAPTERS } from '@/lib/graphql/mutations/ChapterMutation.ts';
|
2023-05-18 13:25:19 +02:00
|
|
|
|
|
|
|
|
enum SWRHttpMethod {
|
|
|
|
|
SWR_GET,
|
|
|
|
|
SWR_GET_INFINITE,
|
|
|
|
|
SWR_POST,
|
2023-05-22 14:04:05 +02:00
|
|
|
SWR_POST_INFINITE,
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:21:59 +02:00
|
|
|
enum GQLMethod {
|
|
|
|
|
USE_QUERY = 'USE_QUERY',
|
|
|
|
|
USE_MUTATION = 'USE_MUTATION',
|
|
|
|
|
MUTATION = 'MUTATION',
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
type HttpMethodType = DefaultHttpMethod | SWRHttpMethod;
|
|
|
|
|
const HttpMethod = { ...SWRHttpMethod, ...DefaultHttpMethod };
|
|
|
|
|
|
|
|
|
|
type RequestOption = { doOnlineFetch?: boolean };
|
|
|
|
|
|
|
|
|
|
type CustomSWROptions<Data> = {
|
|
|
|
|
skipRequest?: boolean;
|
|
|
|
|
getEndpoint?: (index: number, previousData: Data | null) => string | null;
|
2023-06-08 13:40:50 +02:00
|
|
|
disableCache?: boolean;
|
2023-05-18 13:25:19 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type SWROptions<Data = any, Error = any> = SWRConfiguration<Data, Error> & CustomSWROptions<Data>;
|
|
|
|
|
type SWRInfiniteOptions<Data = any, Error = any> = SWRInfiniteConfiguration<Data, Error> & CustomSWROptions<Data>;
|
|
|
|
|
|
2023-05-28 02:18:23 +02:00
|
|
|
type SWRInfiniteResponseLoadInfo = {
|
|
|
|
|
isInitialLoad: boolean;
|
|
|
|
|
isLoadMore: boolean;
|
|
|
|
|
};
|
2023-05-23 13:27:38 +02:00
|
|
|
type AbortableRequest = { abortRequest: AbortController['abort'] };
|
2023-06-04 00:30:48 +02:00
|
|
|
export type AbortableAxiosResponse<Data = any> = { response: Promise<Data> } & AbortableRequest;
|
2023-05-28 12:16:06 +02:00
|
|
|
export type AbortableSWRResponse<Data = any, Error = any> = SWRResponse<Data, Error> & AbortableRequest;
|
|
|
|
|
export type AbortableSWRInfiniteResponse<Data = any, Error = any> = SWRInfiniteResponse<Data, Error> &
|
2023-05-28 02:18:23 +02:00
|
|
|
AbortableRequest &
|
|
|
|
|
SWRInfiniteResponseLoadInfo;
|
2023-05-23 13:27:38 +02:00
|
|
|
|
2023-09-01 20:21:59 +02:00
|
|
|
type AbortableApolloUseQueryResponse<
|
|
|
|
|
Data = any,
|
|
|
|
|
Variables extends OperationVariables = OperationVariables,
|
|
|
|
|
> = QueryResult<Data, Variables> & AbortableRequest;
|
|
|
|
|
type AbortableApolloUseMutationResponse<Data = any, Variables extends OperationVariables = OperationVariables> = [
|
|
|
|
|
MutationTuple<Data, Variables>[0],
|
|
|
|
|
MutationTuple<Data, Variables>[1] & AbortableRequest,
|
|
|
|
|
];
|
|
|
|
|
type AbortableApolloMutationResponse<Data = any> = { response: Promise<FetchResult<Data>> } & AbortableRequest;
|
|
|
|
|
|
2023-06-08 13:40:50 +02:00
|
|
|
const isLoadingMore = (swrResult: SWRInfiniteResponse): boolean => {
|
|
|
|
|
const isNextPageMissing = !!swrResult.data && typeof swrResult.data[swrResult.size - 1] === 'undefined';
|
2023-06-16 00:14:22 +02:00
|
|
|
const isRequestActive = swrResult.isValidating;
|
2023-06-08 13:40:50 +02:00
|
|
|
// SWR "isLoading" state is only updated for the first load, for every subsequent load it's "false"
|
2023-06-16 00:14:22 +02:00
|
|
|
return !swrResult.isLoading && swrResult.size > 0 && isNextPageMissing && isRequestActive;
|
2023-06-08 13:40:50 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const disableSwrInfiniteCache: Middleware = (useSWRNext) => (key, fetcher, config) => {
|
|
|
|
|
const swr = useSWRNext(key, fetcher, config) as unknown as SWRInfiniteResponse;
|
|
|
|
|
const { size, data, isLoading, isValidating } = swr;
|
|
|
|
|
const isActuallyValidating = !isLoading && !isLoadingMore(swr) && isValidating;
|
|
|
|
|
return {
|
|
|
|
|
...swr,
|
|
|
|
|
isLoading: isActuallyValidating ? true : isLoading,
|
|
|
|
|
data: isActuallyValidating ? undefined : data,
|
|
|
|
|
size: isActuallyValidating ? 1 : size,
|
|
|
|
|
} as SWRResponse;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const disableSwrCache: Middleware = (useSWRNext) => (key, fetcher, config) => {
|
|
|
|
|
const swr = useSWRNext(key, fetcher, config);
|
|
|
|
|
const { data, isLoading, isValidating } = swr;
|
|
|
|
|
return { ...swr, isLoading: isValidating ? true : isLoading, data: isValidating ? undefined : data };
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
// the following endpoints have not been implemented:
|
|
|
|
|
// - PUT /api/v1/manga/{mangaId}/chapter/{chapterIndex} - modify chapter # PATCH endpoint used instead
|
|
|
|
|
// - POST /api/v1/backup/import - import backup # "import backup file" endpoint used instead
|
|
|
|
|
// - POST /api/v1/backup/validate - validate backup # "validate backup file" endpoint used instead
|
|
|
|
|
// - GET /api/v1/backup/export - export backup # no function needed, url gets called via link triggering the download
|
|
|
|
|
export class RequestManager {
|
2023-06-04 00:50:58 +02:00
|
|
|
public static readonly API_VERSION = '/api/v1/';
|
2023-05-18 13:25:19 +02:00
|
|
|
|
2023-09-01 20:21:59 +02:00
|
|
|
private readonly graphQLClient = new GraphQLClient();
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
private readonly restClient: RestClient = new RestClient();
|
|
|
|
|
|
|
|
|
|
public getClient(): IRestClient {
|
|
|
|
|
return this.restClient;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:25:48 +02:00
|
|
|
public updateClient(config: Partial<AxiosInstance['defaults']>): void {
|
2023-05-18 13:25:19 +02:00
|
|
|
this.restClient.updateConfig(config);
|
2023-09-01 20:21:59 +02:00
|
|
|
this.graphQLClient.updateConfig({ uri: config.baseURL });
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getBaseUrl(): string {
|
|
|
|
|
return this.restClient.getClient().defaults.baseURL!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getWebSocketBaseUrl(): string {
|
|
|
|
|
return this.getBaseUrl().replace('http', 'ws');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getValidWebSocketUrl(path: string, apiVersion = RequestManager.API_VERSION): string {
|
|
|
|
|
return `${this.getWebSocketBaseUrl()}${apiVersion}${path}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getUpdateWebSocket(): WebSocket {
|
|
|
|
|
return new WebSocket(this.getValidWebSocketUrl('update'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getDownloadWebSocket(): WebSocket {
|
|
|
|
|
return new WebSocket(this.getValidWebSocketUrl('downloads'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getValidUrlFor(endpoint: string, apiVersion: string = RequestManager.API_VERSION): string {
|
|
|
|
|
return `${this.getBaseUrl()}${apiVersion}${endpoint}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getValidImgUrlFor(imageUrl: string, apiVersion: string = ''): string {
|
|
|
|
|
const useCache = storage.getItem('useCache', true);
|
|
|
|
|
const useCacheQuery = `?useCache=${useCache}`;
|
|
|
|
|
// server provided image urls already contain the api version
|
|
|
|
|
return `${this.getValidUrlFor(imageUrl, apiVersion)}${useCacheQuery}`;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:21:59 +02:00
|
|
|
private doRequestNew<Data, Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
method: GQLMethod.USE_QUERY,
|
|
|
|
|
operation: TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables,
|
|
|
|
|
options?: Partial<QueryHookOptions<Data, Variables>>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<Data, Variables>;
|
|
|
|
|
|
|
|
|
|
private doRequestNew<Data, Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
method: GQLMethod.USE_MUTATION,
|
|
|
|
|
operation: TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables | undefined,
|
|
|
|
|
options?: Partial<MutationHookOptions<Data, Variables>>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<Data, Variables>;
|
|
|
|
|
|
|
|
|
|
private doRequestNew<Data, Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
method: GQLMethod.MUTATION,
|
|
|
|
|
operation: TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables,
|
|
|
|
|
options?: Partial<MutationOptions<Data, Variables>>,
|
|
|
|
|
): AbortableApolloMutationResponse<Data>;
|
|
|
|
|
|
|
|
|
|
private doRequestNew<Data, Variables extends OperationVariables = OperationVariables>(
|
|
|
|
|
method: GQLMethod,
|
|
|
|
|
operation: TypedDocumentNode<Data, Variables>,
|
|
|
|
|
variables: Variables,
|
|
|
|
|
options?:
|
|
|
|
|
| QueryHookOptions<Data, Variables>
|
|
|
|
|
| MutationHookOptions<Data, Variables>
|
|
|
|
|
| MutationOptions<Data, Variables>,
|
|
|
|
|
):
|
|
|
|
|
| AbortableApolloUseQueryResponse<Data, Variables>
|
|
|
|
|
| AbortableApolloUseMutationResponse<Data, Variables>
|
|
|
|
|
| AbortableApolloMutationResponse<Data> {
|
|
|
|
|
const abortController = new AbortController();
|
|
|
|
|
const abortRequest = (reason?: any): void => {
|
|
|
|
|
if (!abortController.signal.aborted) {
|
|
|
|
|
abortController.abort(reason);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
switch (method) {
|
|
|
|
|
case GQLMethod.USE_QUERY:
|
|
|
|
|
return {
|
|
|
|
|
...useQuery<Data, Variables>(operation, {
|
|
|
|
|
variables,
|
|
|
|
|
client: this.graphQLClient.client,
|
|
|
|
|
...options,
|
|
|
|
|
context: {
|
|
|
|
|
...options?.context,
|
|
|
|
|
fetchOptions: {
|
|
|
|
|
signal: abortController.signal,
|
|
|
|
|
...options?.context?.fetchOptions,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
abortRequest,
|
|
|
|
|
};
|
|
|
|
|
case GQLMethod.USE_MUTATION:
|
|
|
|
|
// eslint-disable-next-line no-case-declarations
|
|
|
|
|
const mutationResult = useMutation<Data, Variables>(operation, {
|
|
|
|
|
variables,
|
|
|
|
|
client: this.graphQLClient.client,
|
|
|
|
|
...(options as MutationHookOptions<Data, Variables>),
|
|
|
|
|
context: {
|
|
|
|
|
...options?.context,
|
|
|
|
|
fetchOptions: {
|
|
|
|
|
signal: abortController.signal,
|
|
|
|
|
...options?.context?.fetchOptions,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return [mutationResult[0], { ...mutationResult[1], abortRequest }];
|
|
|
|
|
case GQLMethod.MUTATION:
|
|
|
|
|
return {
|
|
|
|
|
response: this.graphQLClient.client.mutate<Data, Variables>({
|
|
|
|
|
mutation: operation,
|
|
|
|
|
variables,
|
|
|
|
|
...(options as MutationOptions<Data, Variables>),
|
|
|
|
|
context: {
|
|
|
|
|
...options?.context,
|
|
|
|
|
fetchOptions: {
|
|
|
|
|
signal: abortController.signal,
|
|
|
|
|
...options?.context?.fetchOptions,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
abortRequest,
|
|
|
|
|
};
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`unexpected GQLRequest type "${method}"`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
private useSwr<
|
|
|
|
|
Data = any,
|
|
|
|
|
ErrorResponse = any,
|
|
|
|
|
OptionsSWR extends SWROptions<Data, ErrorResponse> = SWROptions<Data, ErrorResponse>,
|
|
|
|
|
>(
|
|
|
|
|
url: string,
|
|
|
|
|
httpMethod: DefaultHttpMethod,
|
|
|
|
|
{
|
|
|
|
|
data,
|
|
|
|
|
axiosOptions,
|
|
|
|
|
swrOptions,
|
|
|
|
|
}: {
|
|
|
|
|
data?: Data;
|
|
|
|
|
axiosOptions?: AxiosRequestConfig;
|
|
|
|
|
swrOptions?: OptionsSWR;
|
|
|
|
|
} = {},
|
|
|
|
|
): SWRResponse<Data, ErrorResponse> {
|
2023-06-08 13:40:50 +02:00
|
|
|
const { skipRequest, disableCache, ...swrConfig } = swrOptions ?? {};
|
2023-05-18 13:25:19 +02:00
|
|
|
|
|
|
|
|
// in case "null" gets passed as the url, SWR won't do the request
|
|
|
|
|
return useSWR(skipRequest ? null : url, {
|
|
|
|
|
fetcher: (path: string) => this.restClient.fetcher(path, { data, httpMethod, config: axiosOptions }),
|
2023-06-08 13:40:50 +02:00
|
|
|
use: disableCache ? [disableSwrCache] : undefined,
|
2023-05-18 13:25:19 +02:00
|
|
|
...swrConfig,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-20 13:12:23 +02:00
|
|
|
public useSwrInfinite<
|
|
|
|
|
Data = any,
|
|
|
|
|
ErrorResponse = any,
|
|
|
|
|
OptionsSWR extends SWRInfiniteOptions<Data, ErrorResponse> = SWRInfiniteOptions<Data, ErrorResponse>,
|
|
|
|
|
>(
|
2023-05-18 13:25:19 +02:00
|
|
|
getEndpoint: Required<CustomSWROptions<Data>>['getEndpoint'],
|
2023-05-22 14:04:05 +02:00
|
|
|
httpMethod: DefaultHttpMethod,
|
|
|
|
|
{
|
|
|
|
|
data,
|
|
|
|
|
axiosOptions,
|
|
|
|
|
swrOptions,
|
|
|
|
|
}: { data?: any; axiosOptions?: AxiosRequestConfig; swrOptions?: OptionsSWR } = {},
|
2023-05-28 02:18:23 +02:00
|
|
|
): SWRInfiniteResponse<Data, ErrorResponse> & SWRInfiniteResponseLoadInfo {
|
2023-06-08 13:40:50 +02:00
|
|
|
const { skipRequest, disableCache, ...swrConfig } = swrOptions ?? {};
|
2023-05-20 13:12:23 +02:00
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
// useSWRInfinite will (by default) revalidate the first page, to check if the other pages have to be revalidated as well
|
2023-05-28 02:18:23 +02:00
|
|
|
const swrResult = useSWRInfinite<Data, ErrorResponse>(
|
2023-05-18 13:25:19 +02:00
|
|
|
(index, previousData) => {
|
|
|
|
|
const pageEndpoint = getEndpoint(index, previousData);
|
2023-05-20 13:12:23 +02:00
|
|
|
return pageEndpoint !== null && !skipRequest ? this.getValidUrlFor(pageEndpoint) : null;
|
2023-05-18 13:25:19 +02:00
|
|
|
},
|
|
|
|
|
{
|
2023-05-22 14:04:05 +02:00
|
|
|
fetcher: (path: string) => this.restClient.fetcher(path, { httpMethod, data, config: axiosOptions }),
|
2023-06-08 13:40:50 +02:00
|
|
|
use: disableCache ? [disableSwrInfiniteCache] : undefined,
|
2023-05-20 13:12:23 +02:00
|
|
|
...swrConfig,
|
2023-05-18 13:25:19 +02:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-05-28 02:18:23 +02:00
|
|
|
const customSwrResult = {
|
|
|
|
|
...swrResult,
|
|
|
|
|
isInitialLoad: swrResult.isLoading,
|
2023-06-08 13:40:50 +02:00
|
|
|
isLoadMore: isLoadingMore(swrResult),
|
2023-05-18 13:25:19 +02:00
|
|
|
};
|
2023-05-28 02:18:23 +02:00
|
|
|
customSwrResult.isLoading = customSwrResult.isInitialLoad || customSwrResult.isLoadMore;
|
|
|
|
|
|
|
|
|
|
return customSwrResult;
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Performs the actual server request.
|
|
|
|
|
*
|
|
|
|
|
* In case {@link HttpMethod.GET_SWR} gets passed, the "useSWR" hook gets called.
|
|
|
|
|
* In case {@link HttpMethod.GET_SWR_INFINITE} gets passed, the "useSWRInfinite" hook gets called.
|
|
|
|
|
* In that case "getEndpoint" has to be passed, which gets used over "endpoint"
|
|
|
|
|
*
|
|
|
|
|
* Pass "skipRequest" to make SWR skip sending the request to the server.
|
|
|
|
|
* In case "formData" is passed, "data" gets ignored.
|
|
|
|
|
*/
|
|
|
|
|
private doRequest<
|
2023-05-23 13:27:38 +02:00
|
|
|
Result extends AbortableAxiosResponse | AbortableSWRResponse | AbortableSWRInfiniteResponse,
|
2023-05-18 13:25:19 +02:00
|
|
|
OptionsSWR extends SWROptions | SWRInfiniteOptions,
|
|
|
|
|
>(
|
|
|
|
|
httpMethod: HttpMethodType,
|
|
|
|
|
endpoint: string,
|
|
|
|
|
{
|
|
|
|
|
apiVersion = RequestManager.API_VERSION,
|
|
|
|
|
data: dataToSend,
|
|
|
|
|
formData,
|
|
|
|
|
axiosOptions,
|
|
|
|
|
swrOptions,
|
|
|
|
|
}: {
|
|
|
|
|
apiVersion?: string;
|
|
|
|
|
data?: any;
|
|
|
|
|
formData?: { [key: string]: any };
|
|
|
|
|
axiosOptions?: AxiosRequestConfig;
|
|
|
|
|
swrOptions?: OptionsSWR;
|
|
|
|
|
} = {},
|
|
|
|
|
): Result {
|
|
|
|
|
const url = `${apiVersion}${endpoint}`;
|
|
|
|
|
|
|
|
|
|
let data = dataToSend;
|
|
|
|
|
if (formData) {
|
|
|
|
|
data = new FormData();
|
|
|
|
|
|
|
|
|
|
Object.entries(formData).forEach(([key, value]) => {
|
2023-06-04 00:32:08 +02:00
|
|
|
if (value !== undefined) data.append(key, value); // "append" automatically converts non string or blob values to strings
|
2023-05-18 13:25:19 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
const abortController = new AbortController();
|
|
|
|
|
const abortRequest = (reason?: any): void => {
|
|
|
|
|
if (!abortController.signal.aborted) {
|
|
|
|
|
abortController.abort(reason);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const axiosOptionsWithAbortController = { ...axiosOptions, signal: abortController.signal };
|
2023-05-18 13:25:19 +02:00
|
|
|
switch (httpMethod) {
|
|
|
|
|
case HttpMethod.SWR_GET:
|
2023-05-23 13:27:38 +02:00
|
|
|
return {
|
|
|
|
|
...(this.useSwr(url, HttpMethod.GET, { axiosOptions, swrOptions }) as Result),
|
|
|
|
|
abortRequest,
|
|
|
|
|
};
|
2023-05-18 13:25:19 +02:00
|
|
|
case HttpMethod.SWR_GET_INFINITE:
|
|
|
|
|
// throw TypeError in case options aren't correctly passed
|
2023-05-23 13:27:38 +02:00
|
|
|
return {
|
|
|
|
|
...(this.useSwrInfinite(swrOptions!.getEndpoint!, HttpMethod.GET, {
|
|
|
|
|
axiosOptions: axiosOptionsWithAbortController,
|
|
|
|
|
swrOptions,
|
|
|
|
|
}) as Result),
|
|
|
|
|
abortRequest,
|
|
|
|
|
};
|
2023-05-22 14:04:05 +02:00
|
|
|
case SWRHttpMethod.SWR_POST_INFINITE:
|
2023-05-23 13:27:38 +02:00
|
|
|
return {
|
|
|
|
|
...(this.useSwrInfinite(swrOptions!.getEndpoint!, HttpMethod.POST, {
|
|
|
|
|
data,
|
|
|
|
|
axiosOptions: axiosOptionsWithAbortController,
|
|
|
|
|
swrOptions,
|
|
|
|
|
}) as Result),
|
|
|
|
|
abortRequest,
|
|
|
|
|
};
|
2023-05-18 13:25:19 +02:00
|
|
|
case HttpMethod.SWR_POST:
|
2023-05-23 13:27:38 +02:00
|
|
|
return {
|
|
|
|
|
...(this.useSwr(url, HttpMethod.POST, {
|
|
|
|
|
data,
|
|
|
|
|
axiosOptions: axiosOptionsWithAbortController,
|
|
|
|
|
swrOptions,
|
|
|
|
|
}) as Result),
|
|
|
|
|
controller: abortController,
|
|
|
|
|
};
|
2023-05-18 13:25:19 +02:00
|
|
|
default:
|
2023-05-23 13:27:38 +02:00
|
|
|
return {
|
|
|
|
|
response: this.restClient.fetcher(url, {
|
|
|
|
|
data,
|
|
|
|
|
httpMethod,
|
|
|
|
|
config: axiosOptionsWithAbortController,
|
|
|
|
|
checkResponseIsJson: false,
|
|
|
|
|
}),
|
|
|
|
|
abortRequest,
|
|
|
|
|
} as Result;
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:24:40 +02:00
|
|
|
public useGetGlobalMeta(
|
|
|
|
|
options?: QueryHookOptions<GetGlobalMetadatasQuery, GetGlobalMetadatasQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetGlobalMetadatasQuery, GetGlobalMetadatasQueryVariables> {
|
|
|
|
|
return this.doRequestNew(GQLMethod.USE_QUERY, GET_GLOBAL_METADATAS, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:24:40 +02:00
|
|
|
public setGlobalMetadata(key: string, value: any): AbortableApolloMutationResponse<SetGlobalMetadataMutation> {
|
|
|
|
|
return this.doRequestNew<SetGlobalMetadataMutation, SetGlobalMetadataMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
SET_GLOBAL_METADATA,
|
|
|
|
|
{ input: { meta: { key, value: `${value}` } } },
|
|
|
|
|
{ refetchQueries: [GET_GLOBAL_METADATA, GET_GLOBAL_METADATAS] },
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:25:46 +02:00
|
|
|
public useGetAbout(
|
|
|
|
|
options?: QueryHookOptions<GetAboutQuery, GetAboutQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetAboutQuery, GetAboutQueryVariables> {
|
|
|
|
|
return this.doRequestNew(GQLMethod.USE_QUERY, GET_ABOUT, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:26:06 +02:00
|
|
|
public useCheckForUpdate(
|
|
|
|
|
options?: QueryHookOptions<CheckForServerUpdatesQuery, CheckForServerUpdatesQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<CheckForServerUpdatesQuery, CheckForServerUpdatesQueryVariables> {
|
|
|
|
|
return this.doRequestNew(GQLMethod.USE_QUERY, CHECK_FOR_SERVER_UPDATES, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:28:23 +02:00
|
|
|
public useGetExtensionList(
|
|
|
|
|
options?: QueryHookOptions<GetExtensionsQuery, GetExtensionsQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetExtensionsQuery, GetExtensionsQueryVariables> {
|
|
|
|
|
return this.doRequestNew(GQLMethod.USE_QUERY, GET_EXTENSIONS, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:28:23 +02:00
|
|
|
public useExtensionListFetch(
|
|
|
|
|
options?: MutationHookOptions<GetExtensionsFetchMutation, GetExtensionsFetchMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<GetExtensionsFetchMutation, GetExtensionsFetchMutationVariables> {
|
|
|
|
|
return this.doRequestNew(GQLMethod.USE_MUTATION, GET_EXTENSIONS_FETCH, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:28:23 +02:00
|
|
|
public installExternalExtension(
|
|
|
|
|
extensionFile: File,
|
|
|
|
|
): AbortableApolloMutationResponse<InstallExternalExtensionMutation> {
|
|
|
|
|
return this.doRequestNew<InstallExternalExtensionMutation, InstallExternalExtensionMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
INSTALL_EXTERNAL_EXTENSION,
|
|
|
|
|
{ file: extensionFile },
|
|
|
|
|
{ refetchQueries: [GET_EXTENSION, GET_EXTENSIONS] },
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:28:23 +02:00
|
|
|
public updateExtension(
|
|
|
|
|
id: string,
|
|
|
|
|
patch: UpdateExtensionPatchInput,
|
|
|
|
|
): AbortableApolloMutationResponse<UpdateExtensionMutation> {
|
|
|
|
|
return this.doRequestNew<UpdateExtensionMutation, UpdateExtensionMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_EXTENSION,
|
|
|
|
|
{ input: { id, patch } },
|
|
|
|
|
{ refetchQueries: [GET_EXTENSION, GET_EXTENSIONS, GET_SOURCES] },
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getExtensionIconUrl(extension: string): string {
|
|
|
|
|
return this.getValidImgUrlFor(`extension/icon/${extension}`);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:29:19 +02:00
|
|
|
public useGetSourceList(
|
|
|
|
|
options?: QueryHookOptions<GetSourcesQuery, GetSourcesQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetSourcesQuery, GetSourcesQueryVariables> {
|
|
|
|
|
return this.doRequestNew(GQLMethod.USE_QUERY, GET_SOURCES, {}, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:29:19 +02:00
|
|
|
public useGetSource(
|
|
|
|
|
id: string,
|
|
|
|
|
options?: QueryHookOptions<GetSourceQuery, GetSourceQueryVariables>,
|
|
|
|
|
): AbortableApolloUseQueryResponse<GetSourceQuery, GetSourceQueryVariables> {
|
|
|
|
|
return this.doRequestNew(GQLMethod.USE_QUERY, GET_SOURCE, { id }, options);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetSourcePopularMangas(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
initialPages?: number,
|
2023-05-20 13:12:23 +02:00
|
|
|
swrOptions?: SWRInfiniteOptions<PaginatedMangaList>,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRInfiniteResponse<PaginatedMangaList> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(SWRHttpMethod.SWR_GET_INFINITE, '', {
|
|
|
|
|
swrOptions: {
|
|
|
|
|
getEndpoint: (page, previousData) =>
|
2023-05-20 13:12:23 +02:00
|
|
|
previousData?.hasNextPage ?? true ? `source/${sourceId}/popular/${page + 1}` : null,
|
2023-05-18 13:25:19 +02:00
|
|
|
initialSize: initialPages,
|
|
|
|
|
...swrOptions,
|
|
|
|
|
} as typeof swrOptions,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetSourceLatestMangas(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
initialPages?: number,
|
2023-05-20 13:12:23 +02:00
|
|
|
swrOptions?: SWRInfiniteOptions<PaginatedMangaList>,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRInfiniteResponse<PaginatedMangaList> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(SWRHttpMethod.SWR_GET_INFINITE, '', {
|
|
|
|
|
swrOptions: {
|
|
|
|
|
getEndpoint: (page, previousData) =>
|
2023-05-20 13:12:23 +02:00
|
|
|
previousData?.hasNextPage ?? true ? `source/${sourceId}/latest/${page + 1}` : null,
|
2023-05-18 13:25:19 +02:00
|
|
|
initialSize: initialPages,
|
|
|
|
|
...swrOptions,
|
|
|
|
|
} as typeof swrOptions,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetSourcePreferences(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
swrOptions?: SWROptions<SourcePreferences[]>,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRResponse<SourcePreferences[]> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}/preferences`, { swrOptions });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public setSourcePreferences(sourceId: string, position: number, value: string): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.POST, `source/${sourceId}/preferences`, { data: { position, value } });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetSourceFilters(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
reset?: boolean,
|
|
|
|
|
swrOptions?: SWROptions<ISourceFilters[]>,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRResponse<ISourceFilters[]> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}/filters`, { swrOptions });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public setSourceFilters(sourceId: string, filters: { position: number; state: string }[]): AbortableAxiosResponse {
|
2023-05-22 14:03:50 +02:00
|
|
|
return this.doRequest(HttpMethod.POST, `source/${sourceId}/filters`, { data: filters });
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public resetSourceFilters(sourceId: string): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.GET, `source/${sourceId}/filters?reset=true`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useSourceSearch(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
searchTerm: string,
|
|
|
|
|
initialPages?: number,
|
|
|
|
|
swrOptions?: SWRInfiniteOptions<SourceSearchResult>,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRInfiniteResponse<SourceSearchResult> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_GET_INFINITE, '', {
|
|
|
|
|
swrOptions: {
|
|
|
|
|
getEndpoint: (page, previousData) =>
|
2023-05-20 13:12:23 +02:00
|
|
|
previousData?.hasNextPage ?? true
|
|
|
|
|
? `source/${sourceId}/search?searchTerm=${searchTerm}&pageNum=${page + 1}`
|
2023-05-18 13:25:19 +02:00
|
|
|
: null,
|
|
|
|
|
initialSize: initialPages,
|
|
|
|
|
...swrOptions,
|
|
|
|
|
} as typeof swrOptions,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useSourceQuickSearch(
|
|
|
|
|
sourceId: string,
|
|
|
|
|
searchTerm: string,
|
|
|
|
|
filters: { position: number; state: string }[],
|
|
|
|
|
initialPages?: number,
|
|
|
|
|
swrOptions?: SWRInfiniteOptions<SourceSearchResult>,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRInfiniteResponse<SourceSearchResult> {
|
2023-05-22 14:04:05 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_POST_INFINITE, '', {
|
|
|
|
|
data: { searchTerm, filter: filters },
|
2023-05-18 13:25:19 +02:00
|
|
|
swrOptions: {
|
|
|
|
|
getEndpoint: (page, previousData) =>
|
2023-05-20 13:12:23 +02:00
|
|
|
previousData?.hasNextPage ?? true
|
|
|
|
|
? `source/${sourceId}/quick-search?searchTerm=${searchTerm}&pageNum=${page + 1}`
|
2023-05-18 13:25:19 +02:00
|
|
|
: null,
|
|
|
|
|
initialSize: initialPages,
|
|
|
|
|
...swrOptions,
|
|
|
|
|
} as typeof swrOptions,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetManga(
|
|
|
|
|
mangaId: number | string,
|
|
|
|
|
{ doOnlineFetch, ...swrOptions }: SWROptions<IManga> & RequestOption = {},
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRResponse<IManga> {
|
2023-05-18 13:25:19 +02:00
|
|
|
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
|
|
|
|
|
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}${onlineFetch}`, {
|
|
|
|
|
swrOptions,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-04 00:50:58 +02:00
|
|
|
public getManga(mangaId: number | string, doOnlineFetch?: boolean): AbortableAxiosResponse<IManga> {
|
|
|
|
|
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
|
|
|
|
|
return this.doRequest(HttpMethod.GET, `manga/${mangaId}${onlineFetch}`);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
public useGetFullManga(
|
|
|
|
|
mangaId: number | string,
|
|
|
|
|
{ doOnlineFetch, ...swrOptions }: SWROptions<IManga> & RequestOption = {},
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRResponse<IManga> {
|
2023-05-18 13:25:19 +02:00
|
|
|
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
|
|
|
|
|
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/full${onlineFetch}`, {
|
|
|
|
|
swrOptions,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getMangaThumbnailUrl(mangaId: number): string {
|
|
|
|
|
return this.getValidImgUrlFor(`manga/${mangaId}/thumbnail`);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public useGetMangaCategories(
|
|
|
|
|
mangaId: number,
|
|
|
|
|
swrOptions?: SWROptions<ICategory[]>,
|
|
|
|
|
): AbortableSWRResponse<ICategory[]> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/category`, { swrOptions });
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:33:35 +02:00
|
|
|
public useUpdateMangaCategories(
|
|
|
|
|
options?: MutationHookOptions<UpdateMangaCategoriesMutation, UpdateMangaCategoriesMutationVariables>,
|
|
|
|
|
): AbortableApolloUseMutationResponse<UpdateMangaCategoriesMutation, UpdateMangaCategoriesMutationVariables> {
|
|
|
|
|
return this.doRequestNew(GQLMethod.USE_MUTATION, UPDATE_MANGA_CATEGORIES, undefined, {
|
|
|
|
|
refetchQueries: [GET_MANGA, GET_MANGAS, GET_CATEGORY, GET_CATEGORIES, GET_CATEGORY_MANGAS],
|
|
|
|
|
...options,
|
|
|
|
|
});
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:33:35 +02:00
|
|
|
public updateManga(
|
|
|
|
|
id: number,
|
|
|
|
|
patch: UpdateMangaPatchInput,
|
|
|
|
|
options?: MutationHookOptions<UpdateMangaMutation, UpdateMangaMutationVariables>,
|
|
|
|
|
): AbortableApolloMutationResponse<UpdateMangaMutation> {
|
|
|
|
|
return this.doRequestNew<UpdateMangaMutation, UpdateMangaMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_MANGA,
|
|
|
|
|
{ input: { id, patch } },
|
|
|
|
|
{
|
|
|
|
|
refetchQueries: [
|
|
|
|
|
GET_MANGA,
|
|
|
|
|
GET_MANGAS,
|
|
|
|
|
GET_CATEGORY_MANGAS,
|
|
|
|
|
GET_CATEGORY,
|
|
|
|
|
GET_CATEGORIES,
|
|
|
|
|
GET_SOURCE_MANGAS_FETCH,
|
|
|
|
|
],
|
|
|
|
|
...options,
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:33:35 +02:00
|
|
|
public setMangaMeta(
|
|
|
|
|
mangaId: number,
|
|
|
|
|
key: string,
|
|
|
|
|
value: any,
|
|
|
|
|
): AbortableApolloMutationResponse<SetMangaMetadataMutation> {
|
|
|
|
|
return this.doRequestNew(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
SET_MANGA_METADATA,
|
|
|
|
|
{
|
|
|
|
|
input: { meta: { mangaId, key, value: `${value}` } },
|
|
|
|
|
},
|
|
|
|
|
{ refetchQueries: [GET_MANGA, GET_MANGAS, GET_CATEGORY_MANGAS, GET_SOURCE_MANGAS_FETCH] },
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetMangaChapters(
|
|
|
|
|
mangaId: number | string,
|
|
|
|
|
{ doOnlineFetch, ...swrOptions }: SWROptions<IChapter[]> & RequestOption = {},
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRResponse<IChapter[]> {
|
2023-05-18 13:25:19 +02:00
|
|
|
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
|
|
|
|
|
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/chapters${onlineFetch}`, {
|
|
|
|
|
swrOptions,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-04 00:50:58 +02:00
|
|
|
public getMangaChapters(mangaId: number | string, doOnlineFetch?: boolean): AbortableAxiosResponse<IChapter[]> {
|
|
|
|
|
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
|
|
|
|
|
return this.doRequest(HttpMethod.GET, `manga/${mangaId}/chapters${onlineFetch}`);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 13:25:19 +02:00
|
|
|
public useGetChapter(
|
|
|
|
|
mangaId: number | string,
|
|
|
|
|
chapterIndex: number | string,
|
|
|
|
|
swrOptions?: SWROptions<IChapter>,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRResponse<IChapter> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/chapter/${chapterIndex}`, {
|
|
|
|
|
swrOptions,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-04 00:50:58 +02:00
|
|
|
public getChapter(mangaId: number | string, chapterIndex: number | string): AbortableAxiosResponse<IChapter> {
|
|
|
|
|
return this.doRequest(HttpMethod.GET, `manga/${mangaId}/chapter/${chapterIndex}`);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:36:40 +02:00
|
|
|
public deleteDownloadedChapter(id: number): AbortableApolloMutationResponse<DeleteDownloadedChapterMutation> {
|
|
|
|
|
return this.doRequestNew<DeleteDownloadedChapterMutation, DeleteDownloadedChapterMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DELETE_DOWNLOADED_CHAPTER,
|
|
|
|
|
{ input: { id } },
|
|
|
|
|
{ refetchQueries: [GET_MANGA, GET_MANGAS, GET_CATEGORY_MANGAS, GET_CHAPTERS] },
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public deleteDownloadedChapters(ids: number[]): AbortableApolloMutationResponse<DeleteDownloadedChaptersMutation> {
|
|
|
|
|
return this.doRequestNew<DeleteDownloadedChaptersMutation, DeleteDownloadedChaptersMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
DELETE_DOWNLOADED_CHAPTERS,
|
|
|
|
|
{ input: { ids } },
|
|
|
|
|
{ refetchQueries: [GET_MANGA, GET_MANGAS, GET_CATEGORY_MANGAS, GET_CHAPTERS] },
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public updateChapter(
|
2023-09-01 20:36:40 +02:00
|
|
|
id: number,
|
|
|
|
|
patch: UpdateChapterPatchInput,
|
|
|
|
|
): AbortableApolloMutationResponse<UpdateChapterMutation> {
|
|
|
|
|
return this.doRequestNew<UpdateChapterMutation, UpdateChapterMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_CHAPTER,
|
|
|
|
|
{ input: { id, patch } },
|
|
|
|
|
{ refetchQueries: [GET_MANGA, GET_MANGAS, GET_CATEGORY_MANGAS, GET_CHAPTER, GET_CHAPTERS] },
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public setChapterMeta(
|
2023-09-01 20:36:40 +02:00
|
|
|
chapterId: number,
|
2023-05-18 13:25:19 +02:00
|
|
|
key: string,
|
|
|
|
|
value: any,
|
2023-09-01 20:36:40 +02:00
|
|
|
): AbortableApolloMutationResponse<SetChapterMetadataMutation> {
|
|
|
|
|
return this.doRequestNew<SetChapterMetadataMutation, SetChapterMetadataMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
SET_CHAPTER_METADATA,
|
|
|
|
|
{ input: { meta: { chapterId, key, value: `${value}` } } },
|
|
|
|
|
{ refetchQueries: [GET_CHAPTER, GET_CHAPTERS] },
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getChapterPageUrl(mangaId: number | string, chapterIndex: number | string, page: number): string {
|
|
|
|
|
return this.getValidImgUrlFor(
|
|
|
|
|
`manga/${mangaId}/chapter/${chapterIndex}/page/${page}`,
|
|
|
|
|
RequestManager.API_VERSION,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 20:36:40 +02:00
|
|
|
public updateChapters(
|
|
|
|
|
ids: number[],
|
|
|
|
|
patch: UpdateChapterPatchInput,
|
|
|
|
|
): AbortableApolloMutationResponse<UpdateChaptersMutation> {
|
|
|
|
|
return this.doRequestNew<UpdateChaptersMutation, UpdateChaptersMutationVariables>(
|
|
|
|
|
GQLMethod.MUTATION,
|
|
|
|
|
UPDATE_CHAPTERS,
|
|
|
|
|
{ input: { ids, patch } },
|
|
|
|
|
{ refetchQueries: [GET_MANGA, GET_MANGAS, GET_CATEGORY_MANGAS, GET_CHAPTER, GET_CHAPTERS] },
|
|
|
|
|
);
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public useGetCategories(swrOptions?: SWROptions<ICategory[]>): AbortableSWRResponse<ICategory[]> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_GET, `category`, { swrOptions });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public createCategory(name: string): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.POST, `category`, { formData: { name } });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public reorderCategory(currentPosition: number, newPosition: number): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.PATCH, `category/reorder`, {
|
|
|
|
|
formData: { from: currentPosition, to: newPosition },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public useGetCategoryMangas(categoryId: number, swrOptions?: SWROptions<IManga[]>): AbortableSWRResponse<IManga[]> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_GET, `category/${categoryId}`, { swrOptions });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public deleteCategory(categoryId: number): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.DELETE, `category/${categoryId}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public updateCategory(
|
|
|
|
|
categoryId: number,
|
|
|
|
|
change: { name?: string; default?: boolean; includeInUpdate?: IncludeInGlobalUpdate } = {},
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.PATCH, `category/${categoryId}`, { formData: change });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public setCategoryMeta(categoryId: number, key: string, value: any): AbortableAxiosResponse {
|
2023-06-04 00:50:58 +02:00
|
|
|
return this.doRequest(HttpMethod.PATCH, `category/${categoryId}/meta`, { formData: { key, value } });
|
2023-05-18 13:25:19 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public restoreBackupFile(file: File): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.POST, 'backup/import/file', { formData: { 'backup.proto.gz': file } });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useValidateBackupFile(
|
|
|
|
|
file: File,
|
|
|
|
|
swrOptions?: SWROptions<BackupValidationResult>,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRResponse<BackupValidationResult> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_POST, 'backup/validate/file', {
|
|
|
|
|
formData: { 'backup.proto.gz': file },
|
|
|
|
|
swrOptions,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getExportBackupUrl(): string {
|
|
|
|
|
return this.getValidUrlFor('backup/export/file');
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public startDownloads(): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.GET, 'downloads/start');
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public stopDownloads(): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.GET, 'downloads/stop');
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public clearDownloads(): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.GET, 'downloads/clear');
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public addChapterToDownloadQueue(mangaId: number | string, chapterIndex: number | string): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.GET, `download/${mangaId}/chapter/${chapterIndex}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public removeChapterFromDownloadQueue(
|
|
|
|
|
mangaId: number | string,
|
|
|
|
|
chapterIndex: number | string,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.DELETE, `download/${mangaId}/chapter/${chapterIndex}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public reorderChapterInDownloadQueue(
|
|
|
|
|
mangaId: number | string,
|
|
|
|
|
chapterIndex: number | string,
|
|
|
|
|
position: number,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.PATCH, `download/${mangaId}/chapter/${chapterIndex}/reorder/${position}`);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public addChaptersToDownloadQueue(chapterIds: number[]): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.POST, 'download/batch', { data: { chapterIds } });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public removeChaptersFromDownloadQueue(chapterIds: number[]): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.DELETE, 'download/batch', { data: { chapterIds } });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public useGetRecentlyUpdatedChapters(
|
|
|
|
|
initialPages?: number,
|
|
|
|
|
swrOptions?: SWRInfiniteOptions<PaginatedList<IMangaChapter>>,
|
2023-05-23 13:27:38 +02:00
|
|
|
): AbortableSWRInfiniteResponse<PaginatedList<IMangaChapter>> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_GET_INFINITE, '', {
|
|
|
|
|
swrOptions: {
|
|
|
|
|
getEndpoint: (page, previousData) =>
|
|
|
|
|
previousData?.hasNextPage ?? true ? `update/recentChapters/${page}` : null,
|
|
|
|
|
initialSize: initialPages,
|
|
|
|
|
...swrOptions,
|
|
|
|
|
} as typeof swrOptions,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public startGlobalUpdate(categoryId?: number): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.POST, 'update/fetch', { formData: { categoryId } });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public resetGlobalUpdate(): AbortableAxiosResponse {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.POST, 'update/reset');
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 13:27:38 +02:00
|
|
|
public useGetGlobalUpdateSummary(swrOptions?: SWROptions<IUpdateStatus>): AbortableSWRResponse<IUpdateStatus> {
|
2023-05-18 13:25:19 +02:00
|
|
|
return this.doRequest(HttpMethod.SWR_GET, 'update/summary', { swrOptions });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const requestManager = new RequestManager();
|
|
|
|
|
export default requestManager;
|