feat: add functionality to fetch all chapters from provider and update related hooks

This commit is contained in:
Rodrigo Verdiani 2025-10-27 17:10:03 -03:00
parent 6c8ed19be4
commit f872d96b80
2 changed files with 153 additions and 5 deletions

View File

@ -24,6 +24,10 @@ import type {
} from '@tanstack/react-query';
import { customInstance } from './api';
export interface UpdateMangaDataCommand {
mangaId?: number;
}
export interface DefaultResponseDTOVoid {
timestamp?: string;
data?: unknown;
@ -105,18 +109,18 @@ export interface PageMangaListDTO {
content?: MangaListDTO[];
number?: number;
pageable?: PageableObject;
sort?: SortObject;
first?: boolean;
last?: boolean;
sort?: SortObject;
numberOfElements?: number;
empty?: boolean;
}
export interface PageableObject {
offset?: number;
paged?: boolean;
pageNumber?: number;
pageSize?: number;
paged?: boolean;
sort?: SortObject;
unpaged?: boolean;
}
@ -268,6 +272,65 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
export const sendRecord = (
updateMangaDataCommand: UpdateMangaDataCommand,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<string>(
{url: `http://mangamochi.badger-pirarucu.ts.net:8080/records`, method: 'POST',
headers: {'Content-Type': 'application/json', },
data: updateMangaDataCommand, signal
},
options);
}
export const getSendRecordMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof sendRecord>>, TError,{data: UpdateMangaDataCommand}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof sendRecord>>, TError,{data: UpdateMangaDataCommand}, TContext> => {
const mutationKey = ['sendRecord'];
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 sendRecord>>, {data: UpdateMangaDataCommand}> = (props) => {
const {data} = props ?? {};
return sendRecord(data,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type SendRecordMutationResult = NonNullable<Awaited<ReturnType<typeof sendRecord>>>
export type SendRecordMutationBody = UpdateMangaDataCommand
export type SendRecordMutationError = unknown
export const useSendRecord = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof sendRecord>>, TError,{data: UpdateMangaDataCommand}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof sendRecord>>,
TError,
{data: UpdateMangaDataCommand},
TContext
> => {
const mutationOptions = getSendRecordMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Fetch a list of manga chapters for a specific manga/provider combination.
* @summary Fetch the available chapters for a specific manga/provider combination
@ -332,6 +395,70 @@ export const useFetchMangaChapters = <TError = unknown,
return useMutation(mutationOptions, queryClient);
}
/**
* Fetch all not yet downloaded chapters from the provider
* @summary Fetch all chapters
*/
export const fetchAllChapters = (
mangaProviderId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${encodeURIComponent(String(mangaProviderId))}/fetch-all-chapters`, method: 'POST', signal
},
options);
}
export const getFetchAllChaptersMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof fetchAllChapters>>, TError,{mangaProviderId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof fetchAllChapters>>, TError,{mangaProviderId: number}, TContext> => {
const mutationKey = ['fetchAllChapters'];
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 fetchAllChapters>>, {mangaProviderId: number}> = (props) => {
const {mangaProviderId} = props ?? {};
return fetchAllChapters(mangaProviderId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type FetchAllChaptersMutationResult = NonNullable<Awaited<ReturnType<typeof fetchAllChapters>>>
export type FetchAllChaptersMutationError = unknown
/**
* @summary Fetch all chapters
*/
export const useFetchAllChapters = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof fetchAllChapters>>, TError,{mangaProviderId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof fetchAllChapters>>,
TError,
{mangaProviderId: number},
TContext
> => {
const mutationOptions = getFetchAllChaptersMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Remove a manga from favorites for the logged user.
* @summary Unfavorite a manga

View File

@ -20,9 +20,10 @@ import {
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { ThemeToggle } from "@/components/theme-toggle";
import { useFetchMangaChapters, useGetManga } from "@/api/mangamochi";
import {useFetchAllChapters, useFetchMangaChapters, useGetManga} from "@/api/mangamochi";
import { MangaChapter } from "@/components/manga-chapter";
import { useQueryClient } from "@tanstack/react-query";
import {toast} from "sonner";
export default function MangaDetailPage() {
const params = useParams();
@ -33,12 +34,20 @@ export default function MangaDetailPage() {
const { data: mangaData, queryKey } = useGetManga(mangaId);
const { mutate, isPending } = useFetchMangaChapters({
const { mutate, isPending: fetchPending } = useFetchMangaChapters({
mutation: {
onSuccess: () => queryClient.invalidateQueries({ queryKey }),
},
});
const { mutate: fetchAllMutate, isPending: fetchAllPending } = useFetchAllChapters({
mutation: {
onSuccess: () => toast.success("Chapter import queued successfully.")
}
})
const isPending = fetchPending || fetchAllPending;
const [openProviders, setOpenProviders] = useState<Set<number>>(new Set());
if (!mangaData) {
@ -258,7 +267,19 @@ export default function MangaDetailPage() {
</div>
</div>
{provider.supportsChapterFetch && (
<div className={"pr-4"}>
<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"