增加平台持久化
This commit is contained in:
@@ -5,10 +5,13 @@ import { Router } from 'express'
|
||||
import {
|
||||
getAdminAgisoShopConfigs,
|
||||
getAdminFulfillmentBindingConfigs,
|
||||
getAdminKhhaoSourceConfig,
|
||||
lookupAdminFulfillmentBindingOrder,
|
||||
queryAdminKhhaoOrders,
|
||||
syncAdminKhhaoOrders,
|
||||
testAdminKhhaoLogin,
|
||||
updateAdminAgisoShopConfigs,
|
||||
updateAdminKhhaoSourceConfig,
|
||||
updateAdminFulfillmentBindingConfigs,
|
||||
} from '../../services/admin/admin-platform-config-service.js'
|
||||
import { createJsonHandler, requireAdminRoles } from './shared.js'
|
||||
@@ -17,6 +20,7 @@ import { createJsonHandler, requireAdminRoles } from './shared.js'
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminFulfillmentBindingConfigRouteBody} AdminFulfillmentBindingConfigRouteBody */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminFulfillmentBindingLookupRouteBody} AdminFulfillmentBindingLookupRouteBody */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoOrderQueryRouteBody} AdminKhhaoOrderQueryRouteBody */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoSourceConfigRouteBody} AdminKhhaoSourceConfigRouteBody */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoTestLoginRouteBody} AdminKhhaoTestLoginRouteBody */
|
||||
/** @typedef {import('../../types/admin-write-models.js').AdminAgisoShopConfigSaveResponse} AdminAgisoShopConfigSaveResponse */
|
||||
|
||||
@@ -54,6 +58,37 @@ router.post('/platform-config/agiso-shops', createJsonHandler(
|
||||
},
|
||||
))
|
||||
|
||||
router.get('/platform-config/khhao-source', createJsonHandler(
|
||||
() => getAdminKhhaoSourceConfig(),
|
||||
{
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取 khhao 来源配置失败',
|
||||
scope: '[admin/platform-config/khhao-source]',
|
||||
},
|
||||
))
|
||||
|
||||
router.post('/platform-config/khhao-source', createJsonHandler(
|
||||
(req) => updateAdminKhhaoSourceConfig(/** @type {AdminKhhaoSourceConfigRouteBody} */ (req.body)),
|
||||
{
|
||||
successMessage: 'khhao 来源配置已保存',
|
||||
errorMessage: '保存 khhao 来源配置失败',
|
||||
scope: '[admin/platform-config/khhao-source]',
|
||||
audit: (_req, data) => {
|
||||
const result = /** @type {{ filePath?: string, source?: { username?: string, enabled?: boolean } }} */ (data)
|
||||
return {
|
||||
action: 'platform_khhao_source_updated',
|
||||
targetType: 'platform_config',
|
||||
targetId: 'khhao_source',
|
||||
data: {
|
||||
filePath: String(result.filePath || '').trim(),
|
||||
username: String(result.source?.username || '').trim(),
|
||||
enabled: Boolean(result.source?.enabled),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
))
|
||||
|
||||
router.post('/platform-config/khhao/test-login', createJsonHandler(
|
||||
(req) => testAdminKhhaoLogin(/** @type {AdminKhhaoTestLoginRouteBody} */ (req.body)),
|
||||
{
|
||||
@@ -102,6 +137,32 @@ router.post('/platform-config/khhao/query-orders', createJsonHandler(
|
||||
},
|
||||
))
|
||||
|
||||
router.post('/platform-config/khhao/sync-orders', createJsonHandler(
|
||||
(req) => syncAdminKhhaoOrders(/** @type {AdminKhhaoOrderQueryRouteBody} */ (req.body)),
|
||||
{
|
||||
successMessage: 'khhao 订单同步成功',
|
||||
errorMessage: 'khhao 订单同步失败',
|
||||
scope: '[admin/platform-config/khhao/sync-orders]',
|
||||
audit: (req, data) => {
|
||||
const body = /** @type {AdminKhhaoOrderQueryRouteBody} */ (req.body)
|
||||
const result = /** @type {{ page?: number, limit?: number, fetchedCount?: number, syncedCount?: number, ignoredCount?: number, baseUrl?: string }} */ (data)
|
||||
return {
|
||||
action: 'platform_khhao_sync_orders',
|
||||
targetType: 'platform_config',
|
||||
targetId: String(body.username || '').trim() || 'khhao',
|
||||
data: {
|
||||
baseUrl: result.baseUrl || String(body.baseUrl || '').trim(),
|
||||
page: Number(result.page || 1),
|
||||
limit: Number(result.limit || 50),
|
||||
fetchedCount: Number(result.fetchedCount || 0),
|
||||
syncedCount: Number(result.syncedCount || 0),
|
||||
ignoredCount: Number(result.ignoredCount || 0),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
))
|
||||
|
||||
router.get('/platform-config/fulfillment-bindings', createJsonHandler(
|
||||
() => getAdminFulfillmentBindingConfigs(),
|
||||
{
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
import { enrichAgisoXianyuTradeOrder } from '../platforms/agiso/xianyu/order-detail-service.js'
|
||||
import { mapKhhaoOrderPreviewList } from '../platforms/khhao/order-mapper-service.js'
|
||||
import { queryKhhaoOrderList } from '../platforms/khhao/order-query-service.js'
|
||||
import { syncKhhaoOrders } from '../platforms/khhao/order-sync-service.js'
|
||||
import { getKhhaoSourceConfig, getKhhaoSourcesFilePath, saveKhhaoSourceConfig } from '../platforms/khhao/source-config-service.js'
|
||||
import { loginKhhaoSession } from '../platforms/khhao/session-service.js'
|
||||
import { normalizeAgisoMessageTemplate } from '../platforms/agiso/xianyu/message-service.js'
|
||||
import {
|
||||
@@ -26,6 +28,7 @@ import { normalizeProductName } from '../order/product-match-service.js'
|
||||
import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
|
||||
|
||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminAgisoShopConfigSaveInput} AdminAgisoShopConfigSaveInput */
|
||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoSourceConfigInput} AdminKhhaoSourceConfigInput */
|
||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoOrderQueryInput} AdminKhhaoOrderQueryInput */
|
||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoTestLoginInput} AdminKhhaoTestLoginInput */
|
||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminFulfillmentBindingConfigSaveInput} AdminFulfillmentBindingConfigSaveInput */
|
||||
@@ -181,15 +184,53 @@ function applyOptionalStringField(target, key, source) {
|
||||
delete target[key]
|
||||
}
|
||||
|
||||
export function getAdminKhhaoSourceConfig() {
|
||||
const config = getKhhaoSourceConfig()
|
||||
|
||||
return {
|
||||
filePath: getKhhaoSourcesFilePath(),
|
||||
source: {
|
||||
enabled: config.enabled !== false,
|
||||
baseUrl: String(config.baseUrl || '').trim(),
|
||||
username: String(config.username || '').trim(),
|
||||
password: String(config.password || '').trim(),
|
||||
maxCaptchaAttempts: Number(config.maxCaptchaAttempts || 3) || 3,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {AdminKhhaoSourceConfigInput} [payload] */
|
||||
export function updateAdminKhhaoSourceConfig(payload = /** @type {AdminKhhaoSourceConfigInput} */ ({})) {
|
||||
const saved = saveKhhaoSourceConfig({
|
||||
enabled: payload.enabled !== false,
|
||||
baseUrl: String(payload.baseUrl || '').trim() || 'https://admin.khhao.com',
|
||||
username: String(payload.username || '').trim(),
|
||||
password: String(payload.password || '').trim(),
|
||||
maxCaptchaAttempts: Number(payload.maxCaptchaAttempts || 3) || 3,
|
||||
})
|
||||
|
||||
return {
|
||||
filePath: getKhhaoSourcesFilePath(),
|
||||
source: {
|
||||
enabled: saved.enabled !== false,
|
||||
baseUrl: String(saved.baseUrl || '').trim(),
|
||||
username: String(saved.username || '').trim(),
|
||||
password: String(saved.password || '').trim(),
|
||||
maxCaptchaAttempts: Number(saved.maxCaptchaAttempts || 3) || 3,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {AdminKhhaoTestLoginInput} [payload] */
|
||||
export async function testAdminKhhaoLogin(payload = /** @type {AdminKhhaoTestLoginInput} */ ({})) {
|
||||
const savedSource = getKhhaoSourceConfig()
|
||||
const session = await loginKhhaoSession({
|
||||
baseUrl: payload.baseUrl,
|
||||
username: payload.username,
|
||||
password: payload.password,
|
||||
maxCaptchaAttempts: payload.maxCaptchaAttempts,
|
||||
baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(),
|
||||
username: String(payload.username || savedSource.username || '').trim(),
|
||||
password: String(payload.password || savedSource.password || '').trim(),
|
||||
maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts,
|
||||
includeImageBase64: payload.includeImageBase64,
|
||||
requestId: `admin-khhao-test-login:${String(payload.username || '').trim() || 'anonymous'}`,
|
||||
requestId: `admin-khhao-test-login:${String(payload.username || savedSource.username || '').trim() || 'anonymous'}`,
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -212,16 +253,17 @@ export async function testAdminKhhaoLogin(payload = /** @type {AdminKhhaoTestLog
|
||||
|
||||
/** @param {AdminKhhaoOrderQueryInput} [payload] */
|
||||
export async function queryAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) {
|
||||
const savedSource = getKhhaoSourceConfig()
|
||||
const session = await loginKhhaoSession({
|
||||
baseUrl: payload.baseUrl,
|
||||
username: payload.username,
|
||||
password: payload.password,
|
||||
maxCaptchaAttempts: payload.maxCaptchaAttempts,
|
||||
requestId: `admin-khhao-query-orders:${String(payload.username || '').trim() || 'anonymous'}`,
|
||||
baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(),
|
||||
username: String(payload.username || savedSource.username || '').trim(),
|
||||
password: String(payload.password || savedSource.password || '').trim(),
|
||||
maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts,
|
||||
requestId: `admin-khhao-query-orders:${String(payload.username || savedSource.username || '').trim() || 'anonymous'}`,
|
||||
})
|
||||
|
||||
const result = await queryKhhaoOrderList({
|
||||
baseUrl: payload.baseUrl,
|
||||
baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(),
|
||||
page: payload.page,
|
||||
limit: payload.limit,
|
||||
session,
|
||||
@@ -239,6 +281,18 @@ export async function queryAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrder
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {AdminKhhaoOrderQueryInput} [payload] */
|
||||
export async function syncAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) {
|
||||
const savedSource = getKhhaoSourceConfig()
|
||||
return syncKhhaoOrders({
|
||||
...payload,
|
||||
baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(),
|
||||
username: String(payload.username || savedSource.username || '').trim(),
|
||||
password: String(payload.password || savedSource.password || '').trim(),
|
||||
maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts,
|
||||
})
|
||||
}
|
||||
|
||||
export async function getAdminFulfillmentBindingConfigs() {
|
||||
const bindings = getOrderFulfillmentBindingConfigs()
|
||||
const rowsResult = await query(
|
||||
|
||||
@@ -9,6 +9,10 @@ import { nowIso } from '../../utils/time.js'
|
||||
import { logWebhook } from '../../utils/logger.js'
|
||||
|
||||
export async function upsertOrderFromWebhook(event) {
|
||||
return upsertOrderFromSource(event, { sourceLabel: 'webhook' })
|
||||
}
|
||||
|
||||
export async function upsertOrderFromSource(event, { sourceLabel = 'source' } = {}) {
|
||||
const now = nowIso()
|
||||
const existing = await findOrderByPlatformOrderId({
|
||||
provider: event.provider,
|
||||
@@ -17,7 +21,7 @@ export async function upsertOrderFromWebhook(event) {
|
||||
platformOrderId: event.platformOrderId,
|
||||
})
|
||||
|
||||
logWebhook('[order-service]', '开始处理 webhook 订单 upsert', {
|
||||
logWebhook('[order-service]', `开始处理 ${sourceLabel} 订单 upsert`, {
|
||||
provider: event.provider,
|
||||
platform: event.platform,
|
||||
shopId: event.shopId,
|
||||
@@ -37,7 +41,7 @@ export async function upsertOrderFromWebhook(event) {
|
||||
const configuredItems = resolvedItems.filter((item) => item.isConfigured)
|
||||
|
||||
if (configuredItems.length === 0) {
|
||||
logWebhook('[order-service]', 'Webhook 订单已忽略:未命中任何已配置履约商品', {
|
||||
logWebhook('[order-service]', `${sourceLabel} 订单已忽略:未命中任何已配置履约商品`, {
|
||||
provider: event.provider,
|
||||
platform: event.platform,
|
||||
shopId: event.shopId,
|
||||
@@ -104,7 +108,7 @@ export async function upsertOrderFromWebhook(event) {
|
||||
const tasks = await syncDeliveryTasksForOrder(order, orderItems)
|
||||
const messageDeliveries = []
|
||||
|
||||
logWebhook('[order-service]', 'Webhook 订单 upsert 完成', {
|
||||
logWebhook('[order-service]', `${sourceLabel} 订单 upsert 完成`, {
|
||||
orderId: order.id,
|
||||
provider: order.provider,
|
||||
platform: order.platform,
|
||||
|
||||
@@ -6,6 +6,42 @@ export function mapKhhaoOrderPreviewList(items = []) {
|
||||
return (Array.isArray(items) ? items : []).map((item) => mapKhhaoOrderPreview(item))
|
||||
}
|
||||
|
||||
export function mapKhhaoOrderToSourceEvent(item = {}) {
|
||||
const preview = mapKhhaoOrderPreview(item)
|
||||
const orderStatus = resolveKhhaoOrderStatus(preview.status)
|
||||
const payStatus = resolveKhhaoPayStatus(preview.status)
|
||||
|
||||
return {
|
||||
provider: 'khhao',
|
||||
platform: preview.platform || 'unknown',
|
||||
shopId: preview.shopId,
|
||||
shopName: preview.shopName,
|
||||
platformOrderId: preview.platformOrderId,
|
||||
orderStatus,
|
||||
payStatus,
|
||||
buyerId: '',
|
||||
buyerName: String(preview.raw.name || '').trim(),
|
||||
receiverContact: '',
|
||||
totalAmount: preview.totalAmountFen,
|
||||
currency: 'CNY',
|
||||
paidAt: payStatus === 'paid' ? normalizePaidAt(preview.orderCreatedAt) : null,
|
||||
rawPayload: preview.raw,
|
||||
items: [
|
||||
{
|
||||
itemId: preview.itemId,
|
||||
externalItemId: preview.itemId,
|
||||
externalSkuCode: preview.skuCode || preview.itemId,
|
||||
externalSkuName: preview.itemTitle || preview.skuCode || preview.itemId,
|
||||
skuCode: preview.skuCode || preview.itemId,
|
||||
skuName: preview.itemTitle || preview.skuCode || preview.itemId,
|
||||
quantity: preview.quantity,
|
||||
snapshot: preview.raw,
|
||||
spec: preview.raw,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export function mapKhhaoOrderPreview(item = {}) {
|
||||
const raw = isPlainObject(item) ? item : {}
|
||||
const platform = resolveKhhaoPlatform(raw.pingtai)
|
||||
@@ -40,6 +76,34 @@ export function resolveKhhaoPlatform(value) {
|
||||
return normalized ? 'unknown' : ''
|
||||
}
|
||||
|
||||
export function resolveKhhaoOrderStatus(value) {
|
||||
const normalized = String(value || '').trim()
|
||||
|
||||
if (normalized === '2') {
|
||||
return 'paid'
|
||||
}
|
||||
|
||||
if (normalized === '0') {
|
||||
return 'created'
|
||||
}
|
||||
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
export function resolveKhhaoPayStatus(value) {
|
||||
const normalized = String(value || '').trim()
|
||||
|
||||
if (normalized === '2') {
|
||||
return 'paid'
|
||||
}
|
||||
|
||||
if (normalized === '0') {
|
||||
return 'unpaid'
|
||||
}
|
||||
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
function normalizeQuantity(value) {
|
||||
const parsed = Number(value)
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : 1
|
||||
@@ -49,6 +113,11 @@ function stripHtmlTags(value) {
|
||||
return String(value || '').replace(/<[^>]+>/g, '').trim()
|
||||
}
|
||||
|
||||
function normalizePaidAt(value) {
|
||||
const text = String(value || '').trim()
|
||||
return text ? text.replace(' ', 'T') : null
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return Object.prototype.toString.call(value) === '[object Object]'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// @ts-check
|
||||
|
||||
import { upsertOrderFromSource } from '../../order/order-service.js'
|
||||
import { mapKhhaoOrderPreview, mapKhhaoOrderToSourceEvent } from './order-mapper-service.js'
|
||||
import { queryKhhaoOrderList } from './order-query-service.js'
|
||||
import { loginKhhaoSession } from './session-service.js'
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* baseUrl?: string
|
||||
* username?: string
|
||||
* password?: string
|
||||
* page?: number | string
|
||||
* limit?: number | string
|
||||
* maxCaptchaAttempts?: number | string
|
||||
* }} [payload]
|
||||
*/
|
||||
export async function syncKhhaoOrders(payload = {}) {
|
||||
const session = await loginKhhaoSession({
|
||||
baseUrl: payload.baseUrl,
|
||||
username: payload.username,
|
||||
password: payload.password,
|
||||
maxCaptchaAttempts: payload.maxCaptchaAttempts,
|
||||
requestId: `admin-khhao-sync-orders:${String(payload.username || '').trim() || 'anonymous'}`,
|
||||
})
|
||||
|
||||
const queryResult = await queryKhhaoOrderList({
|
||||
baseUrl: payload.baseUrl,
|
||||
page: payload.page,
|
||||
limit: payload.limit,
|
||||
session,
|
||||
})
|
||||
|
||||
const results = []
|
||||
|
||||
for (const item of queryResult.items) {
|
||||
const preview = mapKhhaoOrderPreview(item)
|
||||
const sourceEvent = mapKhhaoOrderToSourceEvent(item)
|
||||
const upsertResult = await upsertOrderFromSource(sourceEvent, { sourceLabel: 'khhao-sync' })
|
||||
|
||||
results.push({
|
||||
platformOrderId: preview.platformOrderId,
|
||||
provider: sourceEvent.provider,
|
||||
platform: sourceEvent.platform,
|
||||
shopId: sourceEvent.shopId,
|
||||
skuCode: preview.skuCode,
|
||||
itemTitle: preview.itemTitle,
|
||||
ignored: Boolean(upsertResult.ignored),
|
||||
ignoreReason: String(upsertResult.ignoreReason || '').trim(),
|
||||
orderId: Number(upsertResult.order?.id || 0) || null,
|
||||
orderItemCount: Array.isArray(upsertResult.orderItems) ? upsertResult.orderItems.length : 0,
|
||||
taskCount: Array.isArray(upsertResult.tasks) ? upsertResult.tasks.length : 0,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
baseUrl: session.baseUrl,
|
||||
page: queryResult.page,
|
||||
limit: queryResult.limit,
|
||||
total: queryResult.total,
|
||||
fetchedCount: queryResult.items.length,
|
||||
syncedCount: results.filter((item) => !item.ignored && item.orderId).length,
|
||||
ignoredCount: results.filter((item) => item.ignored).length,
|
||||
results,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// @ts-check
|
||||
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { PROJECT_ROOT } from '../../../config/runtime.js'
|
||||
|
||||
const KHHAO_SOURCES_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'khhao-sources.json')
|
||||
|
||||
export function getKhhaoSourcesFilePath() {
|
||||
return KHHAO_SOURCES_FILE_PATH
|
||||
}
|
||||
|
||||
export function getKhhaoSourceConfig() {
|
||||
return loadKhhaoSourceConfigFromFile()
|
||||
}
|
||||
|
||||
export function saveKhhaoSourceConfig(rawValue) {
|
||||
const normalized = normalizeKhhaoSourceConfig(rawValue)
|
||||
fs.mkdirSync(path.dirname(KHHAO_SOURCES_FILE_PATH), { recursive: true })
|
||||
fs.writeFileSync(KHHAO_SOURCES_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8')
|
||||
return normalized
|
||||
}
|
||||
|
||||
function loadKhhaoSourceConfigFromFile() {
|
||||
if (!fs.existsSync(KHHAO_SOURCES_FILE_PATH)) {
|
||||
return createDefaultKhhaoSourceConfig()
|
||||
}
|
||||
|
||||
try {
|
||||
const rawText = fs.readFileSync(KHHAO_SOURCES_FILE_PATH, 'utf8')
|
||||
return normalizeKhhaoSourceConfig(JSON.parse(rawText))
|
||||
} catch {
|
||||
return createDefaultKhhaoSourceConfig()
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeKhhaoSourceConfig(rawValue) {
|
||||
const source = isPlainObject(rawValue) ? rawValue : {}
|
||||
|
||||
return {
|
||||
enabled: typeof source.enabled === 'boolean' ? source.enabled : true,
|
||||
baseUrl: String(source.baseUrl || 'https://admin.khhao.com').trim() || 'https://admin.khhao.com',
|
||||
username: String(source.username || '').trim(),
|
||||
password: String(source.password || '').trim(),
|
||||
maxCaptchaAttempts: normalizePositiveInteger(source.maxCaptchaAttempts, 3),
|
||||
}
|
||||
}
|
||||
|
||||
function createDefaultKhhaoSourceConfig() {
|
||||
return {
|
||||
enabled: true,
|
||||
baseUrl: 'https://admin.khhao.com',
|
||||
username: '',
|
||||
password: '',
|
||||
maxCaptchaAttempts: 3,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePositiveInteger(value, fallback) {
|
||||
const parsed = Number(value)
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return Object.prototype.toString.call(value) === '[object Object]'
|
||||
}
|
||||
@@ -30,6 +30,10 @@ export {}
|
||||
* @typedef {import('./admin-read-inputs.js').AdminWebhookEventListQueryInput} AdminWebhookEventRouteQuery
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('./admin-write-inputs.js').AdminKhhaoSourceConfigInput} AdminKhhaoSourceConfigRouteBody
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('./admin-write-inputs.js').AdminAgisoShopConfigSaveInput} AdminAgisoShopConfigRouteBody
|
||||
*/
|
||||
|
||||
@@ -67,6 +67,16 @@ export {}
|
||||
* }} AdminAgisoShopConfigSaveInput
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* enabled?: boolean
|
||||
* baseUrl?: string
|
||||
* username?: string
|
||||
* password?: string
|
||||
* maxCaptchaAttempts?: number | string
|
||||
* }} AdminKhhaoSourceConfigInput
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* baseUrl?: string
|
||||
|
||||
Reference in New Issue
Block a user