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'; } from '@tanstack/react-query';
import { customInstance } from './api'; import { customInstance } from './api';
export interface UpdateMangaDataCommand {
mangaId?: number;
}
export interface DefaultResponseDTOVoid { export interface DefaultResponseDTOVoid {
timestamp?: string; timestamp?: string;
data?: unknown; data?: unknown;
@ -105,18 +109,18 @@ export interface PageMangaListDTO {
content?: MangaListDTO[]; content?: MangaListDTO[];
number?: number; number?: number;
pageable?: PageableObject; pageable?: PageableObject;
sort?: SortObject;
first?: boolean; first?: boolean;
last?: boolean; last?: boolean;
sort?: SortObject;
numberOfElements?: number; numberOfElements?: number;
empty?: boolean; empty?: boolean;
} }
export interface PageableObject { export interface PageableObject {
offset?: number; offset?: number;
paged?: boolean;
pageNumber?: number; pageNumber?: number;
pageSize?: number; pageSize?: number;
paged?: boolean;
sort?: SortObject; sort?: SortObject;
unpaged?: boolean; 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. * Fetch a list of manga chapters for a specific manga/provider combination.
* @summary Fetch the available 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); 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. * Remove a manga from favorites for the logged user.
* @summary Unfavorite a manga * @summary Unfavorite a manga

View File

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