feat: Add AniList ID support for manga imports, introduce manga data update API, and include chapter language.

This commit is contained in:
Rodrigo Verdiani 2025-12-31 20:01:42 -03:00
parent 8b27e56758
commit 9f0eeab4fb
3 changed files with 123 additions and 25 deletions

View File

@ -12,6 +12,7 @@ export interface DefaultResponseDTOVoid {
export interface ImportRequestDTO { export interface ImportRequestDTO {
metadataId?: string; metadataId?: string;
aniListId?: string;
id: string; id: string;
} }
@ -114,18 +115,18 @@ export interface PageMangaListDTO {
} }
export interface PageableObject { export interface PageableObject {
paged?: boolean;
pageNumber?: number; pageNumber?: number;
pageSize?: number; pageSize?: number;
paged?: boolean; unpaged?: boolean;
offset?: number; offset?: number;
sort?: SortObject; sort?: SortObject;
unpaged?: boolean;
} }
export interface SortObject { export interface SortObject {
sorted?: boolean; sorted?: boolean;
empty?: boolean;
unsorted?: boolean; unsorted?: boolean;
empty?: boolean;
} }
export interface DefaultResponseDTOListMangaChapterDTO { export interface DefaultResponseDTOListMangaChapterDTO {
@ -134,12 +135,21 @@ export interface DefaultResponseDTOListMangaChapterDTO {
message?: string; message?: string;
} }
export interface LanguageDTO {
id: number;
/** @minLength 1 */
code: string;
/** @minLength 1 */
name: string;
}
export interface MangaChapterDTO { export interface MangaChapterDTO {
id: number; id: number;
/** @minLength 1 */ /** @minLength 1 */
title: string; title: string;
downloaded: boolean; downloaded: boolean;
isRead: boolean; isRead: boolean;
language?: LanguageDTO;
} }
export interface DefaultResponseDTOMangaDTO { export interface DefaultResponseDTOMangaDTO {

View File

@ -217,6 +217,69 @@ export const useUpdateMangaList = <TError = unknown,
return useMutation(mutationOptions, queryClient); return useMutation(mutationOptions, queryClient);
} }
/** /**
* Triggers the update of the metadata for a manga by its ID
* @summary Trigger manga data update
*/
export const triggerUpdateMangaData = (
mangaId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/management/update-manga-data/${encodeURIComponent(String(mangaId))}`, method: 'POST', signal
},
options);
}
export const getTriggerUpdateMangaDataMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof triggerUpdateMangaData>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof triggerUpdateMangaData>>, TError,{mangaId: number}, TContext> => {
const mutationKey = ['triggerUpdateMangaData'];
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 triggerUpdateMangaData>>, {mangaId: number}> = (props) => {
const {mangaId} = props ?? {};
return triggerUpdateMangaData(mangaId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type TriggerUpdateMangaDataMutationResult = NonNullable<Awaited<ReturnType<typeof triggerUpdateMangaData>>>
export type TriggerUpdateMangaDataMutationError = unknown
/**
* @summary Trigger manga data update
*/
export const useTriggerUpdateMangaData = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof triggerUpdateMangaData>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof triggerUpdateMangaData>>,
TError,
{mangaId: number},
TContext
> => {
const mutationOptions = getTriggerUpdateMangaDataMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Sends a test notification to all users * Sends a test notification to all users
* @summary Test notification * @summary Test notification
*/ */

View File

@ -45,6 +45,7 @@ export const ProviderImportDialog = ({
value: z.string().min(1, "Please enter an ID or URL."), value: z.string().min(1, "Please enter an ID or URL."),
providerId: z.string(), providerId: z.string(),
myAnimeListId: z.string().optional(), myAnimeListId: z.string().optional(),
anilistId: z.string().optional(),
}); });
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<typeof formSchema>>({
@ -53,6 +54,7 @@ export const ProviderImportDialog = ({
value: "", value: "",
providerId: "", providerId: "",
myAnimeListId: undefined, myAnimeListId: undefined,
anilistId: undefined,
}, },
}); });
@ -74,7 +76,11 @@ export const ProviderImportDialog = ({
(data: z.output<typeof formSchema>) => { (data: z.output<typeof formSchema>) => {
importFromProvider({ importFromProvider({
providerId: Number(data.providerId), providerId: Number(data.providerId),
data: { id: data.value, metadataId: data.myAnimeListId }, data: {
id: data.value,
metadataId: data.myAnimeListId,
aniListId: data.anilistId,
},
}); });
}, },
[importFromProvider], [importFromProvider],
@ -145,11 +151,12 @@ export const ProviderImportDialog = ({
</FormItem> </FormItem>
)} )}
/> />
<div className="flex w-full gap-4">
<FormField <FormField
control={form.control} control={form.control}
name="myAnimeListId" name="myAnimeListId"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className="flex-1">
<FormLabel>MyAnimeList ID (Optional)</FormLabel> <FormLabel>MyAnimeList ID (Optional)</FormLabel>
<FormControl> <FormControl>
<Input <Input
@ -158,14 +165,32 @@ export const ProviderImportDialog = ({
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription>
Optionally link this manga to a MyAnimeList entry for better
precision on metadata fetching.
</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="anilistId"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>AniList ID (Optional)</FormLabel>
<FormControl>
<Input
placeholder="e.g., 21 (for One Piece)"
disabled={isPendingImportFromProvider}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormDescription>
Optionally link this manga to a MyAnimeList or AniList entry for
better precision on metadata fetching.
</FormDescription>
</form> </form>
</Form> </Form>
<DialogFooter> <DialogFooter>