From acf4bac82a1ba43ace56752eb4bc097f6036fc56 Mon Sep 17 00:00:00 2001 From: whatuserever <83753332+whatuserever@users.noreply.github.com> Date: Sat, 15 Feb 2025 22:55:30 +0200 Subject: [PATCH] zodzod --- .../configMigration/configMigrator.ts | 35 ++++--- .../configMigration/versions/original.ts | 94 ++++++++++++++++++ .../configMigration/versions/v1.ts | 97 ++----------------- 3 files changed, 123 insertions(+), 103 deletions(-) create mode 100644 packages/main/src/backend/configManager/configMigration/versions/original.ts diff --git a/packages/main/src/backend/configManager/configMigration/configMigrator.ts b/packages/main/src/backend/configManager/configMigration/configMigrator.ts index 00a88be9..21788321 100644 --- a/packages/main/src/backend/configManager/configMigration/configMigrator.ts +++ b/packages/main/src/backend/configManager/configMigration/configMigrator.ts @@ -1,22 +1,31 @@ +import { z } from 'zod'; import { type Config } from '../../commonTypes'; -import { isV1Config } from './versions/v1'; +import { isOriginalConfig } from './versions/original'; +import { migrateOriginalToV1, v1ConfigSchema } from './versions/v1'; -interface VersionedConfig { - version: number; - [key: string]: unknown; -} +const latestConfigSchema = v1ConfigSchema; // migrations[n] should be a function that converts version n to version n+1 -const migrations: Record VersionedConfig> = {}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const migrations: Record any> = {}; -export function migrateConfig(config: VersionedConfig): Config { +export function migrateConfig(config: unknown): Config { let currentConfig = config; - if (isV1Config(config)) { - currentConfig = { version: 1, config }; + // original config does not have version key and must be handled separately + if (isOriginalConfig(config)) { + currentConfig = migrateOriginalToV1(config); } - while (migrations[currentConfig.version]) { - currentConfig = migrations[currentConfig.version](currentConfig); + let currentVersion = getConfigVersion(currentConfig); + + while (migrations[currentVersion]) { + currentConfig = migrations[currentVersion](currentConfig); + currentVersion = getConfigVersion(currentConfig); } - const { _version, ...actualConfig } = currentConfig; - return actualConfig as unknown as Config; + + return latestConfigSchema.parse(currentConfig) as Config; +} + +function getConfigVersion(config: unknown): keyof typeof migrations { + const versionSchema = z.object({ version: z.number().int().positive() }); + return versionSchema.parse(config).version; } diff --git a/packages/main/src/backend/configManager/configMigration/versions/original.ts b/packages/main/src/backend/configManager/configMigration/versions/original.ts new file mode 100644 index 00000000..88e6991e --- /dev/null +++ b/packages/main/src/backend/configManager/configMigration/versions/original.ts @@ -0,0 +1,94 @@ +import { z } from 'zod'; + +export const outputVendorNameSchema = z.enum(['ynab', 'googleSheets', 'json', 'csv']); + +export const companyTypeSchema = z.enum([ + 'hapoalim', + 'hapoalimBeOnline', + 'beinleumi', + 'union', + 'amex', + 'isracard', + 'visaCal', + 'max', + 'leumiCard', + 'otsarHahayal', + 'discount', + 'mercantile', + 'mizrahi', + 'leumi', + 'massad', + 'yahav', + 'behatsdaa', + 'beyahadBishvilha', + 'oneZero', + 'pagi', +]); + +export const googleSheetsConfigSchema = z.object({ + active: z.boolean(), + options: z.object({ + credentials: z.any(), + spreadsheetId: z.string(), + }), +}); + +export const ynabConfigSchema = z.object({ + active: z.boolean(), + options: z.object({ + accessToken: z.string(), + accountNumbersToYnabAccountIds: z.record(z.string(), z.string()), + budgetId: z.string(), + maxPayeeNameLength: z.number().optional(), + }), +}); + +export const jsonConfigSchema = z.object({ + active: z.boolean(), + options: z.object({ + filePath: z.string(), + }), +}); + +export const csvConfigSchema = z.object({ + active: z.boolean(), + options: z.object({ + filePath: z.string(), + }), +}); + +export const outputVendorsSchema = z.object({ + [outputVendorNameSchema.Values.googleSheets]: googleSheetsConfigSchema.optional(), + [outputVendorNameSchema.Values.ynab]: ynabConfigSchema.optional(), + [outputVendorNameSchema.Values.json]: jsonConfigSchema.optional(), + [outputVendorNameSchema.Values.csv]: csvConfigSchema.optional(), +}); + +export const accountToScrapeConfigSchema = z.object({ + id: z.string(), + key: companyTypeSchema, + name: z.string(), + loginFields: z.any(), + active: z.boolean().optional(), +}); + +export const scrapingSchema = z.object({ + numDaysBack: z.number(), + showBrowser: z.boolean(), + accountsToScrape: z.array(accountToScrapeConfigSchema), + chromiumPath: z.string().optional(), + maxConcurrency: z.number().optional(), + timeout: z.number(), + periodicScrapingIntervalHours: z.number().optional(), +}); + +export const originalConfigSchema = z.object({ + outputVendors: outputVendorsSchema, + scraping: scrapingSchema, + useReactUI: z.boolean().optional(), +}); + +export function isOriginalConfig(obj: unknown): obj is z.infer { + const parseResult = originalConfigSchema.strict().safeParse(obj); + return parseResult.success; +} diff --git a/packages/main/src/backend/configManager/configMigration/versions/v1.ts b/packages/main/src/backend/configManager/configMigration/versions/v1.ts index 47a836e0..6b3a635b 100644 --- a/packages/main/src/backend/configManager/configMigration/versions/v1.ts +++ b/packages/main/src/backend/configManager/configMigration/versions/v1.ts @@ -1,94 +1,11 @@ import { z } from 'zod'; +import { originalConfigSchema } from './original'; -export const outputVendorNameSchema = z.enum(['ynab', 'googleSheets', 'json', 'csv']); +export const v1ConfigSchema = originalConfigSchema.extend({ version: z.literal(1) }); -export const companyTypeSchema = z.enum([ - 'hapoalim', - 'hapoalimBeOnline', - 'beinleumi', - 'union', - 'amex', - 'isracard', - 'visaCal', - 'max', - 'leumiCard', - 'otsarHahayal', - 'discount', - 'mercantile', - 'mizrahi', - 'leumi', - 'massad', - 'yahav', - 'behatsdaa', - 'beyahadBishvilha', - 'oneZero', - 'pagi', -]); - -export const googleSheetsConfigSchema = z.object({ - active: z.boolean(), - options: z.object({ - credentials: z.any(), - spreadsheetId: z.string(), - }), -}); - -export const ynabConfigSchema = z.object({ - active: z.boolean(), - options: z.object({ - accessToken: z.string(), - accountNumbersToYnabAccountIds: z.record(z.string(), z.string()), - budgetId: z.string(), - maxPayeeNameLength: z.number().optional(), - }), -}); - -export const jsonConfigSchema = z.object({ - active: z.boolean(), - options: z.object({ - filePath: z.string(), - }), -}); - -export const csvConfigSchema = z.object({ - active: z.boolean(), - options: z.object({ - filePath: z.string(), - }), -}); - -export const outputVendorsSchema = z.object({ - [outputVendorNameSchema.Values.googleSheets]: googleSheetsConfigSchema.optional(), - [outputVendorNameSchema.Values.ynab]: ynabConfigSchema.optional(), - [outputVendorNameSchema.Values.json]: jsonConfigSchema.optional(), - [outputVendorNameSchema.Values.csv]: csvConfigSchema.optional(), -}); - -export const accountToScrapeConfigSchema = z.object({ - id: z.string(), - key: companyTypeSchema, - name: z.string(), - loginFields: z.any(), - active: z.boolean().optional(), -}); - -export const scrapingSchema = z.object({ - numDaysBack: z.number(), - showBrowser: z.boolean(), - accountsToScrape: z.array(accountToScrapeConfigSchema), - chromiumPath: z.string().optional(), - maxConcurrency: z.number().optional(), - timeout: z.number(), - periodicScrapingIntervalHours: z.number().optional(), -}); - -export const v1ConfigSchema = z.object({ - outputVendors: outputVendorsSchema, - scraping: scrapingSchema, - useReactUI: z.boolean().optional(), -}); - -export function isV1Config(obj: unknown) { - const parseResult = v1ConfigSchema.safeParse(obj); - return parseResult.success; +export function migrateOriginalToV1(v1Config: z.infer): z.infer { + return { + ...v1Config, + version: 1, + }; }