Merge pull request 'feature(provider): add provider status and update manga list functionality' (#14) from feature/inactive-provider into main

Reviewed-on: #14
This commit is contained in:
rov 2025-11-29 22:21:34 -03:00
commit 1a0f730e72
3 changed files with 127 additions and 42 deletions

View File

@ -86,30 +86,30 @@ export interface MangaListDTO {
export interface PageMangaListDTO {
totalPages?: number;
totalElements?: number;
pageable?: PageableObject;
size?: number;
content?: MangaListDTO[];
number?: number;
pageable?: PageableObject;
sort?: SortObject;
first?: boolean;
last?: boolean;
sort?: SortObject;
numberOfElements?: number;
empty?: boolean;
}
export interface PageableObject {
offset?: number;
pageNumber?: number;
pageSize?: number;
paged?: boolean;
sort?: SortObject;
unpaged?: boolean;
offset?: number;
sort?: SortObject;
}
export interface SortObject {
empty?: boolean;
sorted?: boolean;
unsorted?: boolean;
empty?: boolean;
}
export interface DefaultResponseDTOListMangaChapterDTO {
@ -152,10 +152,20 @@ export interface MangaDTO {
following: boolean;
}
export type MangaProviderDTOProviderStatus = typeof MangaProviderDTOProviderStatus[keyof typeof MangaProviderDTOProviderStatus];
// eslint-disable-next-line @typescript-eslint/no-redeclare
export const MangaProviderDTOProviderStatus = {
ACTIVE: 'ACTIVE',
INACTIVE: 'INACTIVE',
} as const;
export interface MangaProviderDTO {
id: number;
/** @minLength 1 */
providerName: string;
providerStatus: MangaProviderDTOProviderStatus;
chaptersAvailable: number;
chaptersDownloaded: number;
supportsChapterFetch: boolean;
@ -231,6 +241,10 @@ importReviewId: number;
malId: string;
};
export type UpdateProviderMangaListParams = {
providerId: number;
};
export type GetMangasParams = {
searchQuery?: string;
genreIds?: number[];

View File

@ -15,7 +15,8 @@ import type {
} from '@tanstack/react-query';
import type {
DefaultResponseDTOVoid
DefaultResponseDTOVoid,
UpdateProviderMangaListParams
} from '../api.schemas';
import { customInstance } from '../../api';
@ -89,6 +90,70 @@ export const useUserFollowUpdate = <TError = unknown,
return useMutation(mutationOptions, queryClient);
}
/**
* Queue the retrieval of the manga list for a specific provider
* @summary Queue update provider manga list
*/
export const updateProviderMangaList = (
params: UpdateProviderMangaListParams,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/management/update-provider-manga-list`, method: 'POST',
params, signal
},
options);
}
export const getUpdateProviderMangaListMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateProviderMangaList>>, TError,{params: UpdateProviderMangaListParams}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof updateProviderMangaList>>, TError,{params: UpdateProviderMangaListParams}, TContext> => {
const mutationKey = ['updateProviderMangaList'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof updateProviderMangaList>>, {params: UpdateProviderMangaListParams}> = (props) => {
const {params} = props ?? {};
return updateProviderMangaList(params,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type UpdateProviderMangaListMutationResult = NonNullable<Awaited<ReturnType<typeof updateProviderMangaList>>>
export type UpdateProviderMangaListMutationError = unknown
/**
* @summary Queue update provider manga list
*/
export const useUpdateProviderMangaList = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateProviderMangaList>>, TError,{params: UpdateProviderMangaListParams}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof updateProviderMangaList>>,
TError,
{params: UpdateProviderMangaListParams},
TContext
> => {
const mutationOptions = getUpdateProviderMangaListMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Queue the retrieval of the manga lists from the content providers
* @summary Queue update manga list
*/

View File

@ -81,11 +81,10 @@ const Manga = () => {
const isPendingFavoriteChange = isPendingFavorite || isPendingUnfavorite;
const handleFavoriteClick = useCallback(
(isFavorite: boolean) => {
(isFavorite: boolean) =>
isFavorite
? mutateUnfavorite({ id: mangaData?.data?.id ?? -1 })
: mutateFavorite({ id: mangaData?.data?.id ?? -1 });
},
: mutateFavorite({ id: mangaData?.data?.id ?? -1 }),
[mutateUnfavorite, mutateFavorite, mangaData?.data?.id],
);
@ -115,11 +114,10 @@ const Manga = () => {
const isPendingFollowChange = isPendingFollow || isPendingUnfollow;
const handleFollowClick = useCallback(
(isFollowing: boolean) => {
(isFollowing: boolean) =>
isFollowing
? mutateUnfollow({ mangaId: mangaData?.data?.id ?? -1 })
: mutateFollow({ mangaId: mangaData?.data?.id ?? -1 });
},
: mutateFollow({ mangaId: mangaData?.data?.id ?? -1 }),
[mangaData?.data?.id, mutateUnfollow, mutateFollow],
);
@ -347,6 +345,13 @@ const Manga = () => {
</h2>
<div className="space-y-4">
{mangaData.data?.providers.length === 0 && (
<div className="flex justify-center">
<p className="text-foreground">
No providers available for this manga.
</p>
</div>
)}
{mangaData.data?.providers.map((provider) => (
<Card key={provider.id} className="border-border bg-card">
<Collapsible
@ -368,36 +373,37 @@ const Manga = () => {
</p>
</div>
</div>
{provider.supportsChapterFetch && (
<div className={"flex gap-4 pr-4"}>
<Button
size="sm"
variant="outline"
disabled={isPending}
onClick={() =>
fetchAllMutate({
mangaProviderId: provider.id,
})
}
className="gap-2"
>
<Database className="h-4 w-4" />
Fetch all from Provider
</Button>
<Button
size="sm"
variant="outline"
disabled={isPending}
onClick={() =>
mutate({ mangaProviderId: provider.id })
}
className="gap-2"
>
<Database className="h-4 w-4" />
Fetch from Provider
</Button>
</div>
)}
{provider.supportsChapterFetch &&
provider.providerStatus === "ACTIVE" && (
<div className={"flex gap-4 pr-4"}>
<Button
size="sm"
variant="outline"
disabled={isPending}
onClick={() =>
fetchAllMutate({
mangaProviderId: provider.id,
})
}
className="gap-2"
>
<Database className="h-4 w-4" />
Fetch all from Provider
</Button>
<Button
size="sm"
variant="outline"
disabled={isPending}
onClick={() =>
mutate({ mangaProviderId: provider.id })
}
className="gap-2"
>
<Database className="h-4 w-4" />
Fetch from Provider
</Button>
</div>
)}
</div>
<ChevronDown
className={`h-5 w-5 text-muted-foreground transition-transform ${