Commit new metadata after migration
This commit is contained in:
@@ -6,9 +6,27 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { APP_METADATA_KEY_PREFIX, METADATA_MIGRATIONS } from '@/modules/metadata/Metadata.constants.ts';
|
import { useEffect } from 'react';
|
||||||
import { IMetadataMigration, Metadata } from '@/modules/metadata/Metadata.types.ts';
|
import {
|
||||||
import { doesMetadataKeyExistIn, getMetadataKey } from '@/modules/metadata/Metadata.utils.ts';
|
APP_METADATA_KEY_PREFIX,
|
||||||
|
METADATA_MIGRATIONS,
|
||||||
|
VALID_APP_METADATA_KEYS,
|
||||||
|
} from '@/modules/metadata/Metadata.constants.ts';
|
||||||
|
import {
|
||||||
|
AppMetadataKeys,
|
||||||
|
IMetadataMigration,
|
||||||
|
Metadata,
|
||||||
|
MetadataHolder,
|
||||||
|
MetadataHolderType,
|
||||||
|
MetadataKeyValuePair,
|
||||||
|
} from '@/modules/metadata/Metadata.types.ts';
|
||||||
|
import { doesMetadataKeyExistIn, extractOriginalKey, getMetadataKey } from '@/modules/metadata/Metadata.utils.ts';
|
||||||
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts';
|
||||||
|
import { CategoryIdInfo } from '@/modules/category/Category.types.ts';
|
||||||
|
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import { getMetadataDeleteFunction, getMetadataUpdateFunction } from '@/modules/metadata/services/MetadataUpdater.ts';
|
||||||
|
|
||||||
const getAppKeyPrefixForMigration = (migrationId: number): string => {
|
const getAppKeyPrefixForMigration = (migrationId: number): string => {
|
||||||
const appKeyPrefix = METADATA_MIGRATIONS.slice(0, migrationId)
|
const appKeyPrefix = METADATA_MIGRATIONS.slice(0, migrationId)
|
||||||
@@ -118,20 +136,159 @@ const applyMetadataKeyMigration = (meta: Metadata, migration: IMetadataMigration
|
|||||||
return migratedMetadata;
|
return migratedMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const applyMetadataMigrations = (meta?: Metadata): Metadata | undefined => {
|
const getOutdatedMetadataKeys = (metadata?: Metadata): AppMetadataKeys[] => {
|
||||||
if (!meta) {
|
if (!metadata) {
|
||||||
return undefined;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldAppKeyPrefixes = METADATA_MIGRATIONS.reduce((acc, migration) => {
|
||||||
|
const oldPrefix = migration.appKeyPrefix?.oldPrefix;
|
||||||
|
if (!oldPrefix) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...acc, oldPrefix];
|
||||||
|
}, [] as string[]);
|
||||||
|
|
||||||
|
return Object.keys(metadata).filter((key) => {
|
||||||
|
const appKeyPrefixOfKey = key.split('_')[0];
|
||||||
|
|
||||||
|
const isMetadataKeyOfApp = [...oldAppKeyPrefixes, APP_METADATA_KEY_PREFIX].includes(appKeyPrefixOfKey);
|
||||||
|
if (!isMetadataKeyOfApp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOldKeyPrefix = oldAppKeyPrefixes.includes(appKeyPrefixOfKey);
|
||||||
|
if (isOldKeyPrefix) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !VALID_APP_METADATA_KEYS.includes(extractOriginalKey(key));
|
||||||
|
}) as AppMetadataKeys[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNewMetadataKeys = (
|
||||||
|
metadata: Metadata | undefined,
|
||||||
|
migratedMetadata: Metadata,
|
||||||
|
metadataKeyToDelete: string[],
|
||||||
|
): string[] => {
|
||||||
|
if (!metadata) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const newKeys = METADATA_MIGRATIONS.reduce((acc, migration) => {
|
||||||
|
if (!migration.keys) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = migration.keys.map(({ newKey }) => newKey).filter((key) => key !== undefined);
|
||||||
|
|
||||||
|
return [...acc, ...keys];
|
||||||
|
}, [] as string[]);
|
||||||
|
|
||||||
|
return Object.keys(migratedMetadata).filter((metadataKey) => {
|
||||||
|
const key = extractOriginalKey(metadataKey);
|
||||||
|
|
||||||
|
const isNewKeyOfAMigration = newKeys.includes(key);
|
||||||
|
const isOldKeyOfAMigration = metadataKeyToDelete.includes(key);
|
||||||
|
const isAlreadyCommited = Object.hasOwn(metadata, metadataKey);
|
||||||
|
|
||||||
|
return !isAlreadyCommited && isNewKeyOfAMigration && !isOldKeyOfAMigration;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent spamming requests due to frequent metadata reads while the migration hasn't been commited to the server yet
|
||||||
|
*/
|
||||||
|
const commitedMigrations = new Set<string>();
|
||||||
|
const commitMigratedMetadata = (
|
||||||
|
type: MetadataHolderType,
|
||||||
|
metadataHolder:
|
||||||
|
| MetadataHolder
|
||||||
|
| (MangaIdInfo & MetadataHolder)
|
||||||
|
| (ChapterIdInfo & MetadataHolder)
|
||||||
|
| (CategoryIdInfo & MetadataHolder)
|
||||||
|
| (Pick<SourceType, 'id'> & MetadataHolder)
|
||||||
|
| undefined,
|
||||||
|
migratedMetadata: Metadata,
|
||||||
|
useEffectFn: typeof useEffect = (fn: () => void) => fn(),
|
||||||
|
): void => {
|
||||||
|
const metadata = metadataHolder?.meta;
|
||||||
|
|
||||||
|
const metadataKeysToDelete = getOutdatedMetadataKeys(metadata);
|
||||||
|
const metadataKeysToCommit = getNewMetadataKeys(metadata, migratedMetadata, metadataKeysToDelete);
|
||||||
|
const metadataToUpdate = metadataKeysToCommit.map((key) => [key, migratedMetadata[key]]) as MetadataKeyValuePair[];
|
||||||
|
|
||||||
|
const updateMetadata = getMetadataUpdateFunction(type, metadataHolder ?? { id: -1, meta: {} });
|
||||||
|
const deleteMetadata = getMetadataDeleteFunction(type, metadataHolder ?? { id: -1, meta: {} });
|
||||||
|
|
||||||
|
useEffectFn(() => {
|
||||||
|
(async () => {
|
||||||
|
const itemMigrationKey = `${type}_${type === 'global' ? '' : (metadataHolder as { id: any }).id}`;
|
||||||
|
|
||||||
|
const commitMigration = !commitedMigrations.has(itemMigrationKey);
|
||||||
|
if (!commitMigration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMetadataAlreadyMigrated =
|
||||||
|
!metadata || Number(metadata[getMetadataKey('migration')]) === METADATA_MIGRATIONS.length;
|
||||||
|
|
||||||
|
const isCommitRequired =
|
||||||
|
!isMetadataAlreadyMigrated || !!metadataKeysToDelete.length || !!metadataToUpdate.length;
|
||||||
|
if (!isCommitRequired) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commitedMigrations.add(itemMigrationKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteMetadata(metadataKeysToDelete, undefined, true);
|
||||||
|
await updateMetadata(
|
||||||
|
[...metadataToUpdate, [getMetadataKey('migration') as AppMetadataKeys, METADATA_MIGRATIONS.length]],
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
commitedMigrations.delete(itemMigrationKey);
|
||||||
|
|
||||||
|
defaultPromiseErrorHandler(
|
||||||
|
`MetadataMigrations#commitMigrateMetadata(${type}, ${(metadataHolder as { id: any })?.id})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyMetadataMigrations = (
|
||||||
|
type: MetadataHolderType,
|
||||||
|
metadataHolder?:
|
||||||
|
| MetadataHolder
|
||||||
|
| (MangaIdInfo & MetadataHolder)
|
||||||
|
| (ChapterIdInfo & MetadataHolder)
|
||||||
|
| (CategoryIdInfo & MetadataHolder)
|
||||||
|
| (Pick<SourceType, 'id'> & MetadataHolder),
|
||||||
|
useEffectFn: typeof useEffect = (fn: () => void) => fn(),
|
||||||
|
): Metadata | undefined => {
|
||||||
|
const meta = { ...(metadataHolder?.meta ?? {}) };
|
||||||
|
|
||||||
const migrationIdKey = getMetadataKey('migration');
|
const migrationIdKey = getMetadataKey('migration');
|
||||||
const appliedMigrationId = Number.isNaN(Number(meta[migrationIdKey]))
|
const appliedMigrationId = Number.isNaN(Number(meta[migrationIdKey]))
|
||||||
? 0
|
? 0
|
||||||
: Math.max(0, Number(meta[migrationIdKey]) - 1);
|
: Math.max(0, Number(meta[migrationIdKey]) - 1);
|
||||||
const migrationToMetadata: [number, Metadata][] = [[appliedMigrationId, meta]];
|
|
||||||
|
const migrationToMetadata: [number, Metadata][] = [[0, meta]];
|
||||||
|
|
||||||
METADATA_MIGRATIONS.forEach((migration, index) => {
|
METADATA_MIGRATIONS.forEach((migration, index) => {
|
||||||
const migrationId = index + 1;
|
const migrationId = index + 1;
|
||||||
const metadataToMigrate = migrationToMetadata[migrationId - 1][1];
|
const metadataToMigrate = migrationToMetadata[migrationId - 1][1];
|
||||||
|
|
||||||
|
const isMigrationRequired = appliedMigrationId < migrationId;
|
||||||
|
if (!isMigrationRequired) {
|
||||||
|
migrationToMetadata.push([migrationId, metadataToMigrate]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const appKeyPrefixMigrated = applyAppKeyPrefixMigration(metadataToMigrate, migration);
|
const appKeyPrefixMigrated = applyAppKeyPrefixMigration(metadataToMigrate, migration);
|
||||||
const metadataValuesMigrated = applyMetadataValueMigration(
|
const metadataValuesMigrated = applyMetadataValueMigration(
|
||||||
appKeyPrefixMigrated,
|
appKeyPrefixMigrated,
|
||||||
@@ -143,10 +300,9 @@ export const applyMetadataMigrations = (meta?: Metadata): Metadata | undefined =
|
|||||||
migrationToMetadata.push([migrationId, metadataKeysMigrated]);
|
migrationToMetadata.push([migrationId, metadataKeysMigrated]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const appliedMigration = migrationToMetadata.length > 1;
|
const migratedMetadata = migrationToMetadata.pop()?.[1] ?? meta;
|
||||||
if (!appliedMigration) {
|
|
||||||
return { ...meta };
|
|
||||||
}
|
|
||||||
|
|
||||||
return migrationToMetadata.pop()![1];
|
commitMigratedMetadata(type, metadataHolder, migratedMetadata, useEffectFn);
|
||||||
|
|
||||||
|
return migratedMetadata;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,34 +7,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { METADATA_MIGRATIONS, VALID_APP_METADATA_KEYS } from '@/modules/metadata/Metadata.constants.ts';
|
import { convertValueFromMetadata } from '@/modules/metadata/services/MetadataConverter.ts';
|
||||||
import { convertToGqlMeta, convertValueFromMetadata } from '@/modules/metadata/services/MetadataConverter.ts';
|
|
||||||
import {
|
import {
|
||||||
AllowedMetadataValueTypes,
|
AllowedMetadataValueTypes,
|
||||||
AppMetadataKeys,
|
AppMetadataKeys,
|
||||||
Metadata,
|
Metadata,
|
||||||
MetadataHolder,
|
MetadataHolder,
|
||||||
MetadataHolderType,
|
MetadataHolderType,
|
||||||
MetadataKeyValuePair,
|
|
||||||
} from '@/modules/metadata/Metadata.types.ts';
|
} from '@/modules/metadata/Metadata.types.ts';
|
||||||
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
import {
|
|
||||||
requestDeleteCategoryMetadata,
|
|
||||||
requestDeleteChapterMetadata,
|
|
||||||
requestDeleteMangaMetadata,
|
|
||||||
requestDeleteServerMetadata,
|
|
||||||
requestDeleteSourceMetadata,
|
|
||||||
requestUpdateCategoryMetadata,
|
|
||||||
requestUpdateChapterMetadata,
|
|
||||||
requestUpdateMangaMetadata,
|
|
||||||
requestUpdateServerMetadata,
|
|
||||||
requestUpdateSourceMetadata,
|
|
||||||
} from '@/modules/metadata/services/MetadataUpdater.ts';
|
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
|
||||||
import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts';
|
import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts';
|
||||||
import { CategoryIdInfo } from '@/modules/category/Category.types.ts';
|
import { CategoryIdInfo } from '@/modules/category/Category.types.ts';
|
||||||
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
|
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { doesMetadataKeyExistIn, extractOriginalKey, getMetadataKey } from '@/modules/metadata/Metadata.utils.ts';
|
import { doesMetadataKeyExistIn, getMetadataKey } from '@/modules/metadata/Metadata.utils.ts';
|
||||||
import { applyMetadataMigrations } from '@/modules/metadata/services/MetadataMigrations.ts';
|
import { applyMetadataMigrations } from '@/modules/metadata/services/MetadataMigrations.ts';
|
||||||
|
|
||||||
const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends AllowedMetadataValueTypes>(
|
const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends AllowedMetadataValueTypes>(
|
||||||
@@ -54,99 +40,6 @@ const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends Allowed
|
|||||||
return convertValueFromMetadata(metadata[getMetadataKey(key, prefixes)]);
|
return convertValueFromMetadata(metadata[getMetadataKey(key, prefixes)]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMetadataUpdateFunction = (
|
|
||||||
type: MetadataHolderType,
|
|
||||||
metadataHolder:
|
|
||||||
| MetadataHolder
|
|
||||||
| (MangaIdInfo & MetadataHolder)
|
|
||||||
| (ChapterIdInfo & MetadataHolder)
|
|
||||||
| (CategoryIdInfo & MetadataHolder)
|
|
||||||
| (Pick<SourceType, 'id'> & MetadataHolder),
|
|
||||||
): ((appliedMigration: [MetadataKeyValuePair]) => Promise<void[]>) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'global':
|
|
||||||
return (appliedMigration) => requestUpdateServerMetadata(appliedMigration);
|
|
||||||
case 'manga':
|
|
||||||
return (appliedMigration) =>
|
|
||||||
requestUpdateMangaMetadata(
|
|
||||||
{ id: (metadataHolder as MangaIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
|
||||||
appliedMigration,
|
|
||||||
);
|
|
||||||
case 'chapter':
|
|
||||||
return (appliedMigration) =>
|
|
||||||
requestUpdateChapterMetadata(
|
|
||||||
{ id: (metadataHolder as ChapterIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
|
||||||
appliedMigration,
|
|
||||||
);
|
|
||||||
case 'category':
|
|
||||||
return (appliedMigration) =>
|
|
||||||
requestUpdateCategoryMetadata(
|
|
||||||
{ id: (metadataHolder as CategoryIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
|
||||||
appliedMigration,
|
|
||||||
);
|
|
||||||
case 'source':
|
|
||||||
return (appliedMigration) =>
|
|
||||||
requestUpdateSourceMetadata(
|
|
||||||
{
|
|
||||||
id: (metadataHolder as Pick<SourceType, 'id'>).id,
|
|
||||||
meta: convertToGqlMeta(metadataHolder.meta),
|
|
||||||
},
|
|
||||||
appliedMigration,
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw new Error(`Unexpected "type" (${type})`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMetadataDeleteFunction = (
|
|
||||||
type: MetadataHolderType,
|
|
||||||
metadataHolder:
|
|
||||||
| MetadataHolder
|
|
||||||
| (MangaIdInfo & MetadataHolder)
|
|
||||||
| (ChapterIdInfo & MetadataHolder)
|
|
||||||
| (CategoryIdInfo & MetadataHolder)
|
|
||||||
| (Pick<SourceType, 'id'> & MetadataHolder),
|
|
||||||
): ((metadataToDelete: AppMetadataKeys[]) => Promise<void[]>) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'global':
|
|
||||||
return (appliedMigration) => requestDeleteServerMetadata(appliedMigration);
|
|
||||||
case 'manga':
|
|
||||||
return (appliedMigration) =>
|
|
||||||
requestDeleteMangaMetadata(
|
|
||||||
{ id: (metadataHolder as MangaIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
|
||||||
appliedMigration,
|
|
||||||
);
|
|
||||||
case 'chapter':
|
|
||||||
return (appliedMigration) =>
|
|
||||||
requestDeleteChapterMetadata(
|
|
||||||
{ id: (metadataHolder as ChapterIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
|
||||||
appliedMigration,
|
|
||||||
);
|
|
||||||
case 'category':
|
|
||||||
return (appliedMigration) =>
|
|
||||||
requestDeleteCategoryMetadata(
|
|
||||||
{ id: (metadataHolder as CategoryIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
|
||||||
appliedMigration,
|
|
||||||
);
|
|
||||||
case 'source':
|
|
||||||
return (appliedMigration) =>
|
|
||||||
requestDeleteSourceMetadata(
|
|
||||||
{
|
|
||||||
id: (metadataHolder as Pick<SourceType, 'id'>).id,
|
|
||||||
meta: convertToGqlMeta(metadataHolder.meta),
|
|
||||||
},
|
|
||||||
appliedMigration,
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw new Error(`Unexpected "type" (${type})`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prevent spamming requests due to frequent metadata reads while the migration hasn't been commited to the server yet
|
|
||||||
*/
|
|
||||||
const commitedMigrations = new Set<string>();
|
|
||||||
|
|
||||||
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
|
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
|
||||||
type: 'global',
|
type: 'global',
|
||||||
metadataHolder: MetadataHolder,
|
metadataHolder: MetadataHolder,
|
||||||
@@ -194,11 +87,7 @@ export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKey
|
|||||||
prefixes?: string[],
|
prefixes?: string[],
|
||||||
useEffectFn: typeof useEffect = (fn: () => void) => fn(),
|
useEffectFn: typeof useEffect = (fn: () => void) => fn(),
|
||||||
): METADATA {
|
): METADATA {
|
||||||
const wasMigrated =
|
const migratedMetadata = applyMetadataMigrations(type, metadataHolder, useEffectFn);
|
||||||
!!metadataHolder?.meta &&
|
|
||||||
Number(getMetadataValueFrom(metadataHolder.meta, 'migration', 0)) !== METADATA_MIGRATIONS.length;
|
|
||||||
|
|
||||||
const migratedMetadata = applyMetadataMigrations(metadataHolder.meta);
|
|
||||||
const appMetadata = {} as METADATA;
|
const appMetadata = {} as METADATA;
|
||||||
|
|
||||||
Object.entries(metadataWithDefaultValues).forEach(([key, defaultValue]) => {
|
Object.entries(metadataWithDefaultValues).forEach(([key, defaultValue]) => {
|
||||||
@@ -210,29 +99,5 @@ export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKey
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffectFn(() => {
|
|
||||||
const itemMigrationKey = `${type}_${type === 'global' ? '' : (metadataHolder as { id: any }).id}`;
|
|
||||||
const commitMigration = !commitedMigrations.has(itemMigrationKey);
|
|
||||||
if (wasMigrated && commitMigration) {
|
|
||||||
const metadataToDelete = Object.keys(metadataHolder.meta ?? {})
|
|
||||||
.map(extractOriginalKey)
|
|
||||||
.filter((key) => !VALID_APP_METADATA_KEYS.includes(key)) as AppMetadataKeys[];
|
|
||||||
|
|
||||||
commitedMigrations.add(itemMigrationKey);
|
|
||||||
|
|
||||||
getMetadataDeleteFunction(
|
|
||||||
type,
|
|
||||||
metadataHolder,
|
|
||||||
)(metadataToDelete)
|
|
||||||
.then(() =>
|
|
||||||
getMetadataUpdateFunction(type, metadataHolder)([['migration', METADATA_MIGRATIONS.length]]),
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
defaultPromiseErrorHandler(`MetadataReader#getMetadataFromServer`)(error);
|
|
||||||
commitedMigrations.delete(itemMigrationKey);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return appMetadata;
|
return appMetadata;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ import {
|
|||||||
AllowedMetadataValueTypes,
|
AllowedMetadataValueTypes,
|
||||||
AppMetadataKeys,
|
AppMetadataKeys,
|
||||||
GqlMetaHolder,
|
GqlMetaHolder,
|
||||||
|
MetadataHolder,
|
||||||
MetadataHolderType,
|
MetadataHolderType,
|
||||||
MetadataKeyValuePair,
|
MetadataKeyValuePair,
|
||||||
} from '@/modules/metadata/Metadata.types.ts';
|
} from '@/modules/metadata/Metadata.types.ts';
|
||||||
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
import { getMetadataKey } from '@/modules/metadata/Metadata.utils.ts';
|
import { getMetadataKey } from '@/modules/metadata/Metadata.utils.ts';
|
||||||
|
import { convertToGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
|
||||||
|
|
||||||
const requestUpdateMetadataValue = async (
|
const requestUpdateMetadataValue = async (
|
||||||
metadataHolder: GqlMetaHolder,
|
metadataHolder: GqlMetaHolder,
|
||||||
@@ -26,8 +28,9 @@ const requestUpdateMetadataValue = async (
|
|||||||
key: AppMetadataKeys,
|
key: AppMetadataKeys,
|
||||||
value: AllowedMetadataValueTypes,
|
value: AllowedMetadataValueTypes,
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
|
isMetadataKey: boolean = false,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const metadataKey = getMetadataKey(key, keyPrefixes);
|
const metadataKey = isMetadataKey ? key : getMetadataKey(key, keyPrefixes);
|
||||||
|
|
||||||
switch (holderType) {
|
switch (holderType) {
|
||||||
case 'category':
|
case 'category':
|
||||||
@@ -56,49 +59,100 @@ const requestUpdateMetadata = async (
|
|||||||
holderType: MetadataHolderType,
|
holderType: MetadataHolderType,
|
||||||
keysToValues: MetadataKeyValuePair[],
|
keysToValues: MetadataKeyValuePair[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
|
isMetadataKey?: boolean,
|
||||||
): Promise<void[]> =>
|
): Promise<void[]> =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
keysToValues.map(([key, value]) =>
|
keysToValues.map(([key, value]) =>
|
||||||
requestUpdateMetadataValue(metadataHolder, holderType, key, value, keyPrefixes),
|
requestUpdateMetadataValue(metadataHolder, holderType, key, value, keyPrefixes, isMetadataKey),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const requestUpdateServerMetadata = async (
|
export const requestUpdateServerMetadata = async (
|
||||||
keysToValues: MetadataKeyValuePair[],
|
keysToValues: MetadataKeyValuePair[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
): Promise<void[]> => requestUpdateMetadata({}, 'global', keysToValues, keyPrefixes);
|
isMetadataKey?: boolean,
|
||||||
|
): Promise<void[]> => requestUpdateMetadata({}, 'global', keysToValues, keyPrefixes, isMetadataKey);
|
||||||
|
|
||||||
export const requestUpdateMangaMetadata = async (
|
export const requestUpdateMangaMetadata = async (
|
||||||
manga: MangaIdInfo & GqlMetaHolder,
|
manga: MangaIdInfo & GqlMetaHolder,
|
||||||
keysToValues: MetadataKeyValuePair[],
|
keysToValues: MetadataKeyValuePair[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
): Promise<void[]> => requestUpdateMetadata(manga, 'manga', keysToValues, keyPrefixes);
|
isMetadataKey?: boolean,
|
||||||
|
): Promise<void[]> => requestUpdateMetadata(manga, 'manga', keysToValues, keyPrefixes, isMetadataKey);
|
||||||
|
|
||||||
export const requestUpdateChapterMetadata = async (
|
export const requestUpdateChapterMetadata = async (
|
||||||
chapter: ChapterIdInfo & GqlMetaHolder,
|
chapter: ChapterIdInfo & GqlMetaHolder,
|
||||||
keysToValues: MetadataKeyValuePair[],
|
keysToValues: MetadataKeyValuePair[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
): Promise<void[]> => requestUpdateMetadata(chapter, 'chapter', keysToValues, keyPrefixes);
|
isMetadataKey?: boolean,
|
||||||
|
): Promise<void[]> => requestUpdateMetadata(chapter, 'chapter', keysToValues, keyPrefixes, isMetadataKey);
|
||||||
|
|
||||||
export const requestUpdateCategoryMetadata = async (
|
export const requestUpdateCategoryMetadata = async (
|
||||||
category: CategoryIdInfo & GqlMetaHolder,
|
category: CategoryIdInfo & GqlMetaHolder,
|
||||||
keysToValues: MetadataKeyValuePair[],
|
keysToValues: MetadataKeyValuePair[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
): Promise<void[]> => requestUpdateMetadata(category, 'category', keysToValues, keyPrefixes);
|
isMetadataKey?: boolean,
|
||||||
|
): Promise<void[]> => requestUpdateMetadata(category, 'category', keysToValues, keyPrefixes, isMetadataKey);
|
||||||
|
|
||||||
export const requestUpdateSourceMetadata = async (
|
export const requestUpdateSourceMetadata = async (
|
||||||
source: Pick<SourceType, 'id'> & GqlMetaHolder,
|
source: Pick<SourceType, 'id'> & GqlMetaHolder,
|
||||||
keysToValue: MetadataKeyValuePair[],
|
keysToValue: MetadataKeyValuePair[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
): Promise<void[]> => requestUpdateMetadata(source, 'source', keysToValue, keyPrefixes);
|
isMetadataKey?: boolean,
|
||||||
|
): Promise<void[]> => requestUpdateMetadata(source, 'source', keysToValue, keyPrefixes, isMetadataKey);
|
||||||
|
|
||||||
|
export const getMetadataUpdateFunction = (
|
||||||
|
type: MetadataHolderType,
|
||||||
|
metadataHolder:
|
||||||
|
| MetadataHolder
|
||||||
|
| (MangaIdInfo & MetadataHolder)
|
||||||
|
| (ChapterIdInfo & MetadataHolder)
|
||||||
|
| (CategoryIdInfo & MetadataHolder)
|
||||||
|
| (Pick<SourceType, 'id'> & MetadataHolder),
|
||||||
|
): ((keyValuePair: MetadataKeyValuePair[], keyPrefixes?: string[], isMetadataKey?: boolean) => Promise<void[]>) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'global':
|
||||||
|
return (...args) => requestUpdateServerMetadata(...args);
|
||||||
|
case 'manga':
|
||||||
|
return (...args) =>
|
||||||
|
requestUpdateMangaMetadata(
|
||||||
|
{ id: (metadataHolder as MangaIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
case 'chapter':
|
||||||
|
return (...args) =>
|
||||||
|
requestUpdateChapterMetadata(
|
||||||
|
{ id: (metadataHolder as ChapterIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
case 'category':
|
||||||
|
return (...args) =>
|
||||||
|
requestUpdateCategoryMetadata(
|
||||||
|
{ id: (metadataHolder as CategoryIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
case 'source':
|
||||||
|
return (...args) =>
|
||||||
|
requestUpdateSourceMetadata(
|
||||||
|
{
|
||||||
|
id: (metadataHolder as Pick<SourceType, 'id'>).id,
|
||||||
|
meta: convertToGqlMeta(metadataHolder.meta),
|
||||||
|
},
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected "type" (${type})`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const requestDeleteMetadataValue = async (
|
export const requestDeleteMetadataValue = async (
|
||||||
metadataHolder: GqlMetaHolder,
|
metadataHolder: GqlMetaHolder,
|
||||||
holderType: MetadataHolderType,
|
holderType: MetadataHolderType,
|
||||||
key: AppMetadataKeys,
|
key: AppMetadataKeys,
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
|
isMetadataKey: boolean = false,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const metadataKey = getMetadataKey(key, keyPrefixes);
|
const metadataKey = isMetadataKey ? key : getMetadataKey(key, keyPrefixes);
|
||||||
|
|
||||||
switch (holderType) {
|
switch (holderType) {
|
||||||
case 'category':
|
case 'category':
|
||||||
@@ -126,33 +180,87 @@ async function requestDeleteMetadata(
|
|||||||
holderType: MetadataHolderType,
|
holderType: MetadataHolderType,
|
||||||
keys: AppMetadataKeys[],
|
keys: AppMetadataKeys[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
|
isMetadataKey?: boolean,
|
||||||
): Promise<void[]> {
|
): Promise<void[]> {
|
||||||
return Promise.all(keys.map((key) => requestDeleteMetadataValue(metadataHolder, holderType, key, keyPrefixes)));
|
return Promise.all(
|
||||||
|
keys.map((key) => requestDeleteMetadataValue(metadataHolder, holderType, key, keyPrefixes, isMetadataKey)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const requestDeleteServerMetadata = async (keys: AppMetadataKeys[], keyPrefixes?: string[]): Promise<void[]> =>
|
export const requestDeleteServerMetadata = async (
|
||||||
requestDeleteMetadata({}, 'global', keys, keyPrefixes);
|
keys: AppMetadataKeys[],
|
||||||
|
keyPrefixes?: string[],
|
||||||
|
isMetadataKey?: boolean,
|
||||||
|
): Promise<void[]> => requestDeleteMetadata({}, 'global', keys, keyPrefixes, isMetadataKey);
|
||||||
|
|
||||||
export const requestDeleteMangaMetadata = async (
|
export const requestDeleteMangaMetadata = async (
|
||||||
manga: MangaIdInfo & GqlMetaHolder,
|
manga: MangaIdInfo & GqlMetaHolder,
|
||||||
keys: AppMetadataKeys[],
|
keys: AppMetadataKeys[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
): Promise<void[]> => requestDeleteMetadata(manga, 'manga', keys, keyPrefixes);
|
isMetadataKey?: boolean,
|
||||||
|
): Promise<void[]> => requestDeleteMetadata(manga, 'manga', keys, keyPrefixes, isMetadataKey);
|
||||||
|
|
||||||
export const requestDeleteChapterMetadata = async (
|
export const requestDeleteChapterMetadata = async (
|
||||||
chapter: ChapterIdInfo & GqlMetaHolder,
|
chapter: ChapterIdInfo & GqlMetaHolder,
|
||||||
keys: AppMetadataKeys[],
|
keys: AppMetadataKeys[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
): Promise<void[]> => requestDeleteMetadata(chapter, 'chapter', keys, keyPrefixes);
|
isMetadataKey?: boolean,
|
||||||
|
): Promise<void[]> => requestDeleteMetadata(chapter, 'chapter', keys, keyPrefixes, isMetadataKey);
|
||||||
|
|
||||||
export const requestDeleteCategoryMetadata = async (
|
export const requestDeleteCategoryMetadata = async (
|
||||||
category: CategoryIdInfo & GqlMetaHolder,
|
category: CategoryIdInfo & GqlMetaHolder,
|
||||||
keys: AppMetadataKeys[],
|
keys: AppMetadataKeys[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
): Promise<void[]> => requestDeleteMetadata(category, 'category', keys, keyPrefixes);
|
isMetadataKey?: boolean,
|
||||||
|
): Promise<void[]> => requestDeleteMetadata(category, 'category', keys, keyPrefixes, isMetadataKey);
|
||||||
|
|
||||||
export const requestDeleteSourceMetadata = async (
|
export const requestDeleteSourceMetadata = async (
|
||||||
source: Pick<SourceType, 'id'> & GqlMetaHolder,
|
source: Pick<SourceType, 'id'> & GqlMetaHolder,
|
||||||
keys: AppMetadataKeys[],
|
keys: AppMetadataKeys[],
|
||||||
keyPrefixes?: string[],
|
keyPrefixes?: string[],
|
||||||
): Promise<void[]> => requestDeleteMetadata(source, 'source', keys, keyPrefixes);
|
isMetadataKey?: boolean,
|
||||||
|
): Promise<void[]> => requestDeleteMetadata(source, 'source', keys, keyPrefixes, isMetadataKey);
|
||||||
|
|
||||||
|
export const getMetadataDeleteFunction = (
|
||||||
|
type: MetadataHolderType,
|
||||||
|
metadataHolder:
|
||||||
|
| MetadataHolder
|
||||||
|
| (MangaIdInfo & MetadataHolder)
|
||||||
|
| (ChapterIdInfo & MetadataHolder)
|
||||||
|
| (CategoryIdInfo & MetadataHolder)
|
||||||
|
| (Pick<SourceType, 'id'> & MetadataHolder),
|
||||||
|
): ((metadataToDelete: AppMetadataKeys[], keyPrefixes?: string[], isMetadataKey?: boolean) => Promise<void[]>) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'global':
|
||||||
|
return (...args) => requestDeleteServerMetadata(...args);
|
||||||
|
case 'manga':
|
||||||
|
return (...args) =>
|
||||||
|
requestDeleteMangaMetadata(
|
||||||
|
{ id: (metadataHolder as MangaIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
case 'chapter':
|
||||||
|
return (...args) =>
|
||||||
|
requestDeleteChapterMetadata(
|
||||||
|
{ id: (metadataHolder as ChapterIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
case 'category':
|
||||||
|
return (...args) =>
|
||||||
|
requestDeleteCategoryMetadata(
|
||||||
|
{ id: (metadataHolder as CategoryIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
case 'source':
|
||||||
|
return (...args) =>
|
||||||
|
requestDeleteSourceMetadata(
|
||||||
|
{
|
||||||
|
id: (metadataHolder as Pick<SourceType, 'id'>).id,
|
||||||
|
meta: convertToGqlMeta(metadataHolder.meta),
|
||||||
|
},
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected "type" (${type})`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user