diff --git a/src/components/MigrateDialog.tsx b/src/components/MigrateDialog.tsx index 1a1cf8aa..e5ff09df 100644 --- a/src/components/MigrateDialog.tsx +++ b/src/components/MigrateDialog.tsx @@ -35,7 +35,7 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat const mangaId = Number(mangaIdAsString); const { - settings: { migrateChapters, migrateCategories, deleteChapters }, + settings: { migrateChapters, migrateCategories, migrateTracking, deleteChapters }, } = useMetadataServerSettings(); const [isMigrationInProcess, setIsMigrationInProcess] = useState(false); @@ -58,6 +58,7 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat mode, migrateChapters, migrateCategories, + migrateTracking, deleteChapters, }); @@ -84,6 +85,12 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat checked={migrateCategories} onChange={(_, checked) => setMigrationFlag('migrateCategories', checked)} /> + setMigrationFlag('migrateTracking', checked)} + /> category.id), - }); + }).response; + } + + private static async migrateTracking( + mode: MigrateMode, + mangaToMigrate: GetMangaToMigrateQuery['manga'], + mangaToMigrateTo: GetMangaToMigrateToFetchMutation['fetchManga']['manga'], + ): Promise { + if (!mangaToMigrate.trackRecords) { + throw new Error('TrackRecords of manga to migrate are missing'); + } + + if (!mangaToMigrateTo.trackRecords) { + throw new Error('TrackRecords of manga to migrate to are missing'); + } + + const trackBindingsToAdd = mangaToMigrate.trackRecords.nodes.filter((trackRecordToMigrate) => + mangaToMigrateTo.trackRecords!.nodes.every( + (trackRecord) => trackRecordToMigrate.remoteId !== trackRecord.remoteId, + ), + ); + + await Promise.all([ + ...(mode === 'migrate' + ? mangaToMigrate.trackRecords.nodes.map( + (trackRecord) => requestManager.unbindTracker(trackRecord.id).response, + ) + : []), + ...trackBindingsToAdd.map((trackRecord) => + requestManager.bindTracker(mangaToMigrateTo.id, trackRecord.trackerId, trackRecord.remoteId), + ), + ]); } static async migrate( mangaId: number, mangaIdToMigrateTo: number, - { mode, migrateChapters, migrateCategories, deleteChapters }: Omit, + { + mode, + migrateChapters, + migrateCategories, + migrateTracking, + deleteChapters, + }: Omit, ): Promise { return Mangas.executeAction('migrate', 1, async () => { const [{ data: mangaToMigrateData }, { data: mangaToMigrateToData }] = await Promise.all([ - requestManager.getMangaToMigrate(mangaId, { migrateChapters, migrateCategories, deleteChapters }) - .response, - requestManager.getMangaToMigrateToFetch(mangaIdToMigrateTo, { migrateChapters, migrateCategories }) - .response, + requestManager.getMangaToMigrate(mangaId, { + migrateChapters, + migrateCategories, + migrateTracking, + deleteChapters, + }).response, + requestManager.getMangaToMigrateToFetch(mangaIdToMigrateTo, { + migrateChapters, + migrateCategories, + migrateTracking, + }).response, ]); if (!mangaToMigrateData.manga || !mangaToMigrateToData?.fetchManga.manga) { @@ -407,6 +451,9 @@ export class Mangas { migrateCategories ? Mangas.migrateCategories(mangaToMigrateData.manga, mangaToMigrateToData.fetchManga.manga) : undefined, + migrateTracking + ? Mangas.migrateTracking(mode, mangaToMigrateData.manga, mangaToMigrateToData.fetchManga.manga) + : undefined, !mangaToMigrateToData.fetchManga.manga.inLibrary ? requestManager.updateManga(mangaIdToMigrateTo, { updateManga: { inLibrary: true } }).response : undefined, diff --git a/src/lib/graphql/generated/graphql.ts b/src/lib/graphql/generated/graphql.ts index 2f9bda06..24a80103 100644 --- a/src/lib/graphql/generated/graphql.ts +++ b/src/lib/graphql/generated/graphql.ts @@ -2890,10 +2890,11 @@ export type GetMangaToMigrateToFetchMutationVariables = Exact<{ id: Scalars['Int']['input']; migrateChapters: Scalars['Boolean']['input']; migrateCategories: Scalars['Boolean']['input']; + migrateTracking: Scalars['Boolean']['input']; }>; -export type GetMangaToMigrateToFetchMutation = { __typename?: 'Mutation', fetchManga: { __typename?: 'FetchMangaPayload', clientMutationId?: string | null, manga: { __typename?: 'MangaType', id: number, title: string, inLibrary: boolean, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> } } }, fetchChapters?: { __typename?: 'FetchChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> } }; +export type GetMangaToMigrateToFetchMutation = { __typename?: 'Mutation', fetchManga: { __typename?: 'FetchMangaPayload', clientMutationId?: string | null, manga: { __typename?: 'MangaType', id: number, title: string, inLibrary: boolean, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> }, trackRecords?: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number }> } } }, fetchChapters?: { __typename?: 'FetchChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> } }; export type SetMangaMetadataMutationVariables = Exact<{ input: SetMangaMetaInput; @@ -3181,10 +3182,11 @@ export type GetMangaToMigrateQueryVariables = Exact<{ id: Scalars['Int']['input']; getChapterData: Scalars['Boolean']['input']; migrateCategories: Scalars['Boolean']['input']; + migrateTracking: Scalars['Boolean']['input']; }>; -export type GetMangaToMigrateQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', id: number, inLibrary: boolean, title: string, chapters?: { __typename?: 'ChapterNodeList', totalCount: number, nodes: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> }, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> } } }; +export type GetMangaToMigrateQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', id: number, inLibrary: boolean, title: string, chapters?: { __typename?: 'ChapterNodeList', totalCount: number, nodes: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> }, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> }, trackRecords?: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number }> } } }; export type GetMangasQueryVariables = Exact<{ after?: InputMaybe; diff --git a/src/lib/graphql/mutations/MangaMutation.ts b/src/lib/graphql/mutations/MangaMutation.ts index 7942b053..b5027fc1 100644 --- a/src/lib/graphql/mutations/MangaMutation.ts +++ b/src/lib/graphql/mutations/MangaMutation.ts @@ -50,7 +50,12 @@ export const GET_MANGA_FETCH = gql` // makes the server fetch and return the manga export const GET_MANGA_TO_MIGRATE_TO_FETCH = gql` - mutation GET_MANGA_TO_MIGRATE_TO_FETCH($id: Int!, $migrateChapters: Boolean!, $migrateCategories: Boolean!) { + mutation GET_MANGA_TO_MIGRATE_TO_FETCH( + $id: Int! + $migrateChapters: Boolean! + $migrateCategories: Boolean! + $migrateTracking: Boolean! + ) { fetchManga(input: { id: $id }) { clientMutationId manga { @@ -62,6 +67,13 @@ export const GET_MANGA_TO_MIGRATE_TO_FETCH = gql` id } } + trackRecords @include(if: $migrateTracking) { + nodes { + id + remoteId + trackerId + } + } } } fetchChapters(input: { mangaId: $id }) @include(if: $migrateChapters) { diff --git a/src/lib/graphql/queries/MangaQuery.ts b/src/lib/graphql/queries/MangaQuery.ts index 53ab16ff..1ab34265 100644 --- a/src/lib/graphql/queries/MangaQuery.ts +++ b/src/lib/graphql/queries/MangaQuery.ts @@ -22,7 +22,12 @@ export const GET_MANGA = gql` // returns the current manga from the database export const GET_MANGA_TO_MIGRATE = gql` - query GET_MANGA_TO_MIGRATE($id: Int!, $getChapterData: Boolean!, $migrateCategories: Boolean!) { + query GET_MANGA_TO_MIGRATE( + $id: Int! + $getChapterData: Boolean! + $migrateCategories: Boolean! + $migrateTracking: Boolean! + ) { manga(id: $id) { id inLibrary @@ -45,6 +50,13 @@ export const GET_MANGA_TO_MIGRATE = gql` id } } + trackRecords @include(if: $migrateTracking) { + nodes { + id + remoteId + trackerId + } + } } } `; diff --git a/src/lib/metadata/metadata.ts b/src/lib/metadata/metadata.ts index 227a2448..4d75f88f 100644 --- a/src/lib/metadata/metadata.ts +++ b/src/lib/metadata/metadata.ts @@ -41,6 +41,7 @@ const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [ // migration 'migrateChapters', 'migrateCategories', + 'migrateTracking', 'deleteChapters', // browse diff --git a/src/lib/metadata/metadataServerSettings.ts b/src/lib/metadata/metadataServerSettings.ts index ab1b4d08..e06e4cee 100644 --- a/src/lib/metadata/metadataServerSettings.ts +++ b/src/lib/metadata/metadataServerSettings.ts @@ -37,6 +37,7 @@ export const getDefaultSettings = (): MetadataServerSettings => ({ // migration migrateChapters: true, migrateCategories: true, + migrateTracking: true, deleteChapters: true, // browse diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index a765487d..81f13e93 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -287,7 +287,7 @@ import { TRACKER_UPDATE_BIND, } from '@/lib/graphql/mutations/TrackerMutation.ts'; import { ControlledPromise } from '@/lib/ControlledPromise.ts'; -import { TManga } from '@/typings.ts'; +import { MetadataMigrationSettings, TManga } from '@/typings.ts'; enum GQLMethod { QUERY = 'QUERY', @@ -1549,19 +1549,22 @@ export class RequestManager { { migrateChapters = false, migrateCategories = false, + migrateTracking = false, deleteChapters = false, apolloOptions: options, - }: { - migrateChapters?: boolean; - migrateCategories?: boolean; - deleteChapters?: boolean; + }: Partial & { apolloOptions?: QueryOptions; } = {}, ): AbortabaleApolloQueryResponse { return this.doRequest( GQLMethod.QUERY, GET_MANGA_TO_MIGRATE, - { id: Number(mangaId), getChapterData: migrateChapters || deleteChapters, migrateCategories }, + { + id: Number(mangaId), + getChapterData: migrateChapters || deleteChapters, + migrateCategories, + migrateTracking, + }, options, ); } @@ -1587,10 +1590,9 @@ export class RequestManager { { migrateChapters = false, migrateCategories = false, + migrateTracking = false, apolloOptions: options, - }: { - migrateChapters?: boolean; - migrateCategories?: boolean; + }: Partial> & { apolloOptions?: MutationOptions< GetMangaToMigrateToFetchMutation, GetMangaToMigrateToFetchMutationVariables @@ -1604,6 +1606,7 @@ export class RequestManager { id: Number(mangaId), migrateChapters, migrateCategories, + migrateTracking, }, options, ); @@ -2490,6 +2493,15 @@ export class RequestManager { return this.doRequest(GQLMethod.USE_MUTATION, TRACKER_BIND, undefined, options); } + public bindTracker( + mangaId: number, + trackerId: number, + remoteId: string, + options?: MutationOptions, + ): AbortableApolloMutationResponse { + return this.doRequest(GQLMethod.MUTATION, TRACKER_BIND, { mangaId, remoteId, trackerId }, options); + } + public unbindTracker( recordId: number, deleteRemoteTrack?: boolean, diff --git a/src/typings.ts b/src/typings.ts index a069856e..69954148 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -242,6 +242,7 @@ export type MetadataClientSettings = { export type MetadataMigrationSettings = { migrateChapters: boolean; migrateCategories: boolean; + migrateTracking: boolean; deleteChapters: boolean; };