From 589de4b50be581f243a37289f177135955411ab1 Mon Sep 17 00:00:00 2001 From: yml Date: Thu, 21 May 2026 18:54:41 +0800 Subject: [PATCH] deduplicate json config file access --- .../services/notification/config-service.ts | 22 ++++-------- .../fulfillment-binding-config-service.ts | 27 +++++--------- ...aishou-cloud-fulfillment-config-service.ts | 32 +++++------------ .../kuaishou-eticket/source-config-service.ts | 26 ++++++-------- .../src/services/scheduler/config-service.ts | 22 ++++-------- apps/backend/src/utils/json-file-store.ts | 36 +++++++++++++++++++ 6 files changed, 79 insertions(+), 86 deletions(-) create mode 100644 apps/backend/src/utils/json-file-store.ts diff --git a/apps/backend/src/services/notification/config-service.ts b/apps/backend/src/services/notification/config-service.ts index 7e987731..8a56aeb2 100644 --- a/apps/backend/src/services/notification/config-service.ts +++ b/apps/backend/src/services/notification/config-service.ts @@ -1,7 +1,7 @@ -import fs from 'node:fs' import path from 'node:path' import { PROJECT_ROOT } from '../../config/runtime.js' +import { readJsonFile, writeJsonFile } from '../../utils/json-file-store.js' const NOTIFICATION_CONFIG_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'notification-config.json') const DEFAULT_BARK_SERVER_URL = 'https://api.day.app' @@ -17,10 +17,7 @@ export function getNotificationConfig() { } export function saveNotificationConfig(rawValue: unknown) { - const normalized = normalizeNotificationConfig(rawValue) - fs.mkdirSync(path.dirname(NOTIFICATION_CONFIG_FILE_PATH), { recursive: true }) - fs.writeFileSync(NOTIFICATION_CONFIG_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8') - return normalized + return writeJsonFile(NOTIFICATION_CONFIG_FILE_PATH, rawValue, normalizeNotificationConfig) } export function listEnabledBarkRecipients(config: JsonObject = getNotificationConfig()) { @@ -33,16 +30,11 @@ export function listEnabledBarkRecipients(config: JsonObject = getNotificationCo } function loadNotificationConfigFromFile() { - if (!fs.existsSync(NOTIFICATION_CONFIG_FILE_PATH)) { - return createDefaultNotificationConfig() - } - - try { - const rawText = fs.readFileSync(NOTIFICATION_CONFIG_FILE_PATH, 'utf8') - return normalizeNotificationConfig(JSON.parse(rawText)) - } catch { - return createDefaultNotificationConfig() - } + return readJsonFile( + NOTIFICATION_CONFIG_FILE_PATH, + createDefaultNotificationConfig, + normalizeNotificationConfig, + ) } function normalizeNotificationConfig(rawValue: unknown) { diff --git a/apps/backend/src/services/order/fulfillment-binding-config-service.ts b/apps/backend/src/services/order/fulfillment-binding-config-service.ts index 63ca7257..ed8511fa 100644 --- a/apps/backend/src/services/order/fulfillment-binding-config-service.ts +++ b/apps/backend/src/services/order/fulfillment-binding-config-service.ts @@ -1,7 +1,7 @@ -import fs from 'node:fs' import path from 'node:path' import { PROJECT_ROOT } from '../../config/runtime.js' +import { readJsonFile, writeJsonFile } from '../../utils/json-file-store.js' const ORDER_FULFILLMENT_BINDINGS_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'order-fulfillment-bindings.json') @@ -20,28 +20,19 @@ export function getLegacyOrderFulfillmentBindingConfigs() { } export function saveOrderFulfillmentBindingConfigs(rawValue) { - const normalized = normalizeOrderFulfillmentBindingConfigs(rawValue) - fs.mkdirSync(path.dirname(ORDER_FULFILLMENT_BINDINGS_FILE_PATH), { recursive: true }) - fs.writeFileSync( + return writeJsonFile( ORDER_FULFILLMENT_BINDINGS_FILE_PATH, - `${JSON.stringify(normalized, null, 2)}\n`, - 'utf8', + rawValue, + normalizeOrderFulfillmentBindingConfigs, ) - return normalized } function loadOrderFulfillmentBindingConfigsFromFile() { - if (!fs.existsSync(ORDER_FULFILLMENT_BINDINGS_FILE_PATH)) { - return [] - } - - try { - const rawText = fs.readFileSync(ORDER_FULFILLMENT_BINDINGS_FILE_PATH, 'utf8') - const parsed = JSON.parse(rawText) - return normalizeOrderFulfillmentBindingConfigs(parsed) - } catch { - return [] - } + return readJsonFile( + ORDER_FULFILLMENT_BINDINGS_FILE_PATH, + [], + normalizeOrderFulfillmentBindingConfigs, + ) } function normalizeOrderFulfillmentBindingConfigs(rawValue) { diff --git a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.ts b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.ts index e8156159..dea4e65d 100644 --- a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.ts +++ b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.ts @@ -1,7 +1,7 @@ -import fs from "node:fs"; import path from "node:path"; import { PROJECT_ROOT } from "../../config/runtime.js"; +import { readJsonFile, writeJsonFile } from "../../utils/json-file-store.js"; import { resolveKuaishouEticketShopConfig } from "../platforms/kuaishou-eticket/source-config-service.js"; const KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH = path.join( @@ -21,16 +21,11 @@ export function getKuaishouCloudFulfillmentConfig() { } export function saveKuaishouCloudFulfillmentConfig(rawValue) { - const normalized = normalizeKuaishouCloudFulfillmentConfig(rawValue); - fs.mkdirSync(path.dirname(KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH), { - recursive: true, - }); - fs.writeFileSync( + return writeJsonFile( KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH, - `${JSON.stringify(normalized, null, 2)}\n`, - "utf8" + rawValue, + normalizeKuaishouCloudFulfillmentConfig ); - return normalized; } export function mapKuaishouCloudFulfillmentItemsToBindings(config: JsonObject = {}) { @@ -92,20 +87,11 @@ export function mapKuaishouCloudFulfillmentItemsToBindings(config: JsonObject = } function loadKuaishouCloudFulfillmentConfigFromFile() { - if (!fs.existsSync(KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH)) { - return normalizeKuaishouCloudFulfillmentConfig({}); - } - - try { - const rawText = fs.readFileSync( - KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH, - "utf8" - ); - const parsed = JSON.parse(rawText); - return normalizeKuaishouCloudFulfillmentConfig(parsed); - } catch { - return normalizeKuaishouCloudFulfillmentConfig({}); - } + return readJsonFile( + KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH, + () => normalizeKuaishouCloudFulfillmentConfig({}), + normalizeKuaishouCloudFulfillmentConfig + ); } function normalizeKuaishouCloudFulfillmentConfig(rawValue) { diff --git a/apps/backend/src/services/platforms/kuaishou-eticket/source-config-service.ts b/apps/backend/src/services/platforms/kuaishou-eticket/source-config-service.ts index 01664c51..2e2a2c08 100644 --- a/apps/backend/src/services/platforms/kuaishou-eticket/source-config-service.ts +++ b/apps/backend/src/services/platforms/kuaishou-eticket/source-config-service.ts @@ -1,7 +1,7 @@ -import fs from 'node:fs' import path from 'node:path' import { PROJECT_ROOT } from '../../../config/runtime.js' +import { readJsonFile, writeJsonFile } from '../../../utils/json-file-store.js' const KUAISHOU_ETICKET_SOURCE_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'kuaishou-eticket-source.json') const DEFAULT_BASE_URL = 'https://s.kwaixiaodian.com' @@ -31,10 +31,11 @@ export function getKuaishouEticketSourceConfig(): KuaishouEticketSourceConfig { } export function saveKuaishouEticketSourceConfig(rawValue: unknown): KuaishouEticketSourceConfig { - const normalized = normalizeKuaishouEticketSourceConfig(rawValue) - fs.mkdirSync(path.dirname(KUAISHOU_ETICKET_SOURCE_FILE_PATH), { recursive: true }) - fs.writeFileSync(KUAISHOU_ETICKET_SOURCE_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8') - return normalized + return writeJsonFile( + KUAISHOU_ETICKET_SOURCE_FILE_PATH, + rawValue, + normalizeKuaishouEticketSourceConfig, + ) } export function listKuaishouEticketShopConfigs(source: KuaishouEticketSourceConfig = getKuaishouEticketSourceConfig()): KuaishouEticketShopConfig[] { @@ -81,16 +82,11 @@ export function getFirstAvailableKuaishouEticketShop(source: KuaishouEticketSour } function loadKuaishouEticketSourceConfigFromFile(): KuaishouEticketSourceConfig { - if (!fs.existsSync(KUAISHOU_ETICKET_SOURCE_FILE_PATH)) { - return createDefaultKuaishouEticketSourceConfig() - } - - try { - const rawText = fs.readFileSync(KUAISHOU_ETICKET_SOURCE_FILE_PATH, 'utf8') - return normalizeKuaishouEticketSourceConfig(JSON.parse(rawText)) - } catch { - return createDefaultKuaishouEticketSourceConfig() - } + return readJsonFile( + KUAISHOU_ETICKET_SOURCE_FILE_PATH, + createDefaultKuaishouEticketSourceConfig, + normalizeKuaishouEticketSourceConfig, + ) } function normalizeKuaishouEticketSourceConfig(rawValue: unknown): KuaishouEticketSourceConfig { diff --git a/apps/backend/src/services/scheduler/config-service.ts b/apps/backend/src/services/scheduler/config-service.ts index 0429e263..b89fb852 100644 --- a/apps/backend/src/services/scheduler/config-service.ts +++ b/apps/backend/src/services/scheduler/config-service.ts @@ -1,7 +1,7 @@ -import fs from 'node:fs' import path from 'node:path' import { PROJECT_ROOT } from '../../config/runtime.js' +import { readJsonFile, writeJsonFile } from '../../utils/json-file-store.js' const SCHEDULED_JOBS_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'scheduled-jobs.json') const CLOUDTENTACLES_HEALTH_JOB_ID = 'cloudtentacles-health' @@ -17,10 +17,7 @@ export function getScheduledJobsConfig() { } export function saveScheduledJobsConfig(rawValue: unknown) { - const normalized = normalizeScheduledJobsConfig(rawValue) - fs.mkdirSync(path.dirname(SCHEDULED_JOBS_FILE_PATH), { recursive: true }) - fs.writeFileSync(SCHEDULED_JOBS_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8') - return normalized + return writeJsonFile(SCHEDULED_JOBS_FILE_PATH, rawValue, normalizeScheduledJobsConfig) } export function getCloudtentaclesHealthJob(config: JsonObject = getScheduledJobsConfig()) { @@ -30,16 +27,11 @@ export function getCloudtentaclesHealthJob(config: JsonObject = getScheduledJobs } function loadScheduledJobsConfigFromFile() { - if (!fs.existsSync(SCHEDULED_JOBS_FILE_PATH)) { - return createDefaultScheduledJobsConfig() - } - - try { - const rawText = fs.readFileSync(SCHEDULED_JOBS_FILE_PATH, 'utf8') - return normalizeScheduledJobsConfig(JSON.parse(rawText)) - } catch { - return createDefaultScheduledJobsConfig() - } + return readJsonFile( + SCHEDULED_JOBS_FILE_PATH, + createDefaultScheduledJobsConfig, + normalizeScheduledJobsConfig, + ) } function normalizeScheduledJobsConfig(rawValue: unknown) { diff --git a/apps/backend/src/utils/json-file-store.ts b/apps/backend/src/utils/json-file-store.ts new file mode 100644 index 00000000..3eaf88ce --- /dev/null +++ b/apps/backend/src/utils/json-file-store.ts @@ -0,0 +1,36 @@ +import fs from 'node:fs' +import path from 'node:path' + +type NormalizeJsonValue = (value: unknown) => T + +export function readJsonFile( + filePath: string, + fallback: T | (() => T), + normalize: NormalizeJsonValue, +): T { + if (!fs.existsSync(filePath)) { + return resolveFallback(fallback) + } + + try { + const rawText = fs.readFileSync(filePath, 'utf8') + return normalize(JSON.parse(rawText)) + } catch { + return resolveFallback(fallback) + } +} + +export function writeJsonFile( + filePath: string, + value: unknown, + normalize: NormalizeJsonValue, +): T { + const normalized = normalize(value) + fs.mkdirSync(path.dirname(filePath), { recursive: true }) + fs.writeFileSync(filePath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8') + return normalized +} + +function resolveFallback(fallback: T | (() => T)): T { + return typeof fallback === 'function' ? (fallback as () => T)() : fallback +}