diff --git a/src/features/metadata/services/MetadataUpdater.ts b/src/features/metadata/services/MetadataUpdater.ts index 351e390c..18a48574 100644 --- a/src/features/metadata/services/MetadataUpdater.ts +++ b/src/features/metadata/services/MetadataUpdater.ts @@ -92,28 +92,50 @@ const requestMetadataUpdate = async ( value: `${value}`, })); - const updateMetaInput = { - updateInput: { metas: allUpdateMetas }, - deleteInput: { keys: uniqueDeleteKeys }, - migrateInput: { metas: migrateMetas }, - }; - switch (holderType) { - case 'category': - await requestManager.updateCategoryMeta([(metadataHolder as CategoryIdInfo).id], updateMetaInput).response; + case 'category': { + const categoryId = (metadataHolder as CategoryIdInfo).id; + await requestManager.updateCategoryMeta({ + updateInput: { items: [{ categoryIds: [categoryId], metas: allUpdateMetas }] }, + deleteInput: { items: [{ categoryIds: [categoryId], keys: uniqueDeleteKeys }] }, + migrateInput: { items: [{ categoryIds: [categoryId], metas: migrateMetas }] }, + }).response; break; - case 'chapter': - await requestManager.updateChapterMeta([(metadataHolder as ChapterIdInfo).id], updateMetaInput).response; + } + case 'chapter': { + const chapterId = (metadataHolder as ChapterIdInfo).id; + await requestManager.updateChapterMeta({ + updateInput: { items: [{ chapterIds: [chapterId], metas: allUpdateMetas }] }, + deleteInput: { items: [{ chapterIds: [chapterId], keys: uniqueDeleteKeys }] }, + migrateInput: { items: [{ chapterIds: [chapterId], metas: migrateMetas }] }, + }).response; break; + } case 'global': - await requestManager.updateGlobalMeta(updateMetaInput).response; + await requestManager.updateGlobalMeta({ + updateInput: { metas: allUpdateMetas }, + deleteInput: { keys: uniqueDeleteKeys }, + migrateInput: { metas: migrateMetas }, + }).response; break; - case 'manga': - await requestManager.updateMangaMeta([(metadataHolder as MangaIdInfo).id], updateMetaInput).response; + case 'manga': { + const mangaId = (metadataHolder as MangaIdInfo).id; + await requestManager.updateMangaMeta({ + updateInput: { items: [{ mangaIds: [mangaId], metas: allUpdateMetas }] }, + deleteInput: { items: [{ mangaIds: [mangaId], keys: uniqueDeleteKeys }] }, + migrateInput: { items: [{ mangaIds: [mangaId], metas: migrateMetas }] }, + }).response; break; - case 'source': - await requestManager.updateSourceMeta([(metadataHolder as SourceIdInfo).id], updateMetaInput).response; + } + case 'source': { + const sourceId = (metadataHolder as SourceIdInfo).id; + await requestManager.updateSourceMeta({ + updateInput: { items: [{ sourceIds: [sourceId], metas: allUpdateMetas }] }, + deleteInput: { items: [{ sourceIds: [sourceId], keys: uniqueDeleteKeys }] }, + migrateInput: { items: [{ sourceIds: [sourceId], metas: migrateMetas }] }, + }).response; break; + } default: throw new Error(`requestMetadataUpdate: unknown holderType "${holderType}"`); } diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index 66afe48d..a7016e2d 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -220,14 +220,14 @@ import { UpdateCategoryMetadataMutationVariables, UpdateSourceMetadataMutation, UpdateSourceMetadataMutationVariables, - SetCategoryMetasItemInput, - DeleteCategoryMetasItemInput, - SetSourceMetasItemInput, - DeleteSourceMetasItemInput, - SetMangaMetasItemInput, - DeleteMangaMetasItemInput, - SetChapterMetasItemInput, - DeleteChapterMetasItemInput, + SetSourceMetasInput, + DeleteSourceMetasInput, + SetMangaMetasInput, + DeleteMangaMetasInput, + SetChapterMetasInput, + DeleteChapterMetasInput, + SetCategoryMetasInput, + DeleteCategoryMetasInput, } from '@/lib/graphql/generated/graphql.ts'; import { GET_GLOBAL_METADATAS } from '@/lib/graphql/metadata/GlobalMetadataQuery.ts'; import { UPDATE_GLOBAL_METADATA } from '@/lib/graphql/metadata/GlobalMetadataMutation.ts'; @@ -339,8 +339,6 @@ import { SOURCE_META_FIELDS } from '@/lib/graphql/source/SourceFragments.ts'; import { CHAPTER_META_FIELDS } from '@/lib/graphql/chapter/ChapterFragments.ts'; import { MetadataMigrationSettings } from '@/features/migration/Migration.types.ts'; import { MangaIdInfo } from '@/features/manga/Manga.types.ts'; -import { ChapterIdInfo } from '@/features/chapter/Chapter.types.ts'; -import { CategoryIdInfo } from '@/features/category/Category.types.ts'; import { updateMetadataList } from '@/features/metadata/services/MetadataApolloCacheHandler.ts'; import { USER_LOGIN, USER_REFRESH } from '@/lib/graphql/user/UserMutation.ts'; import { AuthManager } from '@/features/authentication/AuthManager.ts'; @@ -349,7 +347,6 @@ import { KO_SYNC_LOGIN, KO_SYNC_LOGOUT } from '@/lib/graphql/koreader/KoreaderSy import { GET_KO_SYNC_STATUS } from '@/lib/graphql/koreader/KoreaderSyncQuery.ts'; import { ImageCache } from '@/lib/service-worker/ImageCache.ts'; import { Sources } from '@/features/source/services/Sources.ts'; -import { SourceIdInfo } from '@/features/source/Source.types.ts'; enum GQLMethod { QUERY = 'QUERY', @@ -1309,7 +1306,7 @@ export class RequestManager { deleteInput, hasDeletions: !!deleteInput.keys?.length || !!deleteInput.prefixes?.length, migrateInput, - isMigration: !!migrateInput.metas.length, + isMigration: !!updateInput.metas.length, }, { optimisticResponse: { @@ -1714,15 +1711,14 @@ export class RequestManager { } public updateSourceMeta( - sourceIds: SourceIdInfo['id'][], { - updateInput = { metas: [] }, - deleteInput = { keys: [] }, - migrateInput = { metas: [] }, + updateInput = { items: [] }, + deleteInput = { items: [] }, + migrateInput = { items: [] }, }: { - updateInput?: Omit; - deleteInput?: Omit; - migrateInput?: Omit; + updateInput?: SetSourceMetasInput; + deleteInput?: DeleteSourceMetasInput; + migrateInput?: SetSourceMetasInput; }, options?: MutationOptions, ): AbortableApolloMutationResponse { @@ -1730,69 +1726,86 @@ export class RequestManager { GQLMethod.MUTATION, UPDATE_SOURCE_METADATA, { - updateInput: { items: [{ ...updateInput, sourceIds }] }, - hasUpdates: !!updateInput.metas.length, - deleteInput: { items: [{ ...deleteInput, sourceIds }] }, - hasDeletions: !!deleteInput.keys?.length || !!deleteInput.prefixes?.length, - migrateInput: { items: [{ ...migrateInput, sourceIds }] }, - isMigration: !!migrateInput.metas.length, + updateInput, + hasUpdates: updateInput.items.some((item) => !!item.metas?.length), + deleteInput, + hasDeletions: deleteInput.items.some((item) => !!item.keys?.length || !!item.prefixes?.length), + migrateInput, + isMigration: migrateInput.items.some((item) => !!item.metas?.length), }, { optimisticResponse: { __typename: 'Mutation', updatedMeta: { __typename: 'SetSourceMetasPayload', - metas: sourceIds.flatMap((sourceId) => - (updateInput?.metas ?? []).map((meta) => ({ - __typename: 'SourceMetaType' as const, - sourceId, - key: meta.key, - value: meta.value, - })), + metas: (updateInput?.items ?? []).flatMap((item) => + item.sourceIds.flatMap((sourceId) => + item.metas.map((meta) => ({ + __typename: 'SourceMetaType' as const, + sourceId, + key: meta.key, + value: meta.value, + })), + ), ), }, deletedMeta: { __typename: 'DeleteSourceMetasPayload', - metas: sourceIds.flatMap((sourceId) => - (deleteInput?.keys ?? []).map((key) => ({ - __typename: 'SourceMetaType' as const, - sourceId, - key, - value: '', - })), + metas: (deleteInput?.items ?? []).flatMap((item) => + item.sourceIds.flatMap((sourceId) => + (item.keys ?? []).map((key) => ({ + __typename: 'SourceMetaType' as const, + sourceId, + key, + value: '', + })), + ), ), }, migrationMeta: { __typename: 'SetSourceMetasPayload', - metas: sourceIds.flatMap((sourceId) => - (migrateInput?.metas ?? []).map((meta) => ({ - __typename: 'SourceMetaType' as const, - sourceId, - key: meta.key, - value: meta.value, - })), + metas: (migrateInput?.items ?? []).flatMap((item) => + item.sourceIds.flatMap((sourceId) => + item.metas.map((meta) => ({ + __typename: 'SourceMetaType' as const, + sourceId, + key: meta.key, + value: meta.value, + })), + ), ), }, }, update(cache, { data }) { - sourceIds?.forEach((sourceId) => { - deleteInput?.keys?.forEach((key) => { - cache.evict({ id: cache.identify({ __typename: 'SourceMetaType', sourceId, key }) }); - }); - }); + deleteInput?.items.forEach((item) => + item.sourceIds.forEach((sourceId) => + item.keys?.forEach((key) => { + cache.evict({ + id: cache.identify({ __typename: 'SourceMetaType', sourceId, key }), + }); + }), + ), + ); if (!data?.updatedMeta && !data?.migrationMeta) { return; } + const sourceIds = [ + ...updateInput.items.flatMap((item) => item.sourceIds), + ...migrateInput.items.flatMap((item) => item.sourceIds), + ]; + sourceIds.forEach((sourceId) => { cache.modify({ id: cache.identify({ __typename: 'SourceType', id: sourceId }), fields: { meta(existingMetas, { readField }) { const updatedMeta = [ - ...(data?.updatedMeta?.metas ?? []), - ...(data?.migrationMeta?.metas ?? []), + ...(data?.updatedMeta?.metas.filter((meta) => meta.sourceId === sourceId) ?? + []), + ...(data?.migrationMeta?.metas.filter((meta) => meta.sourceId === sourceId) ?? + []), ]; return updateMetadataList(updatedMeta, existingMetas, readField, (meta) => @@ -2261,15 +2274,14 @@ export class RequestManager { } public updateMangaMeta( - mangaIds: MangaIdInfo['id'][], { - updateInput = { metas: [] }, - deleteInput = { keys: [] }, - migrateInput = { metas: [] }, + updateInput = { items: [] }, + deleteInput = { items: [] }, + migrateInput = { items: [] }, }: { - updateInput?: Omit; - deleteInput?: Omit; - migrateInput?: Omit; + updateInput?: SetMangaMetasInput; + deleteInput?: DeleteMangaMetasInput; + migrateInput?: SetMangaMetasInput; }, options?: MutationOptions, ): AbortableApolloMutationResponse { @@ -2277,69 +2289,85 @@ export class RequestManager { GQLMethod.MUTATION, UPDATE_MANGA_METADATA, { - updateInput: { items: [{ ...updateInput, mangaIds }] }, - hasUpdates: !!updateInput.metas.length, - deleteInput: { items: [{ ...deleteInput, mangaIds }] }, - hasDeletions: !!deleteInput.keys?.length || !!deleteInput.prefixes?.length, - migrateInput: { items: [{ ...migrateInput, mangaIds }] }, - isMigration: !!migrateInput.metas.length, + updateInput, + hasUpdates: updateInput.items.some((item) => !!item.metas?.length), + deleteInput, + hasDeletions: deleteInput.items.some((item) => !!item.keys?.length || !!item.prefixes?.length), + migrateInput, + isMigration: migrateInput.items.some((item) => !!item.metas?.length), }, { optimisticResponse: { __typename: 'Mutation', updatedMeta: { __typename: 'SetMangaMetasPayload', - metas: mangaIds.flatMap((mangaId) => - (updateInput?.metas ?? []).map((meta) => ({ - __typename: 'MangaMetaType' as const, - mangaId, - key: meta.key, - value: meta.value, - })), + metas: (updateInput?.items ?? []).flatMap((item) => + item.mangaIds.flatMap((mangaId) => + item.metas.map((meta) => ({ + __typename: 'MangaMetaType' as const, + mangaId, + key: meta.key, + value: meta.value, + })), + ), ), }, deletedMeta: { __typename: 'DeleteMangaMetasPayload', - metas: mangaIds.flatMap((mangaId) => - (deleteInput?.keys ?? []).map((key) => ({ - __typename: 'MangaMetaType' as const, - mangaId, - key, - value: '', - })), + metas: (deleteInput?.items ?? []).flatMap((item) => + item.mangaIds.flatMap((mangaId) => + (item.keys ?? []).map((key) => ({ + __typename: 'MangaMetaType' as const, + mangaId, + key, + value: '', + })), + ), ), }, migrationMeta: { __typename: 'SetMangaMetasPayload', - metas: mangaIds.flatMap((mangaId) => - (migrateInput?.metas ?? []).map((meta) => ({ - __typename: 'MangaMetaType' as const, - mangaId, - key: meta.key, - value: meta.value, - })), + metas: (migrateInput?.items ?? []).flatMap((item) => + item.mangaIds.flatMap((mangaId) => + item.metas.map((meta) => ({ + __typename: 'MangaMetaType' as const, + mangaId, + key: meta.key, + value: meta.value, + })), + ), ), }, }, update(cache, { data }) { - mangaIds.forEach((mangaId) => { - deleteInput?.keys?.forEach((key) => { - cache.evict({ id: cache.identify({ __typename: 'MangaMetaType', mangaId, key }) }); - }); - }); + deleteInput?.items.forEach((item) => + item.mangaIds.forEach((mangaId) => + item.keys?.forEach((key) => { + cache.evict({ + id: cache.identify({ __typename: 'MangaMetaType', mangaId, key }), + }); + }), + ), + ); if (!data?.updatedMeta && !data?.migrationMeta) { return; } + const mangaIds = [ + ...updateInput.items.flatMap((item) => item.mangaIds), + ...migrateInput.items.flatMap((item) => item.mangaIds), + ]; + mangaIds.forEach((mangaId) => { cache.modify({ id: cache.identify({ __typename: 'MangaType', id: mangaId }), fields: { meta(existingMetas, { readField }) { const updatedMeta = [ - ...(data?.updatedMeta?.metas ?? []), - ...(data?.migrationMeta?.metas ?? []), + ...(data?.updatedMeta?.metas.filter((meta) => meta.mangaId === mangaId) ?? []), + ...(data?.migrationMeta?.metas.filter((meta) => meta.mangaId === mangaId) ?? + []), ]; return updateMetadataList(updatedMeta, existingMetas, readField, (meta) => @@ -2514,15 +2542,14 @@ export class RequestManager { } public updateChapterMeta( - chapterIds: ChapterIdInfo['id'][], { - updateInput = { metas: [] }, - deleteInput = { keys: [] }, - migrateInput = { metas: [] }, + updateInput = { items: [] }, + deleteInput = { items: [] }, + migrateInput = { items: [] }, }: { - updateInput?: Omit; - deleteInput?: Omit; - migrateInput?: Omit; + updateInput?: SetChapterMetasInput; + deleteInput?: DeleteChapterMetasInput; + migrateInput?: SetChapterMetasInput; }, options?: MutationOptions, ): AbortableApolloMutationResponse { @@ -2530,69 +2557,86 @@ export class RequestManager { GQLMethod.MUTATION, UPDATE_CHAPTER_METADATA, { - updateInput: { items: [{ ...updateInput, chapterIds }] }, - hasUpdates: !!updateInput.metas.length, - deleteInput: { items: [{ ...deleteInput, chapterIds }] }, - hasDeletions: !!deleteInput.keys?.length || !!deleteInput.prefixes?.length, - migrateInput: { items: [{ ...migrateInput, chapterIds }] }, - isMigration: !!migrateInput.metas.length, + updateInput, + hasUpdates: updateInput.items.some((item) => !!item.metas?.length), + deleteInput, + hasDeletions: deleteInput.items.some((item) => !!item.keys?.length || !!item.prefixes?.length), + migrateInput, + isMigration: migrateInput.items.some((item) => !!item.metas?.length), }, { optimisticResponse: { __typename: 'Mutation', updatedMeta: { __typename: 'SetChapterMetasPayload', - metas: chapterIds.flatMap((chapterId) => - (updateInput?.metas ?? []).map((meta) => ({ - __typename: 'ChapterMetaType' as const, - chapterId, - key: meta.key, - value: meta.value, - })), + metas: (updateInput?.items ?? []).flatMap((item) => + item.chapterIds.flatMap((chapterId) => + item.metas.map((meta) => ({ + __typename: 'ChapterMetaType' as const, + chapterId, + key: meta.key, + value: meta.value, + })), + ), ), }, deletedMeta: { __typename: 'DeleteChapterMetasPayload', - metas: chapterIds.flatMap((chapterId) => - (deleteInput?.keys ?? []).map((key) => ({ - __typename: 'ChapterMetaType' as const, - chapterId, - key, - value: '', - })), + metas: (deleteInput?.items ?? []).flatMap((item) => + item.chapterIds.flatMap((chapterId) => + (item.keys ?? []).map((key) => ({ + __typename: 'ChapterMetaType' as const, + chapterId, + key, + value: '', + })), + ), ), }, migrationMeta: { __typename: 'SetChapterMetasPayload', - metas: chapterIds.flatMap((chapterId) => - (migrateInput?.metas ?? []).map((meta) => ({ - __typename: 'ChapterMetaType' as const, - chapterId, - key: meta.key, - value: meta.value, - })), + metas: (migrateInput?.items ?? []).flatMap((item) => + item.chapterIds.flatMap((chapterId) => + item.metas.map((meta) => ({ + __typename: 'ChapterMetaType' as const, + chapterId, + key: meta.key, + value: meta.value, + })), + ), ), }, }, update(cache, { data }) { - chapterIds.forEach((chapterId) => { - deleteInput?.keys?.forEach((key) => { - cache.evict({ id: cache.identify({ __typename: 'ChapterMetaType', chapterId, key }) }); - }); - }); + deleteInput?.items.forEach((item) => + item.chapterIds.forEach((chapterId) => + item.keys?.forEach((key) => { + cache.evict({ + id: cache.identify({ __typename: 'ChapterMetaType', chapterId, key }), + }); + }), + ), + ); if (!data?.updatedMeta && !data?.migrationMeta) { return; } + const chapterIds = [ + ...updateInput.items.flatMap((item) => item.chapterIds), + ...migrateInput.items.flatMap((item) => item.chapterIds), + ]; + chapterIds.forEach((chapterId) => { cache.modify({ id: cache.identify({ __typename: 'ChapterType', id: chapterId }), fields: { meta(existingMetas, { readField }) { const updatedMeta = [ - ...(data?.updatedMeta?.metas ?? []), - ...(data?.migrationMeta?.metas ?? []), + ...(data?.updatedMeta?.metas.filter((meta) => meta.chapterId === chapterId) ?? + []), + ...(data?.migrationMeta?.metas.filter((meta) => meta.chapterId === chapterId) ?? + []), ]; return updateMetadataList(updatedMeta, existingMetas, readField, (meta) => @@ -2818,15 +2862,14 @@ export class RequestManager { } public updateCategoryMeta( - categoryIds: CategoryIdInfo['id'][], { - updateInput = { metas: [] }, - deleteInput = { keys: [] }, - migrateInput = { metas: [] }, + updateInput = { items: [] }, + deleteInput = { items: [] }, + migrateInput = { items: [] }, }: { - updateInput?: Omit; - deleteInput?: Omit; - migrateInput?: Omit; + updateInput?: SetCategoryMetasInput; + deleteInput?: DeleteCategoryMetasInput; + migrateInput?: SetCategoryMetasInput; }, options?: MutationOptions, ): AbortableApolloMutationResponse { @@ -2834,69 +2877,87 @@ export class RequestManager { GQLMethod.MUTATION, UPDATE_CATEGORY_METADATA, { - updateInput: { items: [{ ...updateInput, categoryIds }] }, - hasUpdates: !!updateInput.metas.length, - deleteInput: { items: [{ ...deleteInput, categoryIds }] }, - hasDeletions: !!deleteInput.keys?.length || !!deleteInput.prefixes?.length, - migrateInput: { items: [{ ...migrateInput, categoryIds }] }, - isMigration: !!migrateInput.metas.length, + updateInput, + hasUpdates: updateInput.items.some((item) => !!item.metas?.length), + deleteInput, + hasDeletions: deleteInput.items.some((item) => !!item.keys?.length || !!item.prefixes?.length), + migrateInput, + isMigration: migrateInput.items.some((item) => !!item.metas?.length), }, { optimisticResponse: { __typename: 'Mutation', updatedMeta: { __typename: 'SetCategoryMetasPayload', - metas: categoryIds.flatMap((categoryId) => - (updateInput?.metas ?? []).map((meta) => ({ - __typename: 'CategoryMetaType' as const, - categoryId, - key: meta.key, - value: meta.value, - })), + metas: (updateInput?.items ?? []).flatMap((item) => + item.categoryIds.flatMap((categoryId) => + item.metas.map((meta) => ({ + __typename: 'CategoryMetaType' as const, + categoryId, + key: meta.key, + value: meta.value, + })), + ), ), }, deletedMeta: { __typename: 'DeleteCategoryMetasPayload', - metas: categoryIds.flatMap((categoryId) => - (deleteInput?.keys ?? []).map((key) => ({ - __typename: 'CategoryMetaType' as const, - categoryId, - key, - value: '', - })), + metas: (deleteInput?.items ?? []).flatMap((item) => + item.categoryIds.flatMap((categoryId) => + (item.keys ?? []).map((key) => ({ + __typename: 'CategoryMetaType' as const, + categoryId, + key, + value: '', + })), + ), ), }, migrationMeta: { __typename: 'SetCategoryMetasPayload', - metas: categoryIds.flatMap((categoryId) => - (migrateInput?.metas ?? []).map((meta) => ({ - __typename: 'CategoryMetaType' as const, - categoryId, - key: meta.key, - value: meta.value, - })), + metas: (migrateInput?.items ?? []).flatMap((item) => + item.categoryIds.flatMap((categoryId) => + item.metas.map((meta) => ({ + __typename: 'CategoryMetaType' as const, + categoryId, + key: meta.key, + value: meta.value, + })), + ), ), }, }, update(cache, { data }) { - categoryIds.forEach((categoryId) => { - deleteInput?.keys?.forEach((key) => { - cache.evict({ id: cache.identify({ __typename: 'CategoryMetaType', categoryId, key }) }); - }); - }); + deleteInput?.items.forEach((item) => + item.categoryIds.forEach((categoryId) => + item.keys?.forEach((key) => { + cache.evict({ + id: cache.identify({ __typename: 'CategoryMetaType', categoryId, key }), + }); + }), + ), + ); if (!data?.updatedMeta && !data?.migrationMeta) { return; } + const categoryIds = [ + ...updateInput.items.flatMap((item) => item.categoryIds), + ...migrateInput.items.flatMap((item) => item.categoryIds), + ]; + categoryIds.forEach((categoryId) => { cache.modify({ id: cache.identify({ __typename: 'CategoryType', id: categoryId }), fields: { meta(existingMetas, { readField }) { const updatedMeta = [ - ...(data?.updatedMeta?.metas ?? []), - ...(data?.migrationMeta?.metas ?? []), + ...(data?.updatedMeta?.metas.filter((meta) => meta.categoryId === categoryId) ?? + []), + ...(data?.migrationMeta?.metas.filter( + (meta) => meta.categoryId === categoryId, + ) ?? []), ]; return updateMetadataList(updatedMeta, existingMetas, readField, (meta) =>