Feature/improve typing of metadata related logic (#268)

* Improve metadata typing

* Rename types

Still included the "I" prefix for interfaces

* Improve typing in setting util files
This commit is contained in:
schroda
2023-04-22 11:34:19 +02:00
committed by GitHub
parent 0a56a2f6d4
commit 4157611d83
4 changed files with 89 additions and 92 deletions

View File

@@ -74,13 +74,13 @@ export interface IMetadataMigration {
keys?: { oldKey: string; newKey: string }[]; keys?: { oldKey: string; newKey: string }[];
} }
export interface IMetadata<VALUES extends AllowedMetadataValueTypes = string> { export type Metadata<Keys extends string = string, Values = string> = {
[key: string]: VALUES; [key in Keys]: Values;
} };
export interface IMetadataHolder<VALUES extends AllowedMetadataValueTypes = string> { export type MetadataHolder<Keys extends string = string, Values = string> = {
meta?: IMetadata<VALUES>; meta?: Metadata<Keys, Values>;
} };
export type AllowedMetadataValueTypes = string | boolean | number | undefined; export type AllowedMetadataValueTypes = string | boolean | number | undefined;
@@ -100,7 +100,7 @@ export interface IMangaCard {
unreadCount?: number; unreadCount?: number;
downloadCount?: number; downloadCount?: number;
inLibrary?: boolean; inLibrary?: boolean;
meta?: IMetadata; meta?: Metadata;
inLibraryAt: number; inLibraryAt: number;
lastReadAt: number; lastReadAt: number;
} }
@@ -122,7 +122,7 @@ export interface IManga {
inLibrary: boolean; inLibrary: boolean;
source: ISource; source: ISource;
meta: IMetadata; meta: Metadata;
realUrl: string; realUrl: string;
freshData: boolean; freshData: boolean;
@@ -158,7 +158,7 @@ export interface IChapter {
chapterCount: number; chapterCount: number;
pageCount: number; pageCount: number;
downloaded: boolean; downloaded: boolean;
meta: IMetadata; meta: Metadata;
} }
export interface IMangaChapter { export interface IMangaChapter {
@@ -178,7 +178,7 @@ export interface ICategory {
order: number; order: number;
name: string; name: string;
default: boolean; default: boolean;
meta: IMetadata; meta: Metadata;
} }
export interface INavbarOverride { export interface INavbarOverride {

View File

@@ -15,8 +15,8 @@ import {
IManga, IManga,
IMangaCard, IMangaCard,
IMangaChapter, IMangaChapter,
IMetadata, Metadata,
IMetadataHolder, MetadataHolder,
IMetadataMigration, IMetadataMigration,
MetadataKeyValuePair, MetadataKeyValuePair,
} from 'typings'; } from 'typings';
@@ -96,7 +96,7 @@ const getAppKeyPrefixForMigration = (migrationId: number): string => {
const getMetadataKey = (key: string, appPrefix: string = APP_METADATA_KEY_PREFIX) => `${appPrefix}${key}`; const getMetadataKey = (key: string, appPrefix: string = APP_METADATA_KEY_PREFIX) => `${appPrefix}${key}`;
const doesMetadataKeyExistIn = (meta: IMetadata | undefined, key: string, appPrefix?: string): boolean => const doesMetadataKeyExistIn = (meta: Metadata | undefined, key: string, appPrefix?: string): boolean =>
Object.prototype.hasOwnProperty.call(meta ?? {}, getMetadataKey(key, appPrefix)); Object.prototype.hasOwnProperty.call(meta ?? {}, getMetadataKey(key, appPrefix));
const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes>( const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes>(
@@ -117,8 +117,8 @@ const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = AllowedM
return value as T; return value as T;
}; };
const getAppMetadataFrom = (meta: IMetadata, appPrefix: string = APP_METADATA_KEY_PREFIX): IMetadata => { const getAppMetadataFrom = (meta: Metadata, appPrefix: string = APP_METADATA_KEY_PREFIX): Metadata => {
const appMetadata: IMetadata = {}; const appMetadata: Metadata = {};
Object.entries(meta).forEach(([key, value]) => { Object.entries(meta).forEach(([key, value]) => {
if (key.startsWith(appPrefix)) { if (key.startsWith(appPrefix)) {
@@ -129,8 +129,8 @@ const getAppMetadataFrom = (meta: IMetadata, appPrefix: string = APP_METADATA_KE
return appMetadata; return appMetadata;
}; };
const applyAppKeyPrefixMigration = (meta: IMetadata, migration: IMetadataMigration): IMetadata => { const applyAppKeyPrefixMigration = (meta: Metadata, migration: IMetadataMigration): Metadata => {
const migratedMetadata: IMetadata = { ...meta }; const migratedMetadata: Metadata = { ...meta };
if (!migration.appKeyPrefix) { if (!migration.appKeyPrefix) {
return migratedMetadata; return migratedMetadata;
@@ -154,12 +154,8 @@ const applyAppKeyPrefixMigration = (meta: IMetadata, migration: IMetadataMigrati
return migratedMetadata; return migratedMetadata;
}; };
const applyMetadataValueMigration = ( const applyMetadataValueMigration = (meta: Metadata, migration: IMetadataMigration, appKeyPrefix: string): Metadata => {
meta: IMetadata, const migratedMetadata: Metadata = { ...meta };
migration: IMetadataMigration,
appKeyPrefix: string,
): IMetadata => {
const migratedMetadata: IMetadata = { ...meta };
if (!migration.values) { if (!migration.values) {
return migratedMetadata; return migratedMetadata;
@@ -193,8 +189,8 @@ const applyMetadataValueMigration = (
return migratedMetadata; return migratedMetadata;
}; };
const applyMetadataKeyMigration = (meta: IMetadata, migration: IMetadataMigration): IMetadata => { const applyMetadataKeyMigration = (meta: Metadata, migration: IMetadataMigration): Metadata => {
const migratedMetadata: IMetadata = { ...meta }; const migratedMetadata: Metadata = { ...meta };
if (!migration.keys) { if (!migration.keys) {
return migratedMetadata; return migratedMetadata;
@@ -217,12 +213,12 @@ const applyMetadataKeyMigration = (meta: IMetadata, migration: IMetadataMigratio
return migratedMetadata; return migratedMetadata;
}; };
const applyMetadataMigrations = (meta?: IMetadata): IMetadata | undefined => { const applyMetadataMigrations = (meta?: Metadata): Metadata | undefined => {
if (!meta) { if (!meta) {
return undefined; return undefined;
} }
const migrationToMetadata: [number, IMetadata][] = [[0, meta]]; const migrationToMetadata: [number, Metadata][] = [[0, meta]];
migrations.forEach((migration, index) => { migrations.forEach((migration, index) => {
const migrationId = index + 1; const migrationId = index + 1;
@@ -246,12 +242,12 @@ const applyMetadataMigrations = (meta?: IMetadata): IMetadata | undefined => {
return migrationToMetadata.pop()![1]; return migrationToMetadata.pop()![1];
}; };
export const getMetadataValueFrom = <T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes>( export const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends AllowedMetadataValueTypes>(
{ meta }: IMetadataHolder, { meta }: MetadataHolder,
key: AppMetadataKeys, key: Key,
defaultValue?: T, defaultValue?: Value,
applyMigrations: boolean = true, applyMigrations: boolean = true,
): T | undefined => { ): Value | undefined => {
const metadata = applyMigrations ? applyMetadataMigrations(meta) : meta; const metadata = applyMigrations ? applyMetadataMigrations(meta) : meta;
if (metadata === undefined || !doesMetadataKeyExistIn(metadata, key)) { if (metadata === undefined || !doesMetadataKeyExistIn(metadata, key)) {
@@ -261,21 +257,26 @@ export const getMetadataValueFrom = <T extends AllowedMetadataValueTypes = Allow
return convertValueFromMetadata(metadata[getMetadataKey(key)]); return convertValueFromMetadata(metadata[getMetadataKey(key)]);
}; };
export const getMetadataFrom = ( export const getMetadataFrom = <METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
{ meta }: IMetadataHolder, { meta }: MetadataHolder,
keysToDefaultValues: MetadataKeyValuePair[], metadataWithDefaultValues: METADATA,
applyMigrations?: boolean, applyMigrations?: boolean,
): IMetadata<AllowedMetadataValueTypes> => { ): METADATA => {
const appMetadata: IMetadata<AllowedMetadataValueTypes> = {}; const appMetadata = {} as METADATA;
keysToDefaultValues.forEach(([key, defaultValue]) => { Object.entries(metadataWithDefaultValues).forEach(([key, defaultValue]) => {
appMetadata[key] = getMetadataValueFrom({ meta }, key, defaultValue, applyMigrations); appMetadata[key as AppMetadataKeys] = getMetadataValueFrom(
{ meta },
key as AppMetadataKeys,
defaultValue,
applyMigrations,
);
}); });
return appMetadata; return appMetadata;
}; };
const wrapMetadataWithMetaKey = (wrap: boolean, metadata: IMetadata): IMetadataHolder => { const wrapMetadataWithMetaKey = (wrap: boolean, metadata: Metadata): MetadataHolder => {
if (wrap) { if (wrap) {
return { return {
meta: { meta: {
@@ -291,7 +292,7 @@ const wrapMetadataWithMetaKey = (wrap: boolean, metadata: IMetadata): IMetadataH
export const requestUpdateMetadataValue = async ( export const requestUpdateMetadataValue = async (
endpoint: string, endpoint: string,
metadataHolder: IMetadataHolder, metadataHolder: MetadataHolder,
key: AppMetadataKeys, key: AppMetadataKeys,
value: AllowedMetadataValueTypes, value: AllowedMetadataValueTypes,
endpointToMutate: string = endpoint, endpointToMutate: string = endpoint,
@@ -323,7 +324,7 @@ export const requestUpdateMetadataValue = async (
export const requestUpdateMetadata = async ( export const requestUpdateMetadata = async (
endpoint: string, endpoint: string,
metadataHolder: IMetadataHolder, metadataHolder: MetadataHolder,
keysToValues: [AppMetadataKeys, AllowedMetadataValueTypes][], keysToValues: [AppMetadataKeys, AllowedMetadataValueTypes][],
endpointToMutate?: string, endpointToMutate?: string,
wrapWithMetaKey?: boolean, wrapWithMetaKey?: boolean,
@@ -335,7 +336,7 @@ export const requestUpdateMetadata = async (
); );
export const requestUpdateServerMetadata = async ( export const requestUpdateServerMetadata = async (
serverMetadata: IMetadata, serverMetadata: Metadata,
keysToValues: MetadataKeyValuePair[], keysToValues: MetadataKeyValuePair[],
): Promise<void[]> => requestUpdateMetadata('', { meta: serverMetadata }, keysToValues, '/meta', false); ): Promise<void[]> => requestUpdateMetadata('', { meta: serverMetadata }, keysToValues, '/meta', false);

View File

@@ -8,51 +8,45 @@
import { getMetadataFrom, requestUpdateMangaMetadata, requestUpdateServerMetadata } from 'util/metadata'; import { getMetadataFrom, requestUpdateMangaMetadata, requestUpdateServerMetadata } from 'util/metadata';
import { useQuery } from 'util/client'; import { useQuery } from 'util/client';
import { IManga, IMetadata, IMetadataHolder, IReaderSettings, MetadataKeyValuePair } from 'typings'; import { IManga, Metadata, MetadataHolder, IReaderSettings, MetadataKeyValuePair } from 'typings';
export const getDefaultSettings = (forceUndefined: boolean = false) => type UndefinedReaderSettings = {
({ [setting in keyof IReaderSettings]: IReaderSettings[setting] | undefined;
staticNav: forceUndefined ? undefined : false, };
showPageNumber: forceUndefined ? undefined : true,
continuesPageGap: forceUndefined ? undefined : false,
loadNextOnEnding: forceUndefined ? undefined : false,
skipDupChapters: forceUndefined ? undefined : true,
readerType: forceUndefined ? undefined : 'ContinuesVertical',
} as IReaderSettings);
const getReaderSettingsWithDefaultValueFallback = ( export const getDefaultSettings = (): IReaderSettings => ({
meta?: IMetadata, staticNav: false,
defaultSettings?: IReaderSettings, showPageNumber: true,
applyMetadataMigration: boolean = true, loadNextOnEnding: false,
): IReaderSettings => ({ skipDupChapters: true,
...(getMetadataFrom( readerType: 'ContinuesVertical',
{ meta },
Object.entries(defaultSettings ?? getDefaultSettings()) as MetadataKeyValuePair[],
applyMetadataMigration,
) as unknown as IReaderSettings),
}); });
const getReaderSettingsWithDefaultValueFallback = <DefaultSettings extends IReaderSettings | UndefinedReaderSettings>(
meta?: Metadata,
defaultSettings: DefaultSettings = getDefaultSettings() as DefaultSettings,
applyMetadataMigration: boolean = true,
): DefaultSettings => getMetadataFrom({ meta }, defaultSettings, applyMetadataMigration);
export const getReaderSettingsFromMetadata = ( export const getReaderSettingsFromMetadata = (
meta?: IMetadata, meta?: Metadata,
defaultSettings?: IReaderSettings, defaultSettings?: IReaderSettings,
applyMetadataMigration?: boolean, applyMetadataMigration?: boolean,
): IReaderSettings => ({ ): IReaderSettings => getReaderSettingsWithDefaultValueFallback(meta, defaultSettings, applyMetadataMigration);
...getReaderSettingsWithDefaultValueFallback(meta, defaultSettings, applyMetadataMigration),
});
export const getReaderSettingsFor = ( export const getReaderSettingsFor = (
{ meta }: IMetadataHolder, { meta }: MetadataHolder,
defaultSettings?: IReaderSettings, defaultSettings?: IReaderSettings,
applyMetadataMigration?: boolean, applyMetadataMigration?: boolean,
): IReaderSettings => getReaderSettingsFromMetadata(meta, defaultSettings, applyMetadataMigration); ): IReaderSettings => getReaderSettingsFromMetadata(meta, defaultSettings, applyMetadataMigration);
export const useDefaultReaderSettings = (): { export const useDefaultReaderSettings = (): {
metadata?: IMetadata; metadata?: Metadata;
settings: IReaderSettings; settings: IReaderSettings;
loading: boolean; loading: boolean;
} => { } => {
const { data: meta, loading } = useQuery<IMetadata>('/api/v1/meta'); const { data: meta, loading } = useQuery<Metadata>('/api/v1/meta');
const settings = getReaderSettingsWithDefaultValueFallback(meta); const settings = getReaderSettingsWithDefaultValueFallback<IReaderSettings>(meta);
return { metadata: meta, settings, loading }; return { metadata: meta, settings, loading };
}; };
@@ -65,12 +59,22 @@ export const useDefaultReaderSettings = (): {
* @param defaultSettings * @param defaultSettings
*/ */
export const checkAndHandleMissingStoredReaderSettings = async ( export const checkAndHandleMissingStoredReaderSettings = async (
metadataHolder: IManga | IMetadataHolder, metadataHolder: IManga | MetadataHolder,
metadataHolderType: 'manga' | 'server', metadataHolderType: 'manga' | 'server',
defaultSettings: IReaderSettings, defaultSettings: IReaderSettings,
): Promise<void | void[]> => { ): Promise<void | void[]> => {
const meta = metadataHolder.meta ?? (metadataHolder as IMetadata); const meta = metadataHolder.meta ?? (metadataHolder as Metadata);
const settingsToCheck = getReaderSettingsFor({ meta }, getDefaultSettings(true), false); const settingsToCheck = getReaderSettingsWithDefaultValueFallback(
meta,
{
staticNav: undefined,
showPageNumber: undefined,
loadNextOnEnding: undefined,
skipDupChapters: undefined,
readerType: undefined,
},
false,
);
const newSettings = getReaderSettingsFor({ meta }, defaultSettings); const newSettings = getReaderSettingsFor({ meta }, defaultSettings);
const undefinedSettings = Object.entries(settingsToCheck).filter((setting) => setting[1] === undefined); const undefinedSettings = Object.entries(settingsToCheck).filter((setting) => setting[1] === undefined);

View File

@@ -1,30 +1,22 @@
import { useQuery } from 'util/client'; import { useQuery } from 'util/client';
import { getMetadataFrom } from 'util/metadata'; import { getMetadataFrom } from 'util/metadata';
import { IMetadata, ISearchSettings, MetadataKeyValuePair } from 'typings'; import { Metadata, ISearchSettings } from 'typings';
export const getDefaultSettings = () => export const getDefaultSettings = (): ISearchSettings => ({
({
ignoreFilters: false, ignoreFilters: false,
} as ISearchSettings);
const getSearchSettingsWithDefaultValueFallback = (
meta?: IMetadata,
defaultSettings?: ISearchSettings,
applyMetadataMigration: boolean = true,
): ISearchSettings => ({
...(getMetadataFrom(
{ meta },
Object.entries(defaultSettings ?? getDefaultSettings()) as MetadataKeyValuePair[],
applyMetadataMigration,
) as unknown as ISearchSettings),
}); });
const getSearchSettingsWithDefaultValueFallback = (
meta?: Metadata,
defaultSettings: ISearchSettings = getDefaultSettings(),
applyMetadataMigration: boolean = true,
): ISearchSettings => getMetadataFrom({ meta }, defaultSettings, applyMetadataMigration);
export const useSearchSettings = (): { export const useSearchSettings = (): {
metadata?: IMetadata; metadata?: Metadata;
settings: ISearchSettings; settings: ISearchSettings;
loading: boolean; loading: boolean;
} => { } => {
const { data: meta, loading } = useQuery<IMetadata>('/api/v1/meta'); const { data: meta, loading } = useQuery<Metadata>('/api/v1/meta');
const settings = getSearchSettingsWithDefaultValueFallback(meta); const settings = getSearchSettingsWithDefaultValueFallback(meta);
return { metadata: meta, settings, loading }; return { metadata: meta, settings, loading };