Skip to content

Commit

Permalink
zodzod
Browse files Browse the repository at this point in the history
  • Loading branch information
whatuserever committed Feb 15, 2025
1 parent cafdc53 commit acf4bac
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -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<number, (config: VersionedConfig) => VersionedConfig> = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const migrations: Record<number, (config: any) => 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;
}
Original file line number Diff line number Diff line change
@@ -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<typeof originalConfigSchema> {
const parseResult = originalConfigSchema.strict().safeParse(obj);
return parseResult.success;
}
Original file line number Diff line number Diff line change
@@ -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<typeof originalConfigSchema>): z.infer<typeof v1ConfigSchema> {
return {
...v1Config,
version: 1,
};
}

0 comments on commit acf4bac

Please sign in to comment.