收紧后端 TypeScript:统一 Json 类型与 TaskContext 契约
关闭 allowJs;集中 JsonObject 定义并替换分散 any 别名;parseTaskContext 返回 TaskContext;任务详情 API 标注 AdminTaskDetailView;测试减少 as any。
This commit is contained in:
@@ -3,10 +3,9 @@ import { TASK_STATUS } from '../domain/task-status.js'
|
||||
import { maskCode } from '../utils/masking.js'
|
||||
import { parseTaskContext } from '../utils/task-json.js'
|
||||
import { normalizeTimestampIso } from '../utils/time.js'
|
||||
import type { JsonObject, JsonRecord } from '../types/json.js'
|
||||
|
||||
type QueryExecutor = (text: string, params?: unknown[]) => Promise<{ rows: unknown[] }>
|
||||
type JsonRecord = Record<string, any>
|
||||
|
||||
export type KuaishouCloudSourceLoadStat = {
|
||||
sourceKey: string
|
||||
activeCount: number
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Router } from 'express'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
import { getAdminAuditLogs } from '../../services/admin/admin-audit-service.js'
|
||||
import { createJsonHandler, requireAdminRoles } from './session.js'
|
||||
|
||||
type AdminAuditLogRouteQuery = Record<string, any>
|
||||
type AdminAuditLogRouteQuery = JsonObject
|
||||
|
||||
const router = Router()
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import process from 'node:process'
|
||||
import { Router } from 'express'
|
||||
import type { JsonObject } from '../types/json.js'
|
||||
|
||||
import { createRateLimitMiddleware } from '../middleware/rate-limit.js'
|
||||
import { handleSendCode } from '../services/platforms/kuaishou-industry/send-code-service.js'
|
||||
@@ -152,10 +153,10 @@ router.post('/consume-code', industryRateLimit, async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
function mergeRequestParams(req: any): Record<string, any> {
|
||||
function mergeRequestParams(req: any): JsonObject {
|
||||
const query = req.query || {}
|
||||
const body = req.body || {}
|
||||
const params: Record<string, any> = { ...query }
|
||||
const params: JsonObject = { ...query }
|
||||
|
||||
for (const [key, value] of Object.entries(body)) {
|
||||
if (params[key] === undefined && key !== 'param') {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { createAdminAuditLog, listAdminAuditLogs } from '../../repositories/admin-audit-log-repo.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import { normalizeDateQuery, normalizePage, normalizePageSize, safeParseJson } from './admin-query-utils.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
export async function writeAdminAuditLog(session: JsonObject | null | undefined, payload: JsonObject = {}) {
|
||||
if (!session?.userId) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crypto from 'node:crypto'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
import { runtimeConfig } from '../../config/runtime.js'
|
||||
import {
|
||||
@@ -15,8 +16,6 @@ import { normalizePage, normalizePageSize } from './admin-query-utils.js'
|
||||
|
||||
type AdminUserRow = NonNullable<Awaited<ReturnType<typeof getAdminUserById>>>
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export type AdminSession = {
|
||||
sessionId: string
|
||||
userId: number
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { query } from '../../db/client.js'
|
||||
import { TASK_STATUS } from '../../domain/task-status.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
export async function getAdminDashboardSummary() {
|
||||
const todayPrefix = nowIso().slice(0, 10)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
export function normalizePage(rawValue: unknown) {
|
||||
const parsed = Number(rawValue)
|
||||
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 1
|
||||
|
||||
@@ -5,6 +5,7 @@ import { listOrderItemsByOrderId } from '../../repositories/order-item-repo.js'
|
||||
import { getOrderById, listOrders } from '../../repositories/order-repo.js'
|
||||
import { getTaskById, listTasks, listTasksByOrderId } from '../../repositories/task-repo.js'
|
||||
import { listTaskEventsByTaskId } from '../../repositories/task-event-repo.js'
|
||||
import type { JsonObject, JsonRecord } from '../../types/json.js'
|
||||
import {
|
||||
listKuaishouIndustryVouchersByOid,
|
||||
listKuaishouIndustryVouchersByTaskId,
|
||||
@@ -58,6 +59,7 @@ import type {
|
||||
AdminOrderListResponse,
|
||||
AdminTaskListResponse,
|
||||
} from '../../types/admin/read-models.js'
|
||||
import type { AdminTaskDetailView } from '../../types/admin/task-detail.js'
|
||||
import type {
|
||||
AdminEntityIdInput,
|
||||
AdminOrderListQueryInput,
|
||||
@@ -69,8 +71,6 @@ import type {
|
||||
TaskRow,
|
||||
} from '../../types/repository/rows.js'
|
||||
|
||||
type JsonRecord = Record<string, any>
|
||||
|
||||
export async function getAdminOrders(
|
||||
query: AdminOrderListQueryInput = {},
|
||||
): Promise<AdminOrderListResponse> {
|
||||
@@ -218,7 +218,7 @@ export async function getAdminTasks(
|
||||
export async function getAdminTaskDetail(
|
||||
taskId: AdminEntityIdInput,
|
||||
session: AdminViewerSessionInput | null = null,
|
||||
): Promise<JsonRecord> {
|
||||
): Promise<AdminTaskDetailView> {
|
||||
const task = await getTaskById(Number(taskId))
|
||||
|
||||
if (!task) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { safeParseJson } from './admin-query-utils.js'
|
||||
import { normalizeAdminRole } from './admin-auth-service.js'
|
||||
import { asJsonObject, type JsonObject, JsonRecord } from '../../types/json.js'
|
||||
import {
|
||||
parseTaskContext as parseTaskContextValue,
|
||||
parseTaskState as parseTaskStateValue,
|
||||
@@ -9,7 +10,6 @@ import { canRegenerateClaimLinkStatus, isTaskFinalStatus } from '../../domain/ta
|
||||
import type { AdminViewerSessionInput } from '../../types/admin/read-inputs.js'
|
||||
import type { OrderItemRow, TaskRow } from '../../types/repository/rows.js'
|
||||
|
||||
type JsonRecord = Record<string, any>
|
||||
type CloudSourceLabelMap = Map<string, string> | Record<string, string>
|
||||
|
||||
type KuaishouCloudFulfillmentMapOptions = {
|
||||
@@ -74,14 +74,14 @@ export function mapKuaishouCloudFulfillmentContext(
|
||||
}
|
||||
|
||||
const record = value as JsonRecord
|
||||
const ticket = record.ticket && typeof record.ticket === 'object' ? record.ticket : {}
|
||||
const binding = record.binding && typeof record.binding === 'object' ? record.binding : {}
|
||||
const role = record.role && typeof record.role === 'object' ? record.role : {}
|
||||
const purchase = record.purchase && typeof record.purchase === 'object' ? record.purchase : {}
|
||||
const dispatch = record.dispatch && typeof record.dispatch === 'object' ? record.dispatch : {}
|
||||
const ticket = asJsonObject(record.ticket)
|
||||
const binding = asJsonObject(record.binding)
|
||||
const role = asJsonObject(record.role)
|
||||
const purchase = asJsonObject(record.purchase)
|
||||
const dispatch = asJsonObject(record.dispatch)
|
||||
const returnNumber =
|
||||
record.returnNumber && typeof record.returnNumber === 'object' ? record.returnNumber : {}
|
||||
const consume = record.consume && typeof record.consume === 'object' ? record.consume : {}
|
||||
asJsonObject(record.returnNumber)
|
||||
const consume = asJsonObject(record.consume)
|
||||
const cloudSourceKeys = Array.isArray(binding.cloudSourceKeys)
|
||||
? binding.cloudSourceKeys.map((value: unknown) => String(value || '').trim()).filter(Boolean)
|
||||
: []
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getTaskById } from '../../repositories/task-repo.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { safeParseJson } from './admin-query-utils.js'
|
||||
import { asJsonObject, type JsonObject, JsonRecord } from '../../types/json.js'
|
||||
import {
|
||||
TASK_STATUS,
|
||||
isTaskFulfillmentCompletedStatus,
|
||||
@@ -22,8 +23,6 @@ import type {
|
||||
} from '../../types/repository/rows.js'
|
||||
import type { AdminViewerContext } from './admin-read-shared-helpers.js'
|
||||
|
||||
type JsonRecord = Record<string, any>
|
||||
|
||||
type TaskFulfillmentState = {
|
||||
resourceStatus: string
|
||||
customerStatus: string
|
||||
@@ -263,7 +262,7 @@ function isTaskFulfillmentCompleted(task: TaskRow | null | undefined): boolean {
|
||||
}
|
||||
|
||||
function normalizeRecord(value: unknown): JsonRecord {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
||||
return asJsonObject(value)
|
||||
}
|
||||
|
||||
function getTaskRetryCount(task: TaskRow | null | undefined): number {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asJsonObject, type JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
findKuaishouIndustryVoucherByCode,
|
||||
listKuaishouIndustryVouchersByOid,
|
||||
@@ -27,8 +28,6 @@ import { reverseKuaishouIndustryCallback } from '../platforms/kuaishou-industry/
|
||||
import type { KuaishouIndustryOpenApiCallResult } from '../platforms/kuaishou-industry/openapi-client.js'
|
||||
import type { KuaishouIndustryVoucherRow, TaskRow } from '../../types/repository/rows.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export async function listAdminKuaishouIndustryVouchers(
|
||||
input: KuaishouIndustryVoucherAdminListQuery = {},
|
||||
) {
|
||||
@@ -462,7 +461,7 @@ function parseJsonObject(value: unknown): JsonObject {
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '{}'))
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
return asJsonObject(parsed)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function pickFirstNonEmpty(values: unknown[]) {
|
||||
for (const value of values) {
|
||||
const normalized = String(value || "").trim();
|
||||
@@ -17,6 +15,7 @@ export function isPlainObject(value: unknown): value is JsonObject {
|
||||
|
||||
import { getCloudtentaclesSourceByKey } from "../../../platforms/cloudtentacles/source-config-service.js";
|
||||
import { getCloudtentaclesSessionStateByKey } from "../../../platforms/cloudtentacles/session-state-service.js";
|
||||
import type { JsonObject } from '../../../../types/json.js'
|
||||
import {
|
||||
normalizeCloudtentaclesDeviceId,
|
||||
normalizeCloudtentaclesDeviceType,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JsonObject } from '../../../../types/json.js'
|
||||
import {
|
||||
getCloudtentaclesSourceByKey,
|
||||
getCloudtentaclesSourcesFilePath,
|
||||
@@ -79,8 +80,6 @@ import type {
|
||||
AdminCloudtentaclesVirtualNumberInput,
|
||||
} from "../../../../types/admin/write-inputs.js";
|
||||
|
||||
type JsonObject = Record<string, any>;
|
||||
|
||||
type LocalCloudtentaclesTask = {
|
||||
taskId: number;
|
||||
taskNo: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JsonObject } from '../../../../types/json.js'
|
||||
import {
|
||||
maskPhone as maskPhoneValue,
|
||||
maskSecret as maskSecretValue,
|
||||
@@ -7,8 +8,6 @@ import {
|
||||
normalizeCloudtentaclesDeviceType,
|
||||
} from "../../../platforms/cloudtentacles/defaults.js";
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function maskSecret(value: unknown) {
|
||||
return maskSecretValue(value);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getCloudtentaclesSourceByKey } from "../../../platforms/cloudtentacles/source-config-service.js";
|
||||
import { getCloudtentaclesSessionStateByKey } from "../../../platforms/cloudtentacles/session-state-service.js";
|
||||
import type { JsonObject } from '../../../../types/json.js'
|
||||
import {
|
||||
normalizeCloudtentaclesDeviceId,
|
||||
normalizeCloudtentaclesDeviceType,
|
||||
@@ -12,8 +13,6 @@ import { maskPhone, maskSecret } from "./mappers.js";
|
||||
|
||||
const DEFAULT_CLOUDTENTACLES_BASE_URL = "https://123.207.217.176";
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
function _resolveSourceKey(payload: JsonObject = {}) {
|
||||
return String(payload.sourceKey || "").trim() || "default";
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { JsonObject } from '../../../../types/json.js'
|
||||
import {
|
||||
normalizeCloudtentaclesDeviceId,
|
||||
normalizeCloudtentaclesDeviceType,
|
||||
} from "../../../platforms/cloudtentacles/defaults.js";
|
||||
|
||||
type JsonObject = Record<string, any>;
|
||||
|
||||
export function normalizeAdminCloudtentaclesSourceConfigPayload(
|
||||
payload: JsonObject = {}
|
||||
) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getKuaishouFeifeiConfig } from '../../platforms/kuaishou-feifei/config.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
createKuaishouFeifeiOrder,
|
||||
listKuaishouFeifeiProducts,
|
||||
@@ -16,8 +17,6 @@ import {
|
||||
import { normalizeCloudtentaclesMatchName } from '../../order/cloudtentacles-match-utils.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function getAdminKuaishouFeifeiConfig() {
|
||||
const source = getAdminEditableKuaishouFeifeiConfig()
|
||||
const effective = getKuaishouFeifeiConfig()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { maskSecret } from '../../../utils/masking.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
findKuaishouIndustryShopConfig,
|
||||
getKuaishouIndustrySourceConfig,
|
||||
@@ -13,8 +14,6 @@ import {
|
||||
refreshKuaishouIndustryAccessToken,
|
||||
} from '../../platforms/kuaishou-industry/token-service.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
const SECRET_FIELDS = [
|
||||
'appSecret',
|
||||
'signSecret',
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
failOpen91Order,
|
||||
listOpen91Orders,
|
||||
retryOpen91Order,
|
||||
} from '../../platforms/ninetyone/order-service.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export async function getAdminNinetyoneOrders(query: JsonObject = {}) {
|
||||
return listOpen91Orders({
|
||||
page: Number(query.page || 1) || 1,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
getNotificationConfig,
|
||||
getNotificationConfigFilePath,
|
||||
@@ -24,8 +25,6 @@ import type {
|
||||
AdminScheduledJobsConfigInput,
|
||||
} from '../../../types/admin/write-inputs.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function getAdminNotificationConfig() {
|
||||
const config = getNotificationConfig()
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getOrderById } from '../../../repositories/order-repo.js'
|
||||
import { listTasksByOrderId, updateTask } from '../../../repositories/task-repo.js'
|
||||
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
findKuaishouIndustryVoucherByCode,
|
||||
listKuaishouIndustryVouchersByOid,
|
||||
@@ -670,9 +671,9 @@ async function getRequiredIndustryVoucherForTask(task: TaskRow): Promise<Kuaisho
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeAdminTaskIndustryVoucherContext(value: unknown): Record<string, any> {
|
||||
function normalizeAdminTaskIndustryVoucherContext(value: unknown): JsonObject {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as Record<string, any>
|
||||
? value as JsonObject
|
||||
: {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import type { HttpErrorLike } from '../../../utils/http.js'
|
||||
import {
|
||||
resolvePersistedCloudtentaclesContextBySourceKeys,
|
||||
} from '../../fulfillment/kuaishou-cloud/cloudtentacles-context.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import { resolvePersistedCloudtentaclesContextBySourceKeys } from '../../fulfillment/kuaishou-cloud/cloudtentacles-context.js'
|
||||
|
||||
export type { JsonObject }
|
||||
|
||||
export {
|
||||
KUAISHOU_CLOUD_FIXED_VN_KEY,
|
||||
@@ -10,7 +11,6 @@ export {
|
||||
normalizeKuaishouCloudFlow,
|
||||
normalizeKuaishouCloudRoleInfo,
|
||||
resolveKuaishouCloudBindUrlExpiresAt,
|
||||
type JsonObject,
|
||||
} from '../../fulfillment/kuaishou-cloud/domain.js'
|
||||
export {
|
||||
prepareKuaishouCloudBindResourceWithFallback,
|
||||
@@ -34,17 +34,17 @@ export type CloudSkuLikeItem = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type KuaishouCloudFlow = Record<string, any> & {
|
||||
export type KuaishouCloudFlow = JsonObject & {
|
||||
configId: string
|
||||
internalSkuCode: string
|
||||
internalSkuName: string
|
||||
ticket: Record<string, any>
|
||||
binding: Record<string, any>
|
||||
role: Record<string, any>
|
||||
purchase: Record<string, any>
|
||||
dispatch: Record<string, any>
|
||||
returnNumber: Record<string, any>
|
||||
consume: Record<string, any>
|
||||
ticket: JsonObject
|
||||
binding: JsonObject
|
||||
role: JsonObject
|
||||
purchase: JsonObject
|
||||
dispatch: JsonObject
|
||||
returnNumber: JsonObject
|
||||
consume: JsonObject
|
||||
}
|
||||
|
||||
export type KuaishouCloudBindingResources = {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
listFulfillmentProfileRequirements,
|
||||
replaceFulfillmentProfileRequirements,
|
||||
@@ -6,7 +7,6 @@ import {
|
||||
} from '../../repositories/fulfillment-profile-repo.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type CoreProfile = {
|
||||
profileKey: string
|
||||
name: string
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createHttpError } from '../../utils/http.js'
|
||||
import { parseTaskContext } from '../../utils/task-json.js'
|
||||
import { isTaskFinalStatus, normalizeTaskStatus, TASK_STATUS } from '../../domain/task-status.js'
|
||||
import type { TaskRow } from '../../types/repository/rows.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
export type ClaimIdentity = {
|
||||
expectedUid: string
|
||||
@@ -9,8 +10,6 @@ export type ClaimIdentity = {
|
||||
source: string
|
||||
}
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
const CLAIM_UID_MAX_LENGTH = 64
|
||||
|
||||
export function normalizeClaimUid(value: unknown): string {
|
||||
@@ -131,10 +130,22 @@ export function assertLewanAutoFulfillmentUidReady(
|
||||
return expectedUid
|
||||
}
|
||||
|
||||
export type ClaimIdentityAdminSummary = {
|
||||
expectedUid: string
|
||||
submittedAt: string | null
|
||||
source: string
|
||||
ready: boolean
|
||||
boundUid: string
|
||||
boundRoleName: string
|
||||
uidMatched: boolean | null
|
||||
compatibilityMode: 'uid' | 'legacy_no_uid'
|
||||
note: string
|
||||
}
|
||||
|
||||
export function buildClaimIdentityAdminSummary(
|
||||
context: unknown,
|
||||
options: { flowLike?: unknown; taskRoleId?: unknown; taskRoleName?: unknown } = {},
|
||||
) {
|
||||
): ClaimIdentityAdminSummary {
|
||||
const identity = getClaimIdentityFromContext(context)
|
||||
const boundUid = resolveBoundRoleUid(options.flowLike) || normalizeClaimUid(options.taskRoleId)
|
||||
const flow = isPlainObject(options.flowLike) ? options.flowLike : {}
|
||||
|
||||
@@ -11,9 +11,9 @@ import { buildClaimUrl } from './claim-service.js'
|
||||
import { buildClaimIdentityPayload, getClaimIdentityFromContext } from './claim-identity.js'
|
||||
import { resolveKuaishouFeifeiH5UrlWithUid } from '../fulfillment/kuaishou-feifei/index.js'
|
||||
import type { ClaimTokenRow, OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js'
|
||||
import { asJsonObject, type JsonObject } from '../../types/json.js'
|
||||
|
||||
export const CLAIM_TERMINAL_STATUSES = new Set([TASK_STATUS.EXPIRED, TASK_STATUS.CLOSED])
|
||||
type JsonObject = Record<string, any>
|
||||
type ClaimContext = {
|
||||
claimToken: ClaimTokenRow
|
||||
task: TaskRow
|
||||
@@ -343,13 +343,13 @@ export function mapClaimKuaishouCloudFulfillment(task: TaskRow, order: OrderRow)
|
||||
return null
|
||||
}
|
||||
|
||||
const binding = source.binding && typeof source.binding === 'object' ? source.binding : {}
|
||||
const role = source.role && typeof source.role === 'object' ? source.role : {}
|
||||
const purchase = source.purchase && typeof source.purchase === 'object' ? source.purchase : {}
|
||||
const ticket = source.ticket && typeof source.ticket === 'object' ? source.ticket : {}
|
||||
const dispatch = source.dispatch && typeof source.dispatch === 'object' ? source.dispatch : {}
|
||||
const returnNumber = source.returnNumber && typeof source.returnNumber === 'object' ? source.returnNumber : {}
|
||||
const consume = source.consume && typeof source.consume === 'object' ? source.consume : {}
|
||||
const binding = asJsonObject(source.binding)
|
||||
const role = asJsonObject(source.role)
|
||||
const purchase = asJsonObject(source.purchase)
|
||||
const ticket = asJsonObject(source.ticket)
|
||||
const dispatch = asJsonObject(source.dispatch)
|
||||
const returnNumber = asJsonObject(source.returnNumber)
|
||||
const consume = asJsonObject(source.consume)
|
||||
const roleName = String(role.name || binding.roleName || '').trim()
|
||||
const roleId = String(role.rid || binding.roleId || '').trim()
|
||||
const defaultRoleName = String(role.defaultName || role.defaultRoleName || '').trim()
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getTaskById, updateTask, updateTaskStatusIfCurrent } from '../../reposi
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { parseTaskContext as parseTaskContextValue } from '../../utils/task-json.js'
|
||||
import { addHours, nowIso } from '../../utils/time.js'
|
||||
import { asJsonObject, type JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
TASK_STATUS,
|
||||
canRedeemKuaishouCloudClaimStatus,
|
||||
@@ -29,7 +30,6 @@ import { buildClaimDetailPayload, getClaimContext } from './kuaishou-cloud-claim
|
||||
import { syncKuaishouCloudRoleInfo } from './kuaishou-cloud-sync-service.js'
|
||||
import type { TaskRow } from '../../types/repository/rows.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type ClaimDetailPayload = ReturnType<typeof buildClaimDetailPayload>
|
||||
|
||||
async function verifyIndustryVoucherTicket(
|
||||
@@ -537,9 +537,7 @@ function isKuaishouCloudMockTask(task: Partial<TaskRow> | null | undefined) {
|
||||
|
||||
function isKuaishouCloudMockContext(context: JsonObject = {}) {
|
||||
const flow =
|
||||
context.kuaishouCloudFulfillment && typeof context.kuaishouCloudFulfillment === 'object'
|
||||
? context.kuaishouCloudFulfillment
|
||||
: {}
|
||||
asJsonObject(context.kuaishouCloudFulfillment)
|
||||
const mock = flow.mock && typeof flow.mock === 'object' ? flow.mock : context.mock
|
||||
|
||||
return Boolean(mock && typeof mock === 'object' && mock.enabled === true)
|
||||
@@ -558,7 +556,7 @@ function buildMockVerifiedKuaishouCloudFlow(value: unknown, timestamp: string) {
|
||||
return {
|
||||
...flow,
|
||||
mock: {
|
||||
...(source.mock && typeof source.mock === 'object' ? source.mock : {}),
|
||||
...(asJsonObject(source.mock)),
|
||||
enabled: true,
|
||||
},
|
||||
binding: {
|
||||
@@ -593,7 +591,7 @@ async function completeMockKuaishouCloudClaimTask(task: TaskRow, timestamp: stri
|
||||
const nextFlow = {
|
||||
...flow,
|
||||
mock: {
|
||||
...(source.mock && typeof source.mock === 'object' ? source.mock : {}),
|
||||
...(asJsonObject(source.mock)),
|
||||
enabled: true,
|
||||
},
|
||||
dispatch: {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
refreshKuaishouCloudTaskRoleInfo,
|
||||
} from '../fulfillment/kuaishou-cloud/index.js'
|
||||
import type { TaskRow } from '../../types/repository/rows.js'
|
||||
import { parseTaskContext as parseTaskContextValue } from '../../utils/task-json.js'
|
||||
|
||||
export async function syncKuaishouCloudRoleInfo(task: TaskRow) {
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
|
||||
@@ -45,20 +46,6 @@ export async function syncKuaishouCloudRoleInfo(task: TaskRow) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseTaskContext(task: Partial<TaskRow> | null | undefined): Record<string, any> {
|
||||
const rawValue = task?.context_json
|
||||
|
||||
if (!rawValue) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (typeof rawValue === 'object') {
|
||||
return rawValue
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(String(rawValue || '{}'))
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
function parseTaskContext(task: Partial<TaskRow> | null | undefined) {
|
||||
return parseTaskContextValue(task)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
isClaimUidMatched,
|
||||
normalizeClaimUid,
|
||||
} from "../../claim/claim-identity.js";
|
||||
import { asJsonObject } from "../../../types/json.js";
|
||||
import {
|
||||
normalizeKuaishouCloudFlow,
|
||||
normalizeKuaishouCloudRoleInfo,
|
||||
@@ -26,13 +27,9 @@ export async function syncKuaishouCloudRoleInfoBeforeDispatch(
|
||||
String(input.errorCodePrefix || "kuaishou_cloud").trim() ||
|
||||
"kuaishou_cloud";
|
||||
const cloudContext =
|
||||
input.cloudContext && typeof input.cloudContext === "object"
|
||||
? input.cloudContext
|
||||
: {};
|
||||
asJsonObject(input.cloudContext);
|
||||
const taskContext =
|
||||
input.taskContext && typeof input.taskContext === "object"
|
||||
? input.taskContext
|
||||
: {};
|
||||
asJsonObject(input.taskContext);
|
||||
const flow = normalizeKuaishouCloudFlow(
|
||||
input.flow || taskContext.kuaishouCloudFulfillment
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { resolveCloudtentaclesConfig } from '../../platforms/cloudtentacles/helpers.js'
|
||||
import { maskCode as maskCodeValue, maskPhone as maskPhoneValue } from '../../../utils/masking.js'
|
||||
import { asJsonObject, type JsonObject } from '../../../types/json.js'
|
||||
|
||||
export type { JsonObject }
|
||||
|
||||
export const KUAISHOU_CLOUD_FIXED_VN_KEY = '1'
|
||||
|
||||
export type JsonObject = Record<string, any>
|
||||
|
||||
const KUAISHOU_CLOUD_EXECUTOR_KEYS = new Set(['kuaishou_ct_assisted', 'kuaishou-industry'])
|
||||
|
||||
export function isKuaishouCloudTask(task: unknown) {
|
||||
@@ -19,15 +20,15 @@ export function isIndustryEVoucherTask(task: unknown) {
|
||||
|
||||
export function normalizeKuaishouCloudFlow(value: unknown) {
|
||||
const source: JsonObject = value && typeof value === 'object' ? (value as JsonObject) : {}
|
||||
const binding = source.binding && typeof source.binding === 'object' ? source.binding : {}
|
||||
const role = source.role && typeof source.role === 'object' ? source.role : {}
|
||||
const purchase = source.purchase && typeof source.purchase === 'object' ? source.purchase : {}
|
||||
const dispatch = source.dispatch && typeof source.dispatch === 'object' ? source.dispatch : {}
|
||||
const binding = asJsonObject(source.binding)
|
||||
const role = asJsonObject(source.role)
|
||||
const purchase = asJsonObject(source.purchase)
|
||||
const dispatch = asJsonObject(source.dispatch)
|
||||
const returnNumber =
|
||||
source.returnNumber && typeof source.returnNumber === 'object' ? source.returnNumber : {}
|
||||
const consume = source.consume && typeof source.consume === 'object' ? source.consume : {}
|
||||
const ticket = source.ticket && typeof source.ticket === 'object' ? source.ticket : {}
|
||||
const rebind = source.rebind && typeof source.rebind === 'object' ? source.rebind : {}
|
||||
asJsonObject(source.returnNumber)
|
||||
const consume = asJsonObject(source.consume)
|
||||
const ticket = asJsonObject(source.ticket)
|
||||
const rebind = asJsonObject(source.rebind)
|
||||
const deliveryItems = normalizeDeliveryItems(source, binding)
|
||||
|
||||
const roleName = String(role.name || binding.roleName || '').trim()
|
||||
@@ -180,7 +181,7 @@ function normalizeRoleNameForCompare(value: unknown) {
|
||||
|
||||
export function normalizeKuaishouCloudDeliveryItems(value: unknown) {
|
||||
const source: JsonObject = value && typeof value === 'object' ? (value as JsonObject) : {}
|
||||
const binding = source.binding && typeof source.binding === 'object' ? source.binding : {}
|
||||
const binding = asJsonObject(source.binding)
|
||||
return normalizeDeliveryItems(source, binding)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { parseTaskContext as parseTaskContextValue } from "../../../utils/task-json.js";
|
||||
import type { TaskRow } from "../../../types/repository/rows.js";
|
||||
|
||||
type JsonObject = Record<string, any>;
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
export function normalizeActor(actor: unknown) {
|
||||
if (!actor || typeof actor !== "object") {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { parseTaskContext } from '../../../utils/task-json.js'
|
||||
import { nowIso } from '../../../utils/time.js'
|
||||
import { TASK_STATUS } from '../../../domain/task-status.js'
|
||||
import { appendUidToUrl, getClaimIdentityFromContext } from '../../claim/claim-identity.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
createKuaishouFeifeiOrder,
|
||||
queryKuaishouFeifeiOrder,
|
||||
@@ -17,8 +18,6 @@ import { buildKuaishouIndustryVoucherContext } from '../../platforms/kuaishou-in
|
||||
import { normalizeShortLinkPayload } from '../../short-links/short-link-service.js'
|
||||
import type { TaskRow } from '../../../types/repository/rows.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function isKuaishouFeifeiTask(task: Partial<TaskRow> | null | undefined) {
|
||||
return String(task?.executor_key || '').trim() === 'kuaishou_feifei'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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'
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
const NOTIFICATION_CONFIG_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'notification-config.json')
|
||||
const DEFAULT_BARK_SERVER_URL = 'https://api.day.app'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type NotificationRecipient = {
|
||||
id: string
|
||||
name: string
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { logWarn } from '../../utils/logger.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
getNotificationConfig,
|
||||
listEnabledBarkRecipients,
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
import { sendBarkNotification } from './bark-service.js'
|
||||
import { sendWpushNotification } from './wpush-service.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type NotificationResult = ReturnType<typeof mapNotificationResult>
|
||||
type NotificationInput = {
|
||||
title?: string
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import crypto from 'node:crypto'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { assertOpen91Config } from './config.js'
|
||||
import { normalizeOpen91String } from './payload.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function buildOpen91SignSource(params: JsonObject = {}, { secret } = assertOpen91Config()) {
|
||||
const entries = Object.entries(params)
|
||||
.filter(([key]) => key !== 'sign')
|
||||
|
||||
@@ -3,6 +3,7 @@ import { listKuaishouIndustryVouchersByOid } from '../../repositories/kuaishou-i
|
||||
import { listTasksByOrderId } from '../../repositories/task-repo.js'
|
||||
import { resolveTaskDeliveryLink } from '../fulfillment/delivery-link-service.js'
|
||||
import { logIntegration } from '../../utils/logger.js'
|
||||
import { asJsonObject, type JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
OPEN_91_MANUAL_FAILED_STATUS,
|
||||
OPEN_91_PENDING_CONFIG_STATUS,
|
||||
@@ -32,8 +33,6 @@ import {
|
||||
} from './response.js'
|
||||
import type { KuaishouIndustryVoucherRow, OrderRow, TaskRow } from '../../types/repository/rows.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export async function queryOpen91Order(payload: JsonObject = {}, { requestId = '' } = {}) {
|
||||
const config = assertOpen91Config()
|
||||
const normalized = normalizeOpen91QueryPayload(payload)
|
||||
@@ -245,7 +244,7 @@ function parseJsonObject(value: unknown): JsonObject {
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '{}'))
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
return asJsonObject(parsed)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { assertOpen91Config } from './config.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
export function normalizeOpen91String(value: unknown) {
|
||||
return String(value || '').trim()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
OPEN_91_DEFAULT_FAIL_CODE,
|
||||
OPEN_91_SUCCESS_MESSAGE,
|
||||
@@ -5,8 +6,6 @@ import {
|
||||
import { isOpen91FailedTaskStatus } from '../../domain/task-status.js'
|
||||
import { normalizeOpen91String } from './payload.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function buildOpen91SuccessResponse(data: unknown, message = OPEN_91_SUCCESS_MESSAGE) {
|
||||
return {
|
||||
code: 200,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
listCloudtentaclesSources,
|
||||
} from '../platforms/cloudtentacles/source-config-service.js'
|
||||
@@ -17,8 +18,6 @@ import {
|
||||
} from './cloudtentacles-override-rule-service.js'
|
||||
import { normalizeCloudtentaclesMatchName } from './cloudtentacles-match-utils.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
type CloudtentaclesSource = {
|
||||
key?: string
|
||||
label?: string
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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'
|
||||
@@ -14,8 +15,6 @@ const CLOUDTENTACLES_OVERRIDE_RULE_FILE_PATH = path.join(
|
||||
'cloudtentacles-override-rules.json',
|
||||
)
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export type CloudtentaclesOverrideDeliveryItem = {
|
||||
cloudSkuId: number
|
||||
cloudSkuName: string
|
||||
|
||||
@@ -2,22 +2,111 @@ import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { syncDeliveryTasksForOrderWithDeps } from './delivery-task-service.js'
|
||||
import type { OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
type CreatedTaskInput = {
|
||||
orderItemId?: number
|
||||
contextJson?: string
|
||||
taskStatus?: string
|
||||
executorKey?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
function stubOrder(patch: Partial<OrderRow> & Pick<OrderRow, 'id' | 'platform_order_id'>): OrderRow {
|
||||
return {
|
||||
id: patch.id,
|
||||
order_id: patch.id,
|
||||
provider: patch.provider || 'open_91',
|
||||
platform: patch.platform || 'kuaishou',
|
||||
shop_id: patch.shop_id || 'shop-1',
|
||||
shop_name: patch.shop_name || '测试店铺',
|
||||
platform_order_id: patch.platform_order_id,
|
||||
order_status: patch.order_status || 'created',
|
||||
pay_status: patch.pay_status || 'pending_payment',
|
||||
buyer_id: '',
|
||||
buyer_name: '',
|
||||
receiver_contact: '',
|
||||
total_amount: 0,
|
||||
currency: 'CNY',
|
||||
raw_payload_json: '{}',
|
||||
paid_at: null,
|
||||
created_at: '2026-05-29T00:00:00.000Z',
|
||||
updated_at: '2026-05-29T00:00:00.000Z',
|
||||
...patch,
|
||||
} as OrderRow
|
||||
}
|
||||
|
||||
function stubOrderItem(
|
||||
patch: Partial<OrderItemRow> & Pick<OrderItemRow, 'id' | 'order_id' | 'sku_code'>,
|
||||
): OrderItemRow {
|
||||
return {
|
||||
id: patch.id,
|
||||
order_id: patch.order_id,
|
||||
sku_code: patch.sku_code,
|
||||
sku_name: patch.sku_name || patch.sku_code,
|
||||
quantity: patch.quantity || 1,
|
||||
spec_json: '{}',
|
||||
item_snapshot_json: patch.item_snapshot_json || {},
|
||||
created_at: '2026-05-29T00:00:00.000Z',
|
||||
updated_at: '2026-05-29T00:00:00.000Z',
|
||||
...patch,
|
||||
} as OrderItemRow
|
||||
}
|
||||
|
||||
function stubCreatedTask(id: number, input: CreatedTaskInput): TaskRow {
|
||||
return {
|
||||
id,
|
||||
order_id: 0,
|
||||
order_item_id: Number(input.orderItemId || 0),
|
||||
unit_index: 1,
|
||||
platform_order_id: '',
|
||||
profile_id: 0,
|
||||
task_no: 'DT-test',
|
||||
executor_key: String(input.executorKey || ''),
|
||||
task_status: String(input.taskStatus || 'pending'),
|
||||
delivery_status: 'pending',
|
||||
result_code: '',
|
||||
result_message: '',
|
||||
automation_mode: 'manual',
|
||||
requires_claim: true,
|
||||
user_action_status: 'pending_claim',
|
||||
attempt_count: 0,
|
||||
runtime_session_id: '',
|
||||
login_type: '',
|
||||
nickname: '',
|
||||
role_name: '',
|
||||
role_id: '',
|
||||
area: '',
|
||||
partition_name: '',
|
||||
claim_token: '',
|
||||
primary_claim_token: '',
|
||||
primary_claim_token_id: null,
|
||||
primary_claim_token_status: '',
|
||||
artifacts_json: '{}',
|
||||
context_json: String(input.contextJson || '{}'),
|
||||
screenshot_path: '',
|
||||
last_error: '',
|
||||
retry_count: 0,
|
||||
created_at: '2026-05-29T00:00:00.000Z',
|
||||
updated_at: '2026-05-29T00:00:00.000Z',
|
||||
claimed_at: null,
|
||||
role_confirmed_at: null,
|
||||
redeemed_at: null,
|
||||
} as TaskRow
|
||||
}
|
||||
|
||||
test('syncDeliveryTasksForOrder 多账号候选时不提前固定 cloudtentacles 账号', async () => {
|
||||
const createdInputs: any[] = []
|
||||
const createdInputs: CreatedTaskInput[] = []
|
||||
|
||||
await syncDeliveryTasksForOrderWithDeps(
|
||||
{
|
||||
stubOrder({
|
||||
id: 1,
|
||||
pay_status: 'pending_payment',
|
||||
provider: 'open_91',
|
||||
platform: 'kuaishou',
|
||||
shop_id: 'shop-1',
|
||||
shop_name: '测试店铺',
|
||||
platform_order_id: '2614900602069169',
|
||||
} as any,
|
||||
}),
|
||||
[
|
||||
{
|
||||
stubOrderItem({
|
||||
id: 10,
|
||||
order_id: 1,
|
||||
sku_code: 'SKU-1',
|
||||
@@ -37,9 +126,9 @@ test('syncDeliveryTasksForOrder 多账号候选时不提前固定 cloudtentacles
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as any,
|
||||
} as JsonObject,
|
||||
}),
|
||||
],
|
||||
{
|
||||
listTasksByOrderId: async () => [],
|
||||
getFulfillmentProfileByKey: async () => ({
|
||||
@@ -48,44 +137,33 @@ test('syncDeliveryTasksForOrder 多账号候选时不提前固定 cloudtentacles
|
||||
profile_name: 'kuaishou-lewan 履约',
|
||||
executor_key: 'kuaishou_ct_assisted',
|
||||
}),
|
||||
createTask: async (input: any) => {
|
||||
createdInputs.push(input)
|
||||
return {
|
||||
id: 100,
|
||||
order_item_id: input.orderItemId,
|
||||
context_json: input.contextJson,
|
||||
task_status: input.taskStatus,
|
||||
executor_key: input.executorKey,
|
||||
} as any
|
||||
createTask: async (input) => {
|
||||
createdInputs.push(input as CreatedTaskInput)
|
||||
return stubCreatedTask(100, input as CreatedTaskInput)
|
||||
},
|
||||
nowIso: () => '2026-05-29T09:05:46.658Z',
|
||||
randomId: () => 'DT-test',
|
||||
},
|
||||
)
|
||||
|
||||
const context = JSON.parse(createdInputs[0].contextJson)
|
||||
assert.deepEqual(context.kuaishouCloudFulfillment.binding.cloudSourceKeys, [
|
||||
'account-a',
|
||||
'account-b',
|
||||
])
|
||||
assert.equal(context.kuaishouCloudFulfillment.binding.resolvedSourceKey, '')
|
||||
const context = JSON.parse(String(createdInputs[0]?.contextJson || '{}')) as JsonObject
|
||||
const fulfillment = context.kuaishouCloudFulfillment as JsonObject
|
||||
const binding = fulfillment.binding as JsonObject
|
||||
assert.deepEqual(binding.cloudSourceKeys, ['account-a', 'account-b'])
|
||||
assert.equal(binding.resolvedSourceKey, '')
|
||||
})
|
||||
|
||||
test('syncDeliveryTasksForOrder 单账号候选时保留固定 cloudtentacles 账号', async () => {
|
||||
const createdInputs: any[] = []
|
||||
const createdInputs: CreatedTaskInput[] = []
|
||||
|
||||
await syncDeliveryTasksForOrderWithDeps(
|
||||
{
|
||||
stubOrder({
|
||||
id: 2,
|
||||
pay_status: 'pending_payment',
|
||||
provider: 'open_91',
|
||||
platform: 'kuaishou',
|
||||
shop_id: 'shop-1',
|
||||
shop_name: '测试店铺',
|
||||
platform_order_id: '2614900602069170',
|
||||
} as any,
|
||||
}),
|
||||
[
|
||||
{
|
||||
stubOrderItem({
|
||||
id: 11,
|
||||
order_id: 2,
|
||||
sku_code: 'SKU-1',
|
||||
@@ -97,9 +175,9 @@ test('syncDeliveryTasksForOrder 单账号候选时保留固定 cloudtentacles
|
||||
cloudSkuId: 74,
|
||||
cloudSkuName: '荣耀勋章礼包(30个)',
|
||||
},
|
||||
},
|
||||
},
|
||||
] as any,
|
||||
} as JsonObject,
|
||||
}),
|
||||
],
|
||||
{
|
||||
listTasksByOrderId: async () => [],
|
||||
getFulfillmentProfileByKey: async () => ({
|
||||
@@ -108,22 +186,18 @@ test('syncDeliveryTasksForOrder 单账号候选时保留固定 cloudtentacles
|
||||
profile_name: 'kuaishou-lewan 履约',
|
||||
executor_key: 'kuaishou_ct_assisted',
|
||||
}),
|
||||
createTask: async (input: any) => {
|
||||
createdInputs.push(input)
|
||||
return {
|
||||
id: 101,
|
||||
order_item_id: input.orderItemId,
|
||||
context_json: input.contextJson,
|
||||
task_status: input.taskStatus,
|
||||
executor_key: input.executorKey,
|
||||
} as any
|
||||
createTask: async (input) => {
|
||||
createdInputs.push(input as CreatedTaskInput)
|
||||
return stubCreatedTask(101, input as CreatedTaskInput)
|
||||
},
|
||||
nowIso: () => '2026-05-29T09:05:46.658Z',
|
||||
randomId: () => 'DT-test',
|
||||
},
|
||||
)
|
||||
|
||||
const context = JSON.parse(createdInputs[0].contextJson)
|
||||
assert.deepEqual(context.kuaishouCloudFulfillment.binding.cloudSourceKeys, ['account-a'])
|
||||
assert.equal(context.kuaishouCloudFulfillment.binding.resolvedSourceKey, 'account-a')
|
||||
const context = JSON.parse(String(createdInputs[0]?.contextJson || '{}')) as JsonObject
|
||||
const fulfillment = context.kuaishouCloudFulfillment as JsonObject
|
||||
const binding = fulfillment.binding as JsonObject
|
||||
assert.deepEqual(binding.cloudSourceKeys, ['account-a'])
|
||||
assert.equal(binding.resolvedSourceKey, 'account-a')
|
||||
})
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { createHttpError, type HttpErrorLike } from '../../../utils/http.js'
|
||||
import { cloudtentaclesRequest } from './http-client.js'
|
||||
import { resolveCloudtentaclesConfig } from './helpers.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
export async function getCloudtentaclesAsset(payload: JsonObject = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 余额查询缺少 token', 'cloudtentacles_asset_missing_token')
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { createHash, constants, publicEncrypt } from 'node:crypto'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { resolveCloudtentaclesConfig } from './helpers.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function md5CloudtentaclesPassword(password: unknown) {
|
||||
return createHash('md5').update(String(password || ''), 'utf8').digest('hex')
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { getCloudtentaclesAsset, listCloudtentaclesSku, buyCloudtentaclesSku } from './catalog-service.js'
|
||||
import { getCloudtentaclesKnapsack } from './knapsack-service.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
appointCloudtentaclesVirtualNumber,
|
||||
fetchCloudtentaclesVirtualNumberCode,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
verifyCloudtentaclesLoginCode,
|
||||
} from './virtual-number-service.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type FlowStepOptions = {
|
||||
retries?: number
|
||||
retryDelayMs?: number
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { runtimeConfig } from '../../../config/runtime.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
import type { RuntimeConfig } from '../../../types/runtime-config.js'
|
||||
import {
|
||||
@@ -9,8 +10,6 @@ import {
|
||||
} from './defaults.js'
|
||||
|
||||
type CloudtentaclesRuntimeConfig = RuntimeConfig['platforms']['cloudtentacles']
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function resolveCloudtentaclesConfig(overrides: Partial<CloudtentaclesRuntimeConfig> = {}) {
|
||||
const baseConfig = runtimeConfig.platforms?.cloudtentacles || {
|
||||
baseUrl: '',
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import http from 'node:http'
|
||||
import https from 'node:https'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { logInfo, logWarn } from '../../../utils/logger.js'
|
||||
import { notifyCloudtentaclesAuthExpired } from '../../notification/domain-notifications.js'
|
||||
import { buildCloudtentaclesHeaders, buildCloudtentaclesUrl, resolveCloudtentaclesConfig } from './helpers.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type HeaderAdapter = {
|
||||
get(name: unknown): string | null
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { cloudtentaclesRequest } from './http-client.js'
|
||||
import { resolveCloudtentaclesConfig } from './helpers.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
export async function getCloudtentaclesKnapsack(payload: JsonObject = {}) {
|
||||
const token = String(payload.token || '').trim()
|
||||
|
||||
@@ -2,8 +2,7 @@ import { createHttpError } from '../../../utils/http.js'
|
||||
import { logInfo } from '../../../utils/logger.js'
|
||||
import { cloudtentaclesRequest } from './http-client.js'
|
||||
import { resolveCloudtentaclesConfig } from './helpers.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
export type CloudtentaclesDeliveryRecordQuery = JsonObject & {
|
||||
baseUrl?: string
|
||||
|
||||
@@ -3,8 +3,7 @@ import { logInfo } from '../../../utils/logger.js'
|
||||
import { encryptCloudtentaclesPayload, md5CloudtentaclesPassword } from './crypto-service.js'
|
||||
import { cloudtentaclesRequest } from './http-client.js'
|
||||
import { resolveCloudtentaclesConfig } from './helpers.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
export async function sendCloudtentaclesSmsCode(payload: JsonObject = {}) {
|
||||
const username = String(payload.username || '').trim()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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'
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
|
||||
const CLOUDTENTACLES_SESSION_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-session.json')
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type CloudtentaclesSessionState = ReturnType<typeof createDefaultCloudtentaclesSessionState>
|
||||
type CloudtentaclesSessionStatesFile = {
|
||||
sessions: Record<string, CloudtentaclesSessionState>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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'
|
||||
@@ -14,8 +15,6 @@ import {
|
||||
|
||||
const CLOUDTENTACLES_SOURCES_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-sources.json')
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function getCloudtentaclesSourcesFilePath() {
|
||||
return CLOUDTENTACLES_SOURCES_FILE_PATH
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import http from 'node:http'
|
||||
import https from 'node:https'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { cloudtentaclesRequest } from './http-client.js'
|
||||
@@ -8,7 +9,6 @@ import { resolveCloudtentaclesConfig } from './helpers.js'
|
||||
const BIND_URL_PROBE_MAX_BODY_BYTES = 64 * 1024
|
||||
const AMS_SIGNATURE_EXPIRED_CODE = '99998'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type HeaderAdapter = {
|
||||
get(name: unknown): string | null
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import crypto from 'node:crypto'
|
||||
import { asJsonObject, type JsonObject } from '../../../types/json.js'
|
||||
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { createRequestId, logExternalHttpPacket } from '../../../utils/logger.js'
|
||||
import { assertKuaishouFeifeiConfig } from './config.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export async function kuaishouFeifeiRequest(pathname: string, payload: JsonObject = {}) {
|
||||
const config = assertKuaishouFeifeiConfig()
|
||||
const body = JSON.stringify(payload)
|
||||
@@ -189,7 +188,7 @@ export function isKuaishouFeifeiSuccessResponse(json: JsonObject) {
|
||||
function parseJsonObject(text: string): JsonObject {
|
||||
try {
|
||||
const parsed = JSON.parse(text || '{}')
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
return asJsonObject(parsed)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crypto from 'node:crypto'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
||||
import { findKuaishouFeifeiTaskByOrder } from '../../../repositories/task-repo.js'
|
||||
@@ -9,7 +10,6 @@ import { assertKuaishouFeifeiConfig } from './config.js'
|
||||
import { signKuaishouFeifeiPayload } from './http-client.js'
|
||||
import { mapKuaishouFeifeiOrder } from './order-service.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type NotifyHeaders = Record<string, string | string[] | undefined>
|
||||
|
||||
export async function handleKuaishouFeifeiNotify(input: {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getKuaishouFeifeiConfig } from './config.js'
|
||||
import { kuaishouFeifeiRequest } from './http-client.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
export type KuaishouFeifeiProductListResult = ReturnType<typeof mapKuaishouFeifeiProductList>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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'
|
||||
@@ -16,8 +17,6 @@ const KUAISHOU_FEIFEI_CONFIG_FILE_PATH = path.join(
|
||||
'kuaishou-feifei-config.json',
|
||||
)
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export type KuaishouFeifeiSourceConfig = {
|
||||
enabled: boolean
|
||||
baseUrl: string
|
||||
|
||||
@@ -3,6 +3,7 @@ import { findKuaishouIndustryVoucherByCode } from '../../../repositories/kuaisho
|
||||
import { normalizeTimestampIso } from '../../../utils/time.js'
|
||||
import { logWarn } from '../../../utils/logger.js'
|
||||
import { TASK_STATUS } from '../../../domain/task-status.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
getKuaishouIndustryConfig,
|
||||
assertMatchingAppKey,
|
||||
@@ -17,8 +18,6 @@ import { consumeCallback } from './consume-callback-service.js'
|
||||
import { consumeKuaishouIndustryVoucher } from './voucher-service.js'
|
||||
import { attachKuaishouIndustryVoucherToTask } from './voucher-binding-service.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export async function handleConsumeCode(rawBody: JsonObject = {}) {
|
||||
const config = getKuaishouIndustryConfig()
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import crypto from 'node:crypto'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { logWarn } from '../../../utils/logger.js'
|
||||
import { assertKuaishouIndustryConfig } from './config.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export type SignMethod = 'MD5' | 'HMAC_SHA256'
|
||||
|
||||
export function buildKuaishouIndustrySignSource(params: JsonObject = {}, { signSecret } = assertKuaishouIndustryConfig()) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { updateTask } from '../../../repositories/task-repo.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
listKuaishouIndustryVouchersByOid,
|
||||
updateKuaishouIndustryVoucherByCode,
|
||||
@@ -18,8 +19,6 @@ import {
|
||||
import { destroyCallback } from './destroy-callback-service.js'
|
||||
import { attachKuaishouIndustryVoucherToTask } from './voucher-binding-service.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export async function handleDestroyCode(rawBody: JsonObject = {}) {
|
||||
const config = getKuaishouIndustryConfig()
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { getKuaishouIndustryConfig } from './config.js'
|
||||
import { signKuaishouIndustryPayload, type SignMethod } from './crypto.js'
|
||||
import { ensureKuaishouIndustryAccessToken } from './token-service.js'
|
||||
import { logIntegration } from '../../../utils/logger.js'
|
||||
|
||||
export type JsonObject = Record<string, any>
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
export type { JsonObject }
|
||||
|
||||
type KuaishouIndustryOpenApiCallInput = {
|
||||
apiMethod: string
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type JsonObject = Record<string, any>
|
||||
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
export type { JsonObject }
|
||||
export function pickDefinedBizParams(input: JsonObject): JsonObject {
|
||||
const output: JsonObject = {}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { assertKuaishouIndustryConfig } from './config.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
import { asJsonObject, type JsonObject } from '../../../types/json.js'
|
||||
|
||||
export function normalizeIndustryString(value: unknown) {
|
||||
return String(value || '').trim()
|
||||
@@ -239,7 +238,7 @@ function parseParamField(raw: JsonObject) {
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(normalizeIndustryString(raw.param || '{}'))
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
return asJsonObject(parsed)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
findKuaishouIndustryVoucherByCode,
|
||||
listKuaishouIndustryVouchersByOid,
|
||||
@@ -18,8 +19,6 @@ import {
|
||||
isKuaishouIndustryVoucherSendCallbackSuccess,
|
||||
} from './voucher-service.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export async function handleQueryCode(rawBody: JsonObject = {}) {
|
||||
const config = getKuaishouIndustryConfig()
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
export function buildIndustrySuccessResponse(data: unknown = null) {
|
||||
return {
|
||||
result: 1,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { findLatestOrderByPlatformOrderId } from '../../../repositories/order-repo.js'
|
||||
import { listTasksByOrderId } from '../../../repositories/task-repo.js'
|
||||
import { asJsonObject, type JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
findKuaishouIndustryVoucherByCode,
|
||||
listKuaishouIndustryVouchersByOid,
|
||||
@@ -33,8 +34,6 @@ import { bindKuaishouIndustryVouchersToOrderTasks } from './voucher-binding-serv
|
||||
import { parseAmountToFen } from '../../../utils/money.js'
|
||||
import type { KuaishouIndustryVoucherRow, OrderRow } from '../../../types/repository/rows.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
type SendCodeCallbackParams = {
|
||||
oid: string
|
||||
sendType: string
|
||||
@@ -677,7 +676,7 @@ function parseJsonObject(value: unknown): JsonObject {
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '{}'))
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
return asJsonObject(parsed)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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, runtimeConfig } from '../../../config/runtime.js'
|
||||
@@ -14,8 +15,6 @@ const LEGACY_DEFAULT_REDIRECT_URI = 'https://ks.khhao.com/oauth-callback'
|
||||
const DEFAULT_REDIRECT_URI = 'https://ks.khhao.com/admin/platform-shops?tab=kuaishouIndustry'
|
||||
const DEFAULT_SCOPES = 'merchant_item'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export type KuaishouIndustryShopConfig = {
|
||||
enabled: boolean
|
||||
sellerId: string
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { logInfo, logWarn } from '../../../utils/logger.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
findKuaishouIndustryShopConfig,
|
||||
getKuaishouIndustrySourceConfig,
|
||||
@@ -10,8 +11,6 @@ import {
|
||||
type KuaishouIndustrySourceConfig,
|
||||
} from './source-config-service.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
const ACCESS_TOKEN_REFRESH_MARGIN_MS = 30 * 60 * 1000
|
||||
const DEFAULT_ACCESS_TOKEN_TTL_MS = 47 * 60 * 60 * 1000
|
||||
const DEFAULT_REFRESH_TOKEN_TTL_MS = 179 * 24 * 60 * 60 * 1000
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { updateTask } from '../../../repositories/task-repo.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
listKuaishouIndustryVouchersByOid,
|
||||
updateKuaishouIndustryVoucherByCode,
|
||||
@@ -17,8 +18,6 @@ import type {
|
||||
TaskRow,
|
||||
} from '../../../types/repository/rows.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export async function bindKuaishouIndustryVouchersToOrderTasks(
|
||||
order: OrderRow | null | undefined,
|
||||
tasks: TaskRow[] = [],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
||||
import { asJsonObject, type JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
findKuaishouIndustryVoucherByCode,
|
||||
listKuaishouIndustryVouchersByTaskId,
|
||||
@@ -9,8 +10,6 @@ import { buildIndustryEticketItem } from './response.js'
|
||||
import { consumeCallback } from './consume-callback-service.js'
|
||||
import type { KuaishouIndustryVoucherRow, TaskRow } from '../../../types/repository/rows.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export const KUAISHOU_INDUSTRY_DEFAULT_VALID_DAYS = 3
|
||||
export const KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS = {
|
||||
PENDING: 'pending',
|
||||
@@ -358,7 +357,7 @@ function parseJsonObject(value: unknown): JsonObject {
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '{}'))
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
return asJsonObject(parsed)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { listOrderItemsByOrderId, replaceOrderItems } from '../../../repositorie
|
||||
import { listTasksByOrderId } from '../../../repositories/task-repo.js'
|
||||
import { upsertOrderFromSource } from '../../order/order-service.js'
|
||||
import { bindKuaishouIndustryVouchersToOrderTasks } from '../kuaishou-industry/voucher-binding-service.js'
|
||||
import { asJsonObject, type JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
OPEN_91_PLATFORM,
|
||||
OPEN_91_PROVIDER,
|
||||
@@ -18,8 +19,6 @@ import type { OrderItemRow, OrderRow } from '../../../types/repository/rows.js'
|
||||
export const OPEN_91_PENDING_CONFIG_STATUS = 'pending_config'
|
||||
export const OPEN_91_MANUAL_FAILED_STATUS = 'manual_failed'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function buildOpen91SourceEvent(payload: JsonObject = {}, config: JsonObject = {}) {
|
||||
const normalized = normalizeOpen91CreatePayload(payload)
|
||||
const productInfo = parseOpen91ProductNo(normalized.productNo)
|
||||
@@ -397,7 +396,7 @@ function parseJsonObject(value: unknown): JsonObject {
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '{}'))
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
return asJsonObject(parsed)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getCloudtentaclesAsset } from '../platforms/cloudtentacles/catalog-service.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
normalizeCloudtentaclesDeviceId,
|
||||
normalizeCloudtentaclesDeviceType,
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
notifyCloudtentaclesAuthExpired,
|
||||
} from '../notification/domain-notifications.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type CloudtentaclesHealthAccountResult = {
|
||||
sourceKey: string
|
||||
label: string
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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'
|
||||
@@ -10,8 +11,6 @@ import {
|
||||
const SCHEDULED_JOBS_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'scheduled-jobs.json')
|
||||
const CLOUDTENTACLES_HEALTH_JOB_ID = 'cloudtentacles-health'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function getScheduledJobsFilePath() {
|
||||
return SCHEDULED_JOBS_FILE_PATH
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { logError, logInfo, logWarn } from '../../utils/logger.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
getCloudtentaclesHealthJob,
|
||||
getScheduledJobsConfig,
|
||||
} from './config-service.js'
|
||||
import { runCloudtentaclesHealthJob } from './cloudtentacles-health-job.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
const timers = new Map<string, NodeJS.Timeout>()
|
||||
const jobStates = new Map<string, JsonObject>()
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { JsonObject } from '../json.js'
|
||||
|
||||
/**
|
||||
* 后台任务详情 API 返回形状(与前端 AdminTaskDetail 对齐的后端契约)。
|
||||
* 深层履约字段仍用 JsonObject,避免一次改完所有 mapper。
|
||||
*/
|
||||
export type AdminClaimIdentitySummary = {
|
||||
expectedUid: string
|
||||
submittedAt: string | null
|
||||
source: string
|
||||
ready: boolean
|
||||
boundUid: string
|
||||
boundRoleName: string
|
||||
uidMatched: boolean | null
|
||||
compatibilityMode: 'uid' | 'legacy_no_uid'
|
||||
note: string
|
||||
}
|
||||
|
||||
export type AdminTaskDetailView = {
|
||||
task: JsonObject
|
||||
order: JsonObject | null
|
||||
orderItem: JsonObject | null
|
||||
claimToken: JsonObject | null
|
||||
artifacts: JsonObject
|
||||
screenshotUrl: string
|
||||
review: JsonObject
|
||||
redeemResolution: JsonObject | null
|
||||
claimIdentity: AdminClaimIdentitySummary
|
||||
kuaishouCloudFulfillment: JsonObject | null
|
||||
kuaishouIndustryVoucher: JsonObject | null
|
||||
manualDispatch: JsonObject | null
|
||||
events: JsonObject[]
|
||||
operations: JsonObject
|
||||
}
|
||||
@@ -1 +1,41 @@
|
||||
export type JsonRecord = Record<string, any>
|
||||
/**
|
||||
* 项目统一的 JSON 结构类型(中心定义,避免各处复制 Record<string, any>)。
|
||||
*
|
||||
* 渐进策略:
|
||||
* - JsonObject:当前仍用 any 值以兼容深层 context 访问(.a.b.c)
|
||||
* - 新域模型请用 TaskContext / 明确 interface,少用 JsonObject
|
||||
* - 后续可把热点路径迁到 StrictJsonObject(unknown 值)
|
||||
*/
|
||||
|
||||
/** 严格 JSON(推荐新代码) */
|
||||
export type JsonPrimitive = string | number | boolean | null
|
||||
export type StrictJson =
|
||||
| JsonPrimitive
|
||||
| StrictJson[]
|
||||
| { readonly [key: string]: StrictJson }
|
||||
export type StrictJsonObject = { [key: string]: StrictJson }
|
||||
|
||||
/**
|
||||
* 宽松 JSON 对象袋(兼容既有履约 context / 平台响应)。
|
||||
* 值使用 any 仅为兼容深层属性链;新增代码优先 TaskContext 或 StrictJsonObject。
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type JsonObject = Record<string, any>
|
||||
|
||||
/** @deprecated 使用 JsonObject */
|
||||
export type JsonRecord = JsonObject
|
||||
|
||||
export function isPlainObject(value: unknown): value is JsonObject {
|
||||
return Object.prototype.toString.call(value) === '[object Object]'
|
||||
}
|
||||
|
||||
export function asJsonObject(value: unknown): JsonObject {
|
||||
return isPlainObject(value) ? value : {}
|
||||
}
|
||||
|
||||
export function getJsonProp(value: unknown, key: string): unknown {
|
||||
if (!isPlainObject(value)) {
|
||||
return undefined
|
||||
}
|
||||
return value[key]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { JsonObject } from './json.js'
|
||||
|
||||
/**
|
||||
* 履约任务 context_json 的已知字段。
|
||||
* 未建模扩展仍可通过索引访问(JsonObject)。
|
||||
*/
|
||||
export type ClaimIdentityContext = {
|
||||
expectedUid?: string
|
||||
submittedAt?: string | null
|
||||
source?: string
|
||||
uid?: string
|
||||
}
|
||||
|
||||
export type TaskContext = JsonObject & {
|
||||
claimIdentity?: ClaimIdentityContext | JsonObject
|
||||
kuaishouCloudFulfillment?: JsonObject
|
||||
kuaishouFeifei?: JsonObject
|
||||
kuaishouIndustryVoucher?: JsonObject
|
||||
redeemResolution?: JsonObject
|
||||
manualDispatch?: JsonObject
|
||||
mock?: JsonObject
|
||||
profileKey?: string
|
||||
profileName?: string
|
||||
skuCode?: string
|
||||
skuName?: string
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import util from 'node:util'
|
||||
import { PROJECT_ROOT, runtimeConfig } from '../config/runtime.js'
|
||||
import type { HttpErrorLike } from './http.js'
|
||||
import { maskSecret } from './masking.js'
|
||||
import { asJsonObject } from '../types/json.js'
|
||||
|
||||
type LogLevel = 'debug' | 'info' | 'warn' | 'error'
|
||||
type LogChannel = 'app' | 'integration'
|
||||
@@ -362,7 +363,7 @@ function isSensitiveLogKey(key: unknown): boolean {
|
||||
function normalizeExternalHttpPacketDetail(
|
||||
detail: ExternalHttpPacketDetail,
|
||||
): ExternalHttpPacketDetail {
|
||||
const source = detail && typeof detail === 'object' && !Array.isArray(detail) ? detail : {}
|
||||
const source = asJsonObject(detail)
|
||||
const normalized: ExternalHttpPacketDetail = {}
|
||||
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
|
||||
@@ -1,26 +1,34 @@
|
||||
export type JsonRecord = Record<string, any>
|
||||
import type { JsonObject, JsonRecord } from '../types/json.js'
|
||||
import { asJsonObject, isPlainObject } from '../types/json.js'
|
||||
import type { TaskContext } from '../types/task-context.js'
|
||||
|
||||
export function parseJsonObject(value: unknown): JsonRecord {
|
||||
export type { JsonRecord, JsonObject }
|
||||
|
||||
export function parseJsonObject(value: unknown): JsonObject {
|
||||
if (!value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
return value as JsonRecord
|
||||
if (isPlainObject(value)) {
|
||||
return value
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '{}'))
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
const parsed = JSON.parse(String(value || '{}')) as unknown
|
||||
return asJsonObject(parsed)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTaskContext(task: { context_json?: unknown } | null | undefined): JsonRecord {
|
||||
return parseJsonObject(task?.context_json)
|
||||
export function parseTaskContext(
|
||||
task: { context_json?: unknown } | null | undefined,
|
||||
): TaskContext {
|
||||
return parseJsonObject(task?.context_json) as TaskContext
|
||||
}
|
||||
|
||||
export function parseTaskState(task: { state_json?: unknown } | null | undefined): JsonRecord {
|
||||
export function parseTaskState(
|
||||
task: { state_json?: unknown } | null | undefined,
|
||||
): JsonObject {
|
||||
return parseJsonObject(task?.state_json)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"allowJs": false,
|
||||
"checkJs": false,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
@@ -14,13 +14,11 @@
|
||||
"lib": ["ES2022"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"data",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user