186 lines
5.4 KiB
TypeScript
186 lines
5.4 KiB
TypeScript
import path from 'node:path'
|
|
import type { JsonObject } from '../../types/json.js'
|
|
|
|
import { APP_CONFIG_KEYS } from '../../config/app-config-keys.js'
|
|
import { PROJECT_ROOT } from '../../config/runtime.js'
|
|
import { readAppConfigEntry, saveAppConfigEntry } from '../config/app-config-store.js'
|
|
import { normalizeCloudtentaclesMatchName } from './cloudtentacles-match-utils.js'
|
|
|
|
const CLOUDTENTACLES_OVERRIDE_RULE_FILE_PATH = path.join(
|
|
PROJECT_ROOT,
|
|
'data',
|
|
'cloudtentacles-override-rules.json',
|
|
)
|
|
|
|
export type CloudtentaclesOverrideDeliveryItem = {
|
|
cloudSkuId: number
|
|
cloudSkuName: string
|
|
quantity: number
|
|
}
|
|
|
|
export type CloudtentaclesOverrideRule = {
|
|
id: string
|
|
enabled: boolean
|
|
productName: string
|
|
normalizedProductName: string
|
|
sourceKey: string
|
|
deliveryItems: CloudtentaclesOverrideDeliveryItem[]
|
|
notes: string
|
|
}
|
|
|
|
export type CloudtentaclesOverrideRuleConfig = {
|
|
enabled: boolean
|
|
rules: CloudtentaclesOverrideRule[]
|
|
}
|
|
|
|
export function getCloudtentaclesOverrideRuleFilePath() {
|
|
return CLOUDTENTACLES_OVERRIDE_RULE_FILE_PATH
|
|
}
|
|
|
|
export function getCloudtentaclesOverrideRuleConfig(): CloudtentaclesOverrideRuleConfig {
|
|
return readAppConfigEntry({
|
|
configKey: APP_CONFIG_KEYS.cloudtentaclesOverrideRules,
|
|
legacyFilePath: CLOUDTENTACLES_OVERRIDE_RULE_FILE_PATH,
|
|
fallback: createDefaultCloudtentaclesOverrideRuleConfig,
|
|
normalize: normalizeCloudtentaclesOverrideRuleConfig,
|
|
})
|
|
}
|
|
|
|
export function saveCloudtentaclesOverrideRuleConfig(
|
|
rawValue: unknown,
|
|
): Promise<CloudtentaclesOverrideRuleConfig> {
|
|
return saveAppConfigEntry({
|
|
configKey: APP_CONFIG_KEYS.cloudtentaclesOverrideRules,
|
|
legacyFilePath: CLOUDTENTACLES_OVERRIDE_RULE_FILE_PATH,
|
|
value: rawValue,
|
|
normalize: normalizeCloudtentaclesOverrideRuleConfig,
|
|
})
|
|
}
|
|
|
|
export function resolveCloudtentaclesOverrideRule(productName: unknown) {
|
|
const config = getCloudtentaclesOverrideRuleConfig()
|
|
if (config.enabled === false) {
|
|
return null
|
|
}
|
|
|
|
const normalizedProductName = normalizeCloudtentaclesMatchName(productName)
|
|
if (!normalizedProductName) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
config.rules.find(
|
|
(rule) =>
|
|
rule.enabled !== false &&
|
|
rule.normalizedProductName === normalizedProductName &&
|
|
rule.deliveryItems.length > 0,
|
|
) || null
|
|
)
|
|
}
|
|
|
|
export function normalizeCloudtentaclesOverrideRuleConfig(
|
|
rawValue: unknown,
|
|
): CloudtentaclesOverrideRuleConfig {
|
|
const source = isPlainObject(rawValue) ? rawValue : {}
|
|
const rules = Array.isArray(source.rules) ? source.rules : []
|
|
|
|
return {
|
|
enabled: source.enabled !== false,
|
|
rules: rules
|
|
.map((item: unknown) => normalizeCloudtentaclesOverrideRule(item))
|
|
.filter((item): item is CloudtentaclesOverrideRule => Boolean(item)),
|
|
}
|
|
}
|
|
|
|
function normalizeCloudtentaclesOverrideRule(rawValue: unknown): CloudtentaclesOverrideRule | null {
|
|
if (!isPlainObject(rawValue)) {
|
|
return null
|
|
}
|
|
|
|
const productName = String(rawValue.productName || rawValue.name || '').trim()
|
|
const normalizedProductName = normalizeCloudtentaclesMatchName(productName)
|
|
const deliveryItems = normalizeDeliveryItems(rawValue.deliveryItems)
|
|
|
|
if (!productName || !normalizedProductName || deliveryItems.length === 0) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
id: String(rawValue.id || normalizedProductName).trim() || normalizedProductName,
|
|
enabled: rawValue.enabled !== false,
|
|
productName,
|
|
normalizedProductName,
|
|
sourceKey: String(rawValue.sourceKey || '').trim(),
|
|
deliveryItems,
|
|
notes: String(rawValue.notes || '').trim(),
|
|
}
|
|
}
|
|
|
|
function normalizeDeliveryItems(value: unknown) {
|
|
const rawItems = Array.isArray(value) ? value : []
|
|
const items = rawItems
|
|
.map((item) => normalizeDeliveryItem(item))
|
|
.filter((item): item is CloudtentaclesOverrideDeliveryItem => Boolean(item))
|
|
|
|
return mergeDeliveryItems(items)
|
|
}
|
|
|
|
function normalizeDeliveryItem(value: unknown): CloudtentaclesOverrideDeliveryItem | null {
|
|
const source = isPlainObject(value) ? value : {}
|
|
const cloudSkuId = normalizePositiveInteger(source.cloudSkuId || source.skuId, 0)
|
|
const cloudSkuName = String(source.cloudSkuName || source.skuName || '').trim()
|
|
const quantity = normalizePositiveInteger(source.quantity, 1)
|
|
|
|
if (!cloudSkuId && !cloudSkuName) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
cloudSkuId,
|
|
cloudSkuName,
|
|
quantity,
|
|
}
|
|
}
|
|
|
|
function mergeDeliveryItems(items: CloudtentaclesOverrideDeliveryItem[]) {
|
|
const merged = new Map<string, CloudtentaclesOverrideDeliveryItem>()
|
|
|
|
for (const item of items) {
|
|
const key =
|
|
item.cloudSkuId > 0
|
|
? `id:${item.cloudSkuId}`
|
|
: `name:${normalizeCloudtentaclesMatchName(item.cloudSkuName)}`
|
|
const existing = merged.get(key)
|
|
if (existing) {
|
|
existing.quantity += item.quantity
|
|
existing.cloudSkuName = existing.cloudSkuName || item.cloudSkuName
|
|
existing.cloudSkuId = existing.cloudSkuId || item.cloudSkuId
|
|
continue
|
|
}
|
|
|
|
merged.set(key, { ...item })
|
|
}
|
|
|
|
return Array.from(merged.values())
|
|
}
|
|
|
|
function normalizePositiveInteger(value: unknown, fallback: number) {
|
|
const parsed = Number(value)
|
|
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
return fallback
|
|
}
|
|
|
|
return parsed
|
|
}
|
|
|
|
function createDefaultCloudtentaclesOverrideRuleConfig(): CloudtentaclesOverrideRuleConfig {
|
|
return {
|
|
enabled: true,
|
|
rules: [],
|
|
}
|
|
}
|
|
|
|
function isPlainObject(value: unknown): value is JsonObject {
|
|
return Object.prototype.toString.call(value) === '[object Object]'
|
|
}
|