统一前后端代码格式化配置
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
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'
|
||||
import {
|
||||
normalizeDateQuery,
|
||||
normalizePage,
|
||||
normalizePageSize,
|
||||
safeParseJson,
|
||||
} from './admin-query-utils.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
export async function writeAdminAuditLog(session: JsonObject | null | undefined, payload: JsonObject = {}) {
|
||||
export async function writeAdminAuditLog(
|
||||
session: JsonObject | null | undefined,
|
||||
payload: JsonObject = {},
|
||||
) {
|
||||
if (!session?.userId) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -32,10 +32,14 @@ type AdminUserStatus = 'active' | 'disabled'
|
||||
export async function ensureAdminUsersBootstrapped(): Promise<void> {
|
||||
ensureAdminAuthConfigured()
|
||||
|
||||
const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers) ? runtimeConfig.admin.defaultUsers : []
|
||||
const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers)
|
||||
? runtimeConfig.admin.defaultUsers
|
||||
: []
|
||||
|
||||
for (const configuredUser of configuredUsers) {
|
||||
const username = String(configuredUser?.username || '').trim().toLowerCase()
|
||||
const username = String(configuredUser?.username || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
const password = String(configuredUser?.password || '').trim()
|
||||
const role = normalizeAdminRole(configuredUser?.role)
|
||||
|
||||
@@ -70,7 +74,9 @@ export async function loginAdmin(
|
||||
): Promise<JsonObject> {
|
||||
ensureAdminAuthConfigured()
|
||||
|
||||
const normalizedUsername = String(username || '').trim().toLowerCase()
|
||||
const normalizedUsername = String(username || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
const normalizedPassword = String(password || '').trim()
|
||||
|
||||
if (!normalizedUsername || !normalizedPassword) {
|
||||
@@ -87,7 +93,11 @@ export async function loginAdmin(
|
||||
}
|
||||
|
||||
const user = await getAdminUserByUsername(normalizedUsername)
|
||||
if (!user || user.status !== 'active' || !verifyAdminPassword(normalizedPassword, user.password_hash)) {
|
||||
if (
|
||||
!user ||
|
||||
user.status !== 'active' ||
|
||||
!verifyAdminPassword(normalizedPassword, user.password_hash)
|
||||
) {
|
||||
await recordAdminLoginLog({
|
||||
userId: user ? Number(user.id) : null,
|
||||
username: normalizedUsername,
|
||||
@@ -201,7 +211,10 @@ export async function getAdminSessionSummary(token: unknown): Promise<JsonObject
|
||||
}
|
||||
}
|
||||
|
||||
export function requireAdminRole(session: { role?: string } | null | undefined, allowedRoles: string[]): void {
|
||||
export function requireAdminRole(
|
||||
session: { role?: string } | null | undefined,
|
||||
allowedRoles: string[],
|
||||
): void {
|
||||
if (session && allowedRoles.includes(session.role || '')) {
|
||||
return
|
||||
}
|
||||
@@ -342,7 +355,10 @@ export async function updateManagedAdminUserStatus(
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetManagedAdminUserPassword(userId: number | string, payload: JsonObject = {}): Promise<JsonObject> {
|
||||
export async function resetManagedAdminUserPassword(
|
||||
userId: number | string,
|
||||
payload: JsonObject = {},
|
||||
): Promise<JsonObject> {
|
||||
const user = await getRequiredAdminUser(userId)
|
||||
const password = normalizePassword(payload.password)
|
||||
|
||||
@@ -373,7 +389,9 @@ export async function resetManagedAdminUserPassword(userId: number | string, pay
|
||||
|
||||
export function ensureAdminAuthConfigured(): void {
|
||||
const sessionSecret = String(runtimeConfig.admin?.sessionSecret || '').trim()
|
||||
const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers) ? runtimeConfig.admin.defaultUsers : []
|
||||
const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers)
|
||||
? runtimeConfig.admin.defaultUsers
|
||||
: []
|
||||
|
||||
if (sessionSecret && configuredUsers.length > 0) {
|
||||
return
|
||||
@@ -435,7 +453,9 @@ function signPayload(encodedPayload: string): string {
|
||||
}
|
||||
|
||||
export function normalizeAdminRole(role: unknown): AdminRole {
|
||||
const normalized = String(role || '').trim().toLowerCase()
|
||||
const normalized = String(role || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
|
||||
if (normalized === 'admin') {
|
||||
return 'admin'
|
||||
@@ -449,7 +469,11 @@ export function normalizeAdminRole(role: unknown): AdminRole {
|
||||
}
|
||||
|
||||
export function normalizeAdminUserStatus(status: unknown): AdminUserStatus {
|
||||
return String(status || '').trim().toLowerCase() === 'disabled' ? 'disabled' : 'active'
|
||||
return String(status || '')
|
||||
.trim()
|
||||
.toLowerCase() === 'disabled'
|
||||
? 'disabled'
|
||||
: 'active'
|
||||
}
|
||||
|
||||
function safeCompare(input: unknown, expected: unknown): boolean {
|
||||
@@ -464,17 +488,23 @@ function safeCompare(input: unknown, expected: unknown): boolean {
|
||||
}
|
||||
|
||||
function normalizeRoleQuery(role: unknown): string {
|
||||
const normalized = String(role || '').trim().toLowerCase()
|
||||
const normalized = String(role || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
return ['admin', 'operator', 'support'].includes(normalized) ? normalized : ''
|
||||
}
|
||||
|
||||
function normalizeStatusQuery(status: unknown): string {
|
||||
const normalized = String(status || '').trim().toLowerCase()
|
||||
const normalized = String(status || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
return ['active', 'disabled'].includes(normalized) ? normalized : ''
|
||||
}
|
||||
|
||||
function normalizeUsername(username: unknown): string {
|
||||
return String(username || '').trim().toLowerCase()
|
||||
return String(username || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
function normalizePassword(password: unknown): string {
|
||||
@@ -523,7 +553,7 @@ async function getRequiredAdminUser(userId: number | string): Promise<AdminUserR
|
||||
|
||||
async function ensureAdminUserChangeAllowed(
|
||||
user: AdminUserRow,
|
||||
options: { nextRole?: AdminRole, nextStatus?: AdminUserStatus } = {},
|
||||
options: { nextRole?: AdminRole; nextStatus?: AdminUserStatus } = {},
|
||||
session: AdminSession,
|
||||
): Promise<void> {
|
||||
const nextRole = options.nextRole || user.role
|
||||
@@ -536,7 +566,11 @@ async function ensureAdminUserChangeAllowed(
|
||||
})
|
||||
}
|
||||
|
||||
if (user.role === 'admin' && (nextRole !== 'admin' || nextStatus !== 'active') && await countActiveAdminUsers() <= 1) {
|
||||
if (
|
||||
user.role === 'admin' &&
|
||||
(nextRole !== 'admin' || nextStatus !== 'active') &&
|
||||
(await countActiveAdminUsers()) <= 1
|
||||
) {
|
||||
throw createHttpError('至少保留一个启用中的管理员账号', {
|
||||
statusCode: 409,
|
||||
errorCode: 'admin_user_last_admin_not_allowed',
|
||||
@@ -555,11 +589,13 @@ function mapAdminUser(user: AdminUserRow): JsonObject {
|
||||
}
|
||||
}
|
||||
|
||||
function pickLoginMeta(meta: {
|
||||
ip?: string
|
||||
userAgent?: string
|
||||
location?: string
|
||||
} = {}) {
|
||||
function pickLoginMeta(
|
||||
meta: {
|
||||
ip?: string
|
||||
userAgent?: string
|
||||
location?: string
|
||||
} = {},
|
||||
) {
|
||||
const result: {
|
||||
ip?: string
|
||||
userAgent?: string
|
||||
|
||||
@@ -11,15 +11,8 @@ export async function getAdminDashboardSummary() {
|
||||
TASK_STATUS.PENDING_BINDING_PREPARE,
|
||||
TASK_STATUS.WAITING_BINDING,
|
||||
]
|
||||
const claimingStatuses = [
|
||||
TASK_STATUS.CLAIMED,
|
||||
TASK_STATUS.ROLE_CONFIRMED,
|
||||
TASK_STATUS.REDEEMING,
|
||||
]
|
||||
const abnormalStatuses = [
|
||||
TASK_STATUS.RETRY_PENDING,
|
||||
TASK_STATUS.MANUAL_REVIEW,
|
||||
]
|
||||
const claimingStatuses = [TASK_STATUS.CLAIMED, TASK_STATUS.ROLE_CONFIRMED, TASK_STATUS.REDEEMING]
|
||||
const abnormalStatuses = [TASK_STATUS.RETRY_PENDING, TASK_STATUS.MANUAL_REVIEW]
|
||||
const result = await query(
|
||||
`
|
||||
SELECT
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import type { Request } from 'express'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
import {
|
||||
createAdminLoginLog,
|
||||
listAdminLoginLogs,
|
||||
} from '../../repositories/admin-login-log-repo.js'
|
||||
import { createAdminLoginLog, listAdminLoginLogs } from '../../repositories/admin-login-log-repo.js'
|
||||
import { logWarn } from '../../utils/logger.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import {
|
||||
normalizeDateQuery,
|
||||
normalizePage,
|
||||
normalizePageSize,
|
||||
} from './admin-query-utils.js'
|
||||
import { normalizeDateQuery, normalizePage, normalizePageSize } from './admin-query-utils.js'
|
||||
|
||||
type RecordAdminLoginInput = {
|
||||
userId?: number | null
|
||||
@@ -108,16 +101,8 @@ export function resolveClientLocation(req: Request) {
|
||||
'cloudfront-viewer-country',
|
||||
'x-country-code',
|
||||
])
|
||||
const city = firstHeader(req, [
|
||||
'cf-ipcity',
|
||||
'x-vercel-ip-city',
|
||||
'x-city',
|
||||
])
|
||||
const region = firstHeader(req, [
|
||||
'cf-region',
|
||||
'x-vercel-ip-country-region',
|
||||
'x-region',
|
||||
])
|
||||
const city = firstHeader(req, ['cf-ipcity', 'x-vercel-ip-city', 'x-city'])
|
||||
const region = firstHeader(req, ['cf-region', 'x-vercel-ip-country-region', 'x-region'])
|
||||
|
||||
return [country, region, city].filter(Boolean).join(' · ')
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ export async function mapAdminOrderListItem(item: OrderListRow): Promise<AdminOr
|
||||
createdAt: item.created_at,
|
||||
updatedAt: item.updated_at,
|
||||
itemCount: orderItems.length,
|
||||
totalQuantity: orderItems.reduce((sum, orderItem) => sum + Math.max(1, Number(orderItem.quantity || 1)), 0),
|
||||
totalQuantity: orderItems.reduce(
|
||||
(sum, orderItem) => sum + Math.max(1, Number(orderItem.quantity || 1)),
|
||||
0,
|
||||
),
|
||||
itemSummary,
|
||||
taskCount: Number(item.task_count || tasks.length || 0),
|
||||
resourceStatus: fulfillmentProgress.resourceStatus,
|
||||
@@ -52,8 +55,9 @@ export function summarizeOrderItems(items: OrderItemRow[] | null | undefined): s
|
||||
}
|
||||
|
||||
const [firstItem] = normalizedItems
|
||||
const firstLabel = resolveOrderItemTitle(firstItem)
|
||||
|| String(firstItem?.sku_name || firstItem?.sku_code || '').trim()
|
||||
const firstLabel =
|
||||
resolveOrderItemTitle(firstItem) ||
|
||||
String(firstItem?.sku_name || firstItem?.sku_code || '').trim()
|
||||
|
||||
if (normalizedItems.length === 1) {
|
||||
return firstLabel
|
||||
|
||||
@@ -66,10 +66,7 @@ import type {
|
||||
AdminTaskListQueryInput,
|
||||
AdminViewerSessionInput,
|
||||
} from '../../types/admin/read-inputs.js'
|
||||
import type {
|
||||
KuaishouIndustryVoucherRow,
|
||||
TaskRow,
|
||||
} from '../../types/repository/rows.js'
|
||||
import type { KuaishouIndustryVoucherRow, TaskRow } from '../../types/repository/rows.js'
|
||||
|
||||
export async function getAdminOrders(
|
||||
query: AdminOrderListQueryInput = {},
|
||||
@@ -229,15 +226,16 @@ export async function getAdminTaskDetail(
|
||||
}
|
||||
|
||||
const primaryClaimTokenId = getTaskPrimaryClaimTokenId(task)
|
||||
const [order, claimToken, taskEvents, taskKuaishouIndustryVouchers, oidKuaishouIndustryVouchers] = await Promise.all([
|
||||
getOrderById(task.order_id),
|
||||
primaryClaimTokenId ? getClaimTokenById(primaryClaimTokenId) : Promise.resolve(null),
|
||||
listTaskEventsByTaskId(task.id),
|
||||
listKuaishouIndustryVouchersByTaskId(task.id),
|
||||
task.platform_order_id
|
||||
? listKuaishouIndustryVouchersByOid(task.platform_order_id)
|
||||
: Promise.resolve([]),
|
||||
])
|
||||
const [order, claimToken, taskEvents, taskKuaishouIndustryVouchers, oidKuaishouIndustryVouchers] =
|
||||
await Promise.all([
|
||||
getOrderById(task.order_id),
|
||||
primaryClaimTokenId ? getClaimTokenById(primaryClaimTokenId) : Promise.resolve(null),
|
||||
listTaskEventsByTaskId(task.id),
|
||||
listKuaishouIndustryVouchersByTaskId(task.id),
|
||||
task.platform_order_id
|
||||
? listKuaishouIndustryVouchersByOid(task.platform_order_id)
|
||||
: Promise.resolve([]),
|
||||
])
|
||||
const orderItems = order ? await listOrderItemsByOrderId(order.id) : []
|
||||
const orderItem = orderItems.find((item) => item.id === task.order_item_id) || null
|
||||
const taskContext = parseTaskContext(task)
|
||||
@@ -252,10 +250,7 @@ export async function getAdminTaskDetail(
|
||||
cloudSourceLabelMap,
|
||||
}) || mapKuaishouCloudTaskStateProjection(task, { cloudSourceLabelMap })
|
||||
const claimIdentity = buildClaimIdentityAdminSummary(taskContext, {
|
||||
flowLike:
|
||||
taskContext.kuaishouCloudFulfillment ||
|
||||
taskContext.kuaishouFeifei ||
|
||||
null,
|
||||
flowLike: taskContext.kuaishouCloudFulfillment || taskContext.kuaishouFeifei || null,
|
||||
taskRoleId: task.role_id,
|
||||
taskRoleName: task.role_name,
|
||||
})
|
||||
@@ -265,17 +260,20 @@ export async function getAdminTaskDetail(
|
||||
taskKuaishouIndustryVouchers,
|
||||
oidKuaishouIndustryVouchers,
|
||||
)
|
||||
const kuaishouIndustryVoucher =
|
||||
firstKuaishouIndustryVoucher
|
||||
? mapAdminKuaishouIndustryVoucher(firstKuaishouIndustryVoucher)
|
||||
: mapKuaishouIndustryVoucherContext(taskContext.kuaishouIndustryVoucher)
|
||||
const kuaishouIndustryVoucher = firstKuaishouIndustryVoucher
|
||||
? mapAdminKuaishouIndustryVoucher(firstKuaishouIndustryVoucher)
|
||||
: mapKuaishouIndustryVoucherContext(taskContext.kuaishouIndustryVoucher)
|
||||
const hasKuaishouIndustryVoucher = Boolean(
|
||||
kuaishouIndustryVoucher && String(kuaishouIndustryVoucher.voucherCode || '').trim(),
|
||||
)
|
||||
const kuaishouIndustryVoucherStatus =
|
||||
String(kuaishouIndustryVoucher?.status || '').trim().toUpperCase()
|
||||
const kuaishouIndustryVoucherSendCallbackStatus =
|
||||
String(kuaishouIndustryVoucher?.sendCallbackStatus || '').trim().toLowerCase()
|
||||
const kuaishouIndustryVoucherStatus = String(kuaishouIndustryVoucher?.status || '')
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
const kuaishouIndustryVoucherSendCallbackStatus = String(
|
||||
kuaishouIndustryVoucher?.sendCallbackStatus || '',
|
||||
)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
|
||||
return {
|
||||
task: mapAdminTaskListItem(
|
||||
@@ -435,7 +433,7 @@ function resolveKuaishouIndustryVoucherForTask(
|
||||
taskContext.kuaishouIndustryVoucher &&
|
||||
typeof taskContext.kuaishouIndustryVoucher === 'object' &&
|
||||
!Array.isArray(taskContext.kuaishouIndustryVoucher)
|
||||
? taskContext.kuaishouIndustryVoucher as JsonRecord
|
||||
? (taskContext.kuaishouIndustryVoucher as JsonRecord)
|
||||
: {}
|
||||
const voucherCode = String(voucherContext.voucherCode || voucherContext.eticketId || '').trim()
|
||||
if (voucherCode) {
|
||||
@@ -457,7 +455,7 @@ function resolveKuaishouIndustryVoucherForTask(
|
||||
}
|
||||
}
|
||||
|
||||
return oidVouchers.length === 1 ? (oidVouchers[0] || null) : null
|
||||
return oidVouchers.length === 1 ? oidVouchers[0] || null : null
|
||||
}
|
||||
|
||||
function buildCloudSourceLabelMap() {
|
||||
|
||||
@@ -15,7 +15,10 @@ test('resolveAdminTaskScreenshotUrl lets support view final redeemed screenshot'
|
||||
runtime_session_id: '',
|
||||
}
|
||||
|
||||
const screenshotUrl = await resolveAdminTaskScreenshotUrl(task, createAdminViewerContext({ role: 'support' }))
|
||||
const screenshotUrl = await resolveAdminTaskScreenshotUrl(
|
||||
task,
|
||||
createAdminViewerContext({ role: 'support' }),
|
||||
)
|
||||
|
||||
assert.equal(screenshotUrl, '/api/v1/admin/tasks/12/screenshot')
|
||||
})
|
||||
@@ -27,7 +30,10 @@ test('resolveAdminTaskScreenshotUrl falls back to review screenshot when runtime
|
||||
runtime_session_id: 'runtime-session-13',
|
||||
}
|
||||
|
||||
const screenshotUrl = await resolveAdminTaskScreenshotUrl(task, createAdminViewerContext({ role: 'support' }))
|
||||
const screenshotUrl = await resolveAdminTaskScreenshotUrl(
|
||||
task,
|
||||
createAdminViewerContext({ role: 'support' }),
|
||||
)
|
||||
|
||||
assert.equal(screenshotUrl, '/api/v1/admin/tasks/13/screenshot')
|
||||
})
|
||||
|
||||
@@ -1,6 +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 { asJsonObject, type JsonObject, JsonRecord } from '../../types/json.js'
|
||||
import {
|
||||
parseTaskContext as parseTaskContextValue,
|
||||
parseTaskState as parseTaskStateValue,
|
||||
@@ -79,8 +79,7 @@ export function mapKuaishouCloudFulfillmentContext(
|
||||
const role = asJsonObject(record.role)
|
||||
const purchase = asJsonObject(record.purchase)
|
||||
const dispatch = asJsonObject(record.dispatch)
|
||||
const returnNumber =
|
||||
asJsonObject(record.returnNumber)
|
||||
const returnNumber = asJsonObject(record.returnNumber)
|
||||
const consume = asJsonObject(record.consume)
|
||||
const cloudSourceKeys = Array.isArray(binding.cloudSourceKeys)
|
||||
? binding.cloudSourceKeys.map((value: unknown) => String(value || '').trim()).filter(Boolean)
|
||||
@@ -373,7 +372,8 @@ export function createAdminViewerContext(
|
||||
canViewSensitiveTaskData: role === 'admin' || role === 'operator',
|
||||
canManageTaskLifecycle: role === 'admin' || role === 'operator',
|
||||
canOperateAssistedTask: role === 'admin' || role === 'operator' || role === 'support',
|
||||
canOperateKuaishouIndustryVoucher: role === 'admin' || role === 'operator' || role === 'support',
|
||||
canOperateKuaishouIndustryVoucher:
|
||||
role === 'admin' || role === 'operator' || role === 'support',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +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 { asJsonObject, type JsonObject, JsonRecord } from '../../types/json.js'
|
||||
import {
|
||||
TASK_STATUS,
|
||||
isTaskFulfillmentCompletedStatus,
|
||||
@@ -17,10 +17,7 @@ import type {
|
||||
AdminTaskListItem,
|
||||
} from '../../types/admin/read-models.js'
|
||||
import type { AdminTaskActionPayload } from '../../types/admin/write-models.js'
|
||||
import type {
|
||||
TaskEventRow,
|
||||
TaskRow,
|
||||
} from '../../types/repository/rows.js'
|
||||
import type { TaskEventRow, TaskRow } from '../../types/repository/rows.js'
|
||||
import type { AdminViewerContext } from './admin-read-shared-helpers.js'
|
||||
|
||||
type TaskFulfillmentState = {
|
||||
@@ -28,9 +25,7 @@ type TaskFulfillmentState = {
|
||||
customerStatus: string
|
||||
}
|
||||
|
||||
export function mapAdminTaskSummary(
|
||||
task: TaskRow,
|
||||
): JsonRecord {
|
||||
export function mapAdminTaskSummary(task: TaskRow): JsonRecord {
|
||||
const fulfillment = buildTaskFulfillmentState(task)
|
||||
|
||||
return {
|
||||
@@ -111,8 +106,10 @@ export function mapAdminTaskListItem(
|
||||
lastError: task.last_error,
|
||||
createdAt: task.created_at,
|
||||
updatedAt: task.updated_at,
|
||||
claimToken: viewerContext.canViewSensitiveTaskData ? (task.primary_claim_token || task.claim_token || '') : '',
|
||||
screenshotPath: viewerContext.role === 'support' ? '' : (task.screenshot_path || ''),
|
||||
claimToken: viewerContext.canViewSensitiveTaskData
|
||||
? task.primary_claim_token || task.claim_token || ''
|
||||
: '',
|
||||
screenshotPath: viewerContext.role === 'support' ? '' : task.screenshot_path || '',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +131,9 @@ export function buildOrderFulfillmentProgress(
|
||||
const totalTaskCount = normalizedTasks.length
|
||||
const taskFulfillments = normalizedTasks.map((task) => buildTaskFulfillmentState(task))
|
||||
const preparedTaskCount = normalizedTasks.filter((task) => isTaskResourcePrepared(task)).length
|
||||
const completedTaskCount = normalizedTasks.filter((task) => isTaskFulfillmentCompleted(task)).length
|
||||
const completedTaskCount = normalizedTasks.filter((task) =>
|
||||
isTaskFulfillmentCompleted(task),
|
||||
).length
|
||||
|
||||
let resourceStatus = 'pending_prepare'
|
||||
let customerStatus = 'not_started'
|
||||
@@ -152,7 +151,11 @@ export function buildOrderFulfillmentProgress(
|
||||
if (normalizedTasks.every((task) => isTaskFulfillmentCompleted(task))) {
|
||||
resourceStatus = 'resource_ready'
|
||||
customerStatus = 'customer_completed'
|
||||
} else if (taskFulfillments.some((item) => ['customer_processing', 'customer_confirmed', 'link_opened'].includes(item.customerStatus))) {
|
||||
} else if (
|
||||
taskFulfillments.some((item) =>
|
||||
['customer_processing', 'customer_confirmed', 'link_opened'].includes(item.customerStatus),
|
||||
)
|
||||
) {
|
||||
resourceStatus = preparedTaskCount > 0 ? 'resource_ready' : 'pending_prepare'
|
||||
customerStatus = 'customer_processing'
|
||||
} else if (taskFulfillments.some((item) => item.customerStatus === 'waiting_customer')) {
|
||||
@@ -163,7 +166,11 @@ export function buildOrderFulfillmentProgress(
|
||||
customerStatus = 'customer_exception'
|
||||
} else if (preparedTaskCount > 0) {
|
||||
resourceStatus = 'resource_ready'
|
||||
} else if (taskFulfillments.some((item) => ['manual_review', 'retry_pending'].includes(item.resourceStatus))) {
|
||||
} else if (
|
||||
taskFulfillments.some((item) =>
|
||||
['manual_review', 'retry_pending'].includes(item.resourceStatus),
|
||||
)
|
||||
) {
|
||||
resourceStatus = 'resource_exception'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { getAffiliateDashConfig } from '../../platforms/affiliate-dash/config.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
getAffiliateDashWallet,
|
||||
} from '../../platforms/affiliate-dash/order-service.js'
|
||||
import { getAffiliateDashWallet } from '../../platforms/affiliate-dash/order-service.js'
|
||||
import { listAffiliateDashProducts } from '../../platforms/affiliate-dash/product-service.js'
|
||||
import {
|
||||
getAffiliateDashSourceConfig,
|
||||
@@ -35,9 +33,7 @@ export async function updateAdminAffiliateDashConfig(payload: JsonObject = {}) {
|
||||
export function matchAdminAffiliateDashSku(payload: JsonObject = {}) {
|
||||
const productNo = String(payload.productNo || payload.product_no || '').trim()
|
||||
const source = getAdminEditableAffiliateDashConfig()
|
||||
const sku = productNo
|
||||
? String(source.skuMapping[productNo] || '').trim()
|
||||
: ''
|
||||
const sku = productNo ? String(source.skuMapping[productNo] || '').trim() : ''
|
||||
|
||||
return {
|
||||
productNo,
|
||||
|
||||
@@ -52,7 +52,13 @@ test('resolveCloudtentaclesAdminContext merges payload source and persisted sess
|
||||
test('hasCloudtentaclesCredentialContextChanged detects normalized credential changes', () => {
|
||||
assert.equal(
|
||||
hasCloudtentaclesCredentialContextChanged(
|
||||
{ baseUrl: ' https://a ', username: 'u1', phone: '13812345678', deviceId: 'd1', deviceType: 1 },
|
||||
{
|
||||
baseUrl: ' https://a ',
|
||||
username: 'u1',
|
||||
phone: '13812345678',
|
||||
deviceId: 'd1',
|
||||
deviceType: 1,
|
||||
},
|
||||
{ baseUrl: 'https://a', username: 'u1', phone: '13812345678', deviceId: 'd1', deviceType: 1 },
|
||||
),
|
||||
false,
|
||||
|
||||
@@ -1,70 +1,57 @@
|
||||
export function pickFirstNonEmpty(values: unknown[]) {
|
||||
for (const value of values) {
|
||||
const normalized = String(value || "").trim();
|
||||
const normalized = String(value || '').trim()
|
||||
if (normalized) {
|
||||
return normalized;
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
return ''
|
||||
}
|
||||
|
||||
export function isPlainObject(value: unknown): value is JsonObject {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
|
||||
import { getCloudtentaclesSourceByKey } from "../../../platforms/cloudtentacles/source-config-service.js";
|
||||
import { getCloudtentaclesSessionStateByKey } from "../../../platforms/cloudtentacles/session-state-service.js";
|
||||
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,
|
||||
} from "../../../platforms/cloudtentacles/defaults.js";
|
||||
} from '../../../platforms/cloudtentacles/defaults.js'
|
||||
|
||||
export function resolveCloudtentaclesAdminContext(payload: JsonObject = {}, options: JsonObject = {}) {
|
||||
const sourceKey = String(
|
||||
payload.sourceKey || options.sourceKey || "default"
|
||||
).trim();
|
||||
const savedSource = options.savedSource || getCloudtentaclesSourceByKey(sourceKey) || {};
|
||||
const persistedSession = options.persistedSession ||
|
||||
getCloudtentaclesSessionStateByKey(sourceKey) ||
|
||||
{};
|
||||
const defaultBaseUrl = String(
|
||||
options.defaultBaseUrl || "https://123.207.217.176"
|
||||
).trim();
|
||||
export function resolveCloudtentaclesAdminContext(
|
||||
payload: JsonObject = {},
|
||||
options: JsonObject = {},
|
||||
) {
|
||||
const sourceKey = String(payload.sourceKey || options.sourceKey || 'default').trim()
|
||||
const savedSource = options.savedSource || getCloudtentaclesSourceByKey(sourceKey) || {}
|
||||
const persistedSession =
|
||||
options.persistedSession || getCloudtentaclesSessionStateByKey(sourceKey) || {}
|
||||
const defaultBaseUrl = String(options.defaultBaseUrl || 'https://123.207.217.176').trim()
|
||||
return {
|
||||
sourceKey,
|
||||
baseUrl: pickFirstNonEmpty([
|
||||
payload.baseUrl,
|
||||
savedSource.baseUrl,
|
||||
defaultBaseUrl,
|
||||
]),
|
||||
baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, defaultBaseUrl]),
|
||||
token: pickFirstNonEmpty([payload.token, persistedSession.token]),
|
||||
deviceId: normalizeCloudtentaclesDeviceId(
|
||||
pickFirstNonEmpty([
|
||||
payload.deviceId,
|
||||
savedSource.deviceId,
|
||||
persistedSession.deviceId,
|
||||
])
|
||||
pickFirstNonEmpty([payload.deviceId, savedSource.deviceId, persistedSession.deviceId]),
|
||||
),
|
||||
deviceType: normalizeCloudtentaclesDeviceType(
|
||||
payload.deviceType ?? savedSource.deviceType ?? persistedSession.deviceType
|
||||
payload.deviceType ?? savedSource.deviceType ?? persistedSession.deviceType,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function hasCloudtentaclesCredentialContextChanged(
|
||||
current: JsonObject = {},
|
||||
next: JsonObject = {}
|
||||
next: JsonObject = {},
|
||||
) {
|
||||
return (
|
||||
String(current.baseUrl || "").trim() !==
|
||||
String(next.baseUrl || "").trim() ||
|
||||
String(current.username || "").trim() !==
|
||||
String(next.username || "").trim() ||
|
||||
String(current.phone || "").trim() !== String(next.phone || "").trim() ||
|
||||
String(current.deviceId || "").trim() !==
|
||||
String(next.deviceId || "").trim() ||
|
||||
String(current.baseUrl || '').trim() !== String(next.baseUrl || '').trim() ||
|
||||
String(current.username || '').trim() !== String(next.username || '').trim() ||
|
||||
String(current.phone || '').trim() !== String(next.phone || '').trim() ||
|
||||
String(current.deviceId || '').trim() !== String(next.deviceId || '').trim() ||
|
||||
Number(current.deviceType || 0) !== Number(next.deviceType || 0)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,7 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import {
|
||||
mapAdminCloudtentaclesSession,
|
||||
maskPhone,
|
||||
maskSecret,
|
||||
} from './mappers.js'
|
||||
import { mapAdminCloudtentaclesSession, maskPhone, maskSecret } from './mappers.js'
|
||||
|
||||
test('maskSecret preserves edges while hiding middle characters', () => {
|
||||
assert.equal(maskSecret('abcdef1234567890'), 'abcdef****567890')
|
||||
|
||||
@@ -2,44 +2,44 @@ import type { JsonObject } from '../../../../types/json.js'
|
||||
import {
|
||||
maskPhone as maskPhoneValue,
|
||||
maskSecret as maskSecretValue,
|
||||
} from "../../../../utils/masking.js";
|
||||
} from '../../../../utils/masking.js'
|
||||
import {
|
||||
normalizeCloudtentaclesDeviceId,
|
||||
normalizeCloudtentaclesDeviceType,
|
||||
} from "../../../platforms/cloudtentacles/defaults.js";
|
||||
} from '../../../platforms/cloudtentacles/defaults.js'
|
||||
|
||||
export function maskSecret(value: unknown) {
|
||||
return maskSecretValue(value);
|
||||
return maskSecretValue(value)
|
||||
}
|
||||
|
||||
export function maskPhone(value: unknown) {
|
||||
return maskPhoneValue(value, { maskShort: false });
|
||||
return maskPhoneValue(value, { maskShort: false })
|
||||
}
|
||||
|
||||
export function mapAdminCloudtentaclesSourceConfig(config: JsonObject = {}) {
|
||||
return {
|
||||
key: String(config.key || "").trim(),
|
||||
label: String(config.label || "").trim(),
|
||||
key: String(config.key || '').trim(),
|
||||
label: String(config.label || '').trim(),
|
||||
enabled: config.enabled !== false,
|
||||
baseUrl: String(config.baseUrl || "").trim(),
|
||||
username: String(config.username || "").trim(),
|
||||
password: String(config.password || "").trim(),
|
||||
phone: String(config.phone || "").trim(),
|
||||
baseUrl: String(config.baseUrl || '').trim(),
|
||||
username: String(config.username || '').trim(),
|
||||
password: String(config.password || '').trim(),
|
||||
phone: String(config.phone || '').trim(),
|
||||
deviceId: normalizeCloudtentaclesDeviceId(config.deviceId),
|
||||
deviceType: normalizeCloudtentaclesDeviceType(config.deviceType),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function mapAdminCloudtentaclesSession(session: JsonObject = {}) {
|
||||
return {
|
||||
token: String(session.token || "").trim(),
|
||||
token: String(session.token || '').trim(),
|
||||
tokenMasked: maskSecret(session.token),
|
||||
baseUrl: String(session.baseUrl || "").trim(),
|
||||
username: String(session.username || "").trim(),
|
||||
baseUrl: String(session.baseUrl || '').trim(),
|
||||
username: String(session.username || '').trim(),
|
||||
phoneMasked: maskPhone(session.phone),
|
||||
loggedInAt: String(session.loggedInAt || "").trim(),
|
||||
loggedInAt: String(session.loggedInAt || '').trim(),
|
||||
deviceId: normalizeCloudtentaclesDeviceId(session.deviceId),
|
||||
deviceType: normalizeCloudtentaclesDeviceType(session.deviceType),
|
||||
hasToken: Boolean(String(session.token || "").trim()),
|
||||
};
|
||||
hasToken: Boolean(String(session.token || '').trim()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +1,80 @@
|
||||
import { getCloudtentaclesSourceByKey } from "../../../platforms/cloudtentacles/source-config-service.js";
|
||||
import { getCloudtentaclesSessionStateByKey } from "../../../platforms/cloudtentacles/session-state-service.js";
|
||||
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,
|
||||
} from "../../../platforms/cloudtentacles/defaults.js";
|
||||
import {
|
||||
pickFirstNonEmpty,
|
||||
resolveCloudtentaclesAdminContext,
|
||||
} from "./context.js";
|
||||
import { maskPhone, maskSecret } from "./mappers.js";
|
||||
} from '../../../platforms/cloudtentacles/defaults.js'
|
||||
import { pickFirstNonEmpty, resolveCloudtentaclesAdminContext } from './context.js'
|
||||
import { maskPhone, maskSecret } from './mappers.js'
|
||||
|
||||
const DEFAULT_CLOUDTENTACLES_BASE_URL = "https://123.207.217.176";
|
||||
const DEFAULT_CLOUDTENTACLES_BASE_URL = 'https://123.207.217.176'
|
||||
|
||||
function _resolveSourceKey(payload: JsonObject = {}) {
|
||||
return String(payload.sourceKey || "").trim() || "default";
|
||||
return String(payload.sourceKey || '').trim() || 'default'
|
||||
}
|
||||
|
||||
export function resolveAdminCloudtentaclesCredentialPayload(
|
||||
payload: JsonObject = {},
|
||||
options: JsonObject = {}
|
||||
options: JsonObject = {},
|
||||
) {
|
||||
const sourceKey = _resolveSourceKey(payload);
|
||||
const savedSource = options.savedSource || getCloudtentaclesSourceByKey(sourceKey) || {};
|
||||
const defaultBaseUrl = String(
|
||||
options.defaultBaseUrl || DEFAULT_CLOUDTENTACLES_BASE_URL
|
||||
).trim();
|
||||
const sourceKey = _resolveSourceKey(payload)
|
||||
const savedSource = options.savedSource || getCloudtentaclesSourceByKey(sourceKey) || {}
|
||||
const defaultBaseUrl = String(options.defaultBaseUrl || DEFAULT_CLOUDTENTACLES_BASE_URL).trim()
|
||||
|
||||
return {
|
||||
sourceKey,
|
||||
baseUrl: pickFirstNonEmpty([
|
||||
payload.baseUrl,
|
||||
savedSource.baseUrl,
|
||||
defaultBaseUrl,
|
||||
]),
|
||||
baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, defaultBaseUrl]),
|
||||
username: pickFirstNonEmpty([payload.username, savedSource.username]),
|
||||
password: pickFirstNonEmpty([payload.password, savedSource.password]),
|
||||
phone: pickFirstNonEmpty([payload.phone, savedSource.phone]),
|
||||
deviceId: normalizeCloudtentaclesDeviceId(
|
||||
pickFirstNonEmpty([payload.deviceId, savedSource.deviceId])
|
||||
pickFirstNonEmpty([payload.deviceId, savedSource.deviceId]),
|
||||
),
|
||||
deviceType: normalizeCloudtentaclesDeviceType(
|
||||
payload.deviceType ?? savedSource.deviceType
|
||||
),
|
||||
};
|
||||
deviceType: normalizeCloudtentaclesDeviceType(payload.deviceType ?? savedSource.deviceType),
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveAdminCloudtentaclesSessionPayload(
|
||||
payload: JsonObject = {},
|
||||
options: JsonObject = {}
|
||||
options: JsonObject = {},
|
||||
) {
|
||||
const sourceKey = _resolveSourceKey(payload);
|
||||
const savedSource = options.savedSource || getCloudtentaclesSourceByKey(sourceKey) || {};
|
||||
const persistedSession = options.persistedSession ||
|
||||
getCloudtentaclesSessionStateByKey(sourceKey) ||
|
||||
{};
|
||||
const sourceKey = _resolveSourceKey(payload)
|
||||
const savedSource = options.savedSource || getCloudtentaclesSourceByKey(sourceKey) || {}
|
||||
const persistedSession =
|
||||
options.persistedSession || getCloudtentaclesSessionStateByKey(sourceKey) || {}
|
||||
|
||||
return resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource,
|
||||
persistedSession,
|
||||
sourceKey,
|
||||
defaultBaseUrl: options.defaultBaseUrl || DEFAULT_CLOUDTENTACLES_BASE_URL,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export function buildAdminCloudtentaclesPersistedSessionPayload(
|
||||
session: JsonObject = {},
|
||||
options: JsonObject = {}
|
||||
options: JsonObject = {},
|
||||
) {
|
||||
return {
|
||||
token: String(session.token || "").trim(),
|
||||
baseUrl: String(session.baseUrl || "").trim(),
|
||||
token: String(session.token || '').trim(),
|
||||
baseUrl: String(session.baseUrl || '').trim(),
|
||||
username: pickFirstNonEmpty([options.username, session.username]),
|
||||
phone: pickFirstNonEmpty([options.phone, session.phone]),
|
||||
loggedInAt: String(session.loggedInAt || "").trim(),
|
||||
loggedInAt: String(session.loggedInAt || '').trim(),
|
||||
deviceId: normalizeCloudtentaclesDeviceId(
|
||||
pickFirstNonEmpty([options.deviceId, session.deviceId])
|
||||
pickFirstNonEmpty([options.deviceId, session.deviceId]),
|
||||
),
|
||||
deviceType: normalizeCloudtentaclesDeviceType(
|
||||
options.deviceType ?? session.deviceType
|
||||
),
|
||||
};
|
||||
deviceType: normalizeCloudtentaclesDeviceType(options.deviceType ?? session.deviceType),
|
||||
}
|
||||
}
|
||||
|
||||
export function buildAdminCloudtentaclesSessionSummary(
|
||||
session: JsonObject = {},
|
||||
savedSession: JsonObject = {},
|
||||
sourceKey = "default"
|
||||
sourceKey = 'default',
|
||||
) {
|
||||
const permissions = Array.isArray(session.permissions)
|
||||
? session.permissions
|
||||
: [];
|
||||
const permissions = Array.isArray(session.permissions) ? session.permissions : []
|
||||
|
||||
return {
|
||||
sourceKey,
|
||||
@@ -98,47 +82,39 @@ export function buildAdminCloudtentaclesSessionSummary(
|
||||
permissionCount: permissions.length,
|
||||
permissions,
|
||||
persisted: Boolean(savedSession.token),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function buildAdminCloudtentaclesLoginResult(
|
||||
session: JsonObject = {},
|
||||
savedSession: JsonObject = {},
|
||||
sourceKey = "default"
|
||||
sourceKey = 'default',
|
||||
) {
|
||||
return {
|
||||
sourceKey,
|
||||
baseUrl: String(session.baseUrl || "").trim(),
|
||||
username: String(session.username || "").trim(),
|
||||
baseUrl: String(session.baseUrl || '').trim(),
|
||||
username: String(session.username || '').trim(),
|
||||
phoneMasked: maskPhone(session.phone),
|
||||
loggedInAt: String(session.loggedInAt || "").trim(),
|
||||
responseMessage: String(session.responseMessage || "").trim(),
|
||||
token: String(session.token || "").trim(),
|
||||
session: buildAdminCloudtentaclesSessionSummary(
|
||||
session,
|
||||
savedSession,
|
||||
sourceKey
|
||||
),
|
||||
loggedInAt: String(session.loggedInAt || '').trim(),
|
||||
responseMessage: String(session.responseMessage || '').trim(),
|
||||
token: String(session.token || '').trim(),
|
||||
session: buildAdminCloudtentaclesSessionSummary(session, savedSession, sourceKey),
|
||||
userInfo: session.userInfo,
|
||||
asset: session.asset,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function buildAdminCloudtentaclesValidateResult(
|
||||
session: JsonObject = {},
|
||||
savedSession: JsonObject = {},
|
||||
sourceKey = "default"
|
||||
sourceKey = 'default',
|
||||
) {
|
||||
return {
|
||||
sourceKey,
|
||||
baseUrl: String(session.baseUrl || "").trim(),
|
||||
loggedInAt: String(session.loggedInAt || "").trim(),
|
||||
session: buildAdminCloudtentaclesSessionSummary(
|
||||
session,
|
||||
savedSession,
|
||||
sourceKey
|
||||
),
|
||||
baseUrl: String(session.baseUrl || '').trim(),
|
||||
loggedInAt: String(session.loggedInAt || '').trim(),
|
||||
session: buildAdminCloudtentaclesSessionSummary(session, savedSession, sourceKey),
|
||||
userInfo: session.userInfo,
|
||||
asset: session.asset,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,19 @@ import type { JsonObject } from '../../../../types/json.js'
|
||||
import {
|
||||
normalizeCloudtentaclesDeviceId,
|
||||
normalizeCloudtentaclesDeviceType,
|
||||
} from "../../../platforms/cloudtentacles/defaults.js";
|
||||
} from '../../../platforms/cloudtentacles/defaults.js'
|
||||
|
||||
export function normalizeAdminCloudtentaclesSourceConfigPayload(
|
||||
payload: JsonObject = {}
|
||||
) {
|
||||
const sourceKey =
|
||||
String(payload.sourceKey || payload.key || "").trim() || "default";
|
||||
export function normalizeAdminCloudtentaclesSourceConfigPayload(payload: JsonObject = {}) {
|
||||
const sourceKey = String(payload.sourceKey || payload.key || '').trim() || 'default'
|
||||
return {
|
||||
key: sourceKey,
|
||||
label: String(payload.label || "").trim(),
|
||||
label: String(payload.label || '').trim(),
|
||||
enabled: payload.enabled !== false,
|
||||
baseUrl: String(payload.baseUrl || "").trim() || "https://123.207.217.176",
|
||||
username: String(payload.username || "").trim(),
|
||||
password: String(payload.password || "").trim(),
|
||||
phone: String(payload.phone || "").trim(),
|
||||
baseUrl: String(payload.baseUrl || '').trim() || 'https://123.207.217.176',
|
||||
username: String(payload.username || '').trim(),
|
||||
password: String(payload.password || '').trim(),
|
||||
phone: String(payload.phone || '').trim(),
|
||||
deviceId: normalizeCloudtentaclesDeviceId(payload.deviceId),
|
||||
deviceType: normalizeCloudtentaclesDeviceType(payload.deviceType),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,14 +60,18 @@ export async function listAdminKuaishouFeifeiProducts(payload: JsonObject = {})
|
||||
page: Number(payload.page || 1) || 1,
|
||||
perPage: Number(payload.perPage || payload.per_page || 20) || 20,
|
||||
status: String(payload.status || 'on_sale').trim(),
|
||||
supplyProductName: String(payload.supplyProductName || payload.supply_product_name || '').trim(),
|
||||
supplyProductName: String(
|
||||
payload.supplyProductName || payload.supply_product_name || '',
|
||||
).trim(),
|
||||
})
|
||||
}
|
||||
|
||||
export async function syncAdminKuaishouFeifeiProductRules(payload: JsonObject = {}) {
|
||||
const source = getAdminEditableKuaishouFeifeiConfig()
|
||||
const status = String(payload.status || 'on_sale').trim() || 'on_sale'
|
||||
const supplyProductName = String(payload.supplyProductName || payload.supply_product_name || '').trim()
|
||||
const supplyProductName = String(
|
||||
payload.supplyProductName || payload.supply_product_name || '',
|
||||
).trim()
|
||||
const products = await listAllKuaishouFeifeiProducts({
|
||||
status,
|
||||
supplyProductName,
|
||||
@@ -185,10 +189,7 @@ function mapEffectiveKuaishouFeifeiConfig(config: ReturnType<typeof getKuaishouF
|
||||
}
|
||||
}
|
||||
|
||||
async function listAllKuaishouFeifeiProducts(input: {
|
||||
status: string
|
||||
supplyProductName: string
|
||||
}) {
|
||||
async function listAllKuaishouFeifeiProducts(input: { status: string; supplyProductName: string }) {
|
||||
const perPage = 100
|
||||
const firstPage = await listKuaishouFeifeiProducts({
|
||||
page: 1,
|
||||
|
||||
@@ -14,16 +14,9 @@ import {
|
||||
refreshKuaishouIndustryAccessToken,
|
||||
} from '../../platforms/kuaishou-industry/token-service.js'
|
||||
|
||||
const SECRET_FIELDS = [
|
||||
'appSecret',
|
||||
'signSecret',
|
||||
'messageSecret',
|
||||
] as const
|
||||
const SECRET_FIELDS = ['appSecret', 'signSecret', 'messageSecret'] as const
|
||||
|
||||
const SHOP_SECRET_FIELDS = [
|
||||
'accessToken',
|
||||
'refreshToken',
|
||||
] as const
|
||||
const SHOP_SECRET_FIELDS = ['accessToken', 'refreshToken'] as const
|
||||
|
||||
export function getAdminKuaishouIndustrySourceConfig() {
|
||||
const config = getKuaishouIndustrySourceConfig()
|
||||
@@ -38,15 +31,23 @@ export async function updateAdminKuaishouIndustrySourceConfig(payload: JsonObjec
|
||||
const current = getKuaishouIndustrySourceConfig()
|
||||
const saved = await saveKuaishouIndustrySourceConfig({
|
||||
...current,
|
||||
enabled: hasPayloadField(payload, 'enabled') ? payload.enabled !== false : current.enabled !== false,
|
||||
enabled: hasPayloadField(payload, 'enabled')
|
||||
? payload.enabled !== false
|
||||
: current.enabled !== false,
|
||||
baseUrl: readConfigString(payload, 'baseUrl', current.baseUrl),
|
||||
authBaseUrl: readConfigString(payload, 'authBaseUrl', current.authBaseUrl),
|
||||
redirectUri: readConfigString(payload, 'redirectUri', current.redirectUri, { allowBlank: true }),
|
||||
scopes: normalizeScopeText(readConfigString(payload, 'scopes', current.scopes, { allowBlank: true })),
|
||||
redirectUri: readConfigString(payload, 'redirectUri', current.redirectUri, {
|
||||
allowBlank: true,
|
||||
}),
|
||||
scopes: normalizeScopeText(
|
||||
readConfigString(payload, 'scopes', current.scopes, { allowBlank: true }),
|
||||
),
|
||||
authState: readConfigString(payload, 'authState', current.authState, { allowBlank: true }),
|
||||
appKey: readConfigString(payload, 'appKey', current.appKey, { allowBlank: true }),
|
||||
openId: readConfigString(payload, 'openId', current.openId, { allowBlank: true }),
|
||||
grantedScopes: normalizeScopeText(readConfigString(payload, 'grantedScopes', current.grantedScopes, { allowBlank: true })),
|
||||
grantedScopes: normalizeScopeText(
|
||||
readConfigString(payload, 'grantedScopes', current.grantedScopes, { allowBlank: true }),
|
||||
),
|
||||
sellerId: readConfigString(payload, 'sellerId', current.sellerId, { allowBlank: true }),
|
||||
provider: readConfigString(payload, 'provider', current.provider),
|
||||
platform: readConfigString(payload, 'platform', current.platform),
|
||||
@@ -245,33 +246,77 @@ function normalizeShopConfigPayload(
|
||||
|
||||
const payload = rawShop as JsonObject
|
||||
const current = resolveCurrentShopConfig(payload, index, currentShops)
|
||||
const sellerId = readConfigString(payload, 'sellerId', current?.sellerId || '', { allowBlank: true })
|
||||
const shopId = readConfigString(payload, 'shopId', current?.shopId || sellerId, { allowBlank: true }) || sellerId
|
||||
const shopName = readConfigString(payload, 'shopName', current?.shopName || '', { allowBlank: true })
|
||||
const customShopName = readConfigString(payload, 'customShopName', current?.customShopName || '', { allowBlank: true })
|
||||
const sellerId = readConfigString(payload, 'sellerId', current?.sellerId || '', {
|
||||
allowBlank: true,
|
||||
})
|
||||
const shopId =
|
||||
readConfigString(payload, 'shopId', current?.shopId || sellerId, { allowBlank: true }) ||
|
||||
sellerId
|
||||
const shopName = readConfigString(payload, 'shopName', current?.shopName || '', {
|
||||
allowBlank: true,
|
||||
})
|
||||
const customShopName = readConfigString(
|
||||
payload,
|
||||
'customShopName',
|
||||
current?.customShopName || '',
|
||||
{ allowBlank: true },
|
||||
)
|
||||
const accessToken = readSecretString(payload, 'accessToken', current?.accessToken || '')
|
||||
const refreshToken = readSecretString(payload, 'refreshToken', current?.refreshToken || '')
|
||||
const openId = readConfigString(payload, 'openId', current?.openId || '', { allowBlank: true })
|
||||
|
||||
if (!sellerId && !shopId && !shopName && !customShopName && !accessToken && !refreshToken && !openId) {
|
||||
if (
|
||||
!sellerId &&
|
||||
!shopId &&
|
||||
!shopName &&
|
||||
!customShopName &&
|
||||
!accessToken &&
|
||||
!refreshToken &&
|
||||
!openId
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: hasPayloadField(payload, 'enabled') ? payload.enabled !== false : current?.enabled !== false,
|
||||
enabled: hasPayloadField(payload, 'enabled')
|
||||
? payload.enabled !== false
|
||||
: current?.enabled !== false,
|
||||
sellerId,
|
||||
shopId,
|
||||
shopName,
|
||||
customShopName,
|
||||
authState: readConfigString(payload, 'authState', current?.authState || '', { allowBlank: true }),
|
||||
authState: readConfigString(payload, 'authState', current?.authState || '', {
|
||||
allowBlank: true,
|
||||
}),
|
||||
accessToken,
|
||||
refreshToken,
|
||||
accessTokenExpiresAt: readConfigString(payload, 'accessTokenExpiresAt', current?.accessTokenExpiresAt || '', { allowBlank: true }),
|
||||
refreshTokenExpiresAt: readConfigString(payload, 'refreshTokenExpiresAt', current?.refreshTokenExpiresAt || '', { allowBlank: true }),
|
||||
accessTokenExpiresAt: readConfigString(
|
||||
payload,
|
||||
'accessTokenExpiresAt',
|
||||
current?.accessTokenExpiresAt || '',
|
||||
{ allowBlank: true },
|
||||
),
|
||||
refreshTokenExpiresAt: readConfigString(
|
||||
payload,
|
||||
'refreshTokenExpiresAt',
|
||||
current?.refreshTokenExpiresAt || '',
|
||||
{ allowBlank: true },
|
||||
),
|
||||
openId,
|
||||
grantedScopes: normalizeScopeText(readConfigString(payload, 'grantedScopes', current?.grantedScopes || '', { allowBlank: true })),
|
||||
lastRefreshedAt: readConfigString(payload, 'lastRefreshedAt', current?.lastRefreshedAt || '', { allowBlank: true }),
|
||||
lastRefreshError: readConfigString(payload, 'lastRefreshError', current?.lastRefreshError || '', { allowBlank: true }),
|
||||
grantedScopes: normalizeScopeText(
|
||||
readConfigString(payload, 'grantedScopes', current?.grantedScopes || '', {
|
||||
allowBlank: true,
|
||||
}),
|
||||
),
|
||||
lastRefreshedAt: readConfigString(payload, 'lastRefreshedAt', current?.lastRefreshedAt || '', {
|
||||
allowBlank: true,
|
||||
}),
|
||||
lastRefreshError: readConfigString(
|
||||
payload,
|
||||
'lastRefreshError',
|
||||
current?.lastRefreshError || '',
|
||||
{ allowBlank: true },
|
||||
),
|
||||
...resolveShopSecretPatch(payload, current),
|
||||
}
|
||||
}
|
||||
@@ -305,16 +350,14 @@ function resolveShopSecretPatch(
|
||||
return patch
|
||||
}
|
||||
|
||||
function readSecretString(
|
||||
payload: JsonObject,
|
||||
field: string,
|
||||
fallback: string,
|
||||
): string {
|
||||
function readSecretString(payload: JsonObject, field: string, fallback: string): string {
|
||||
const text = String(payload[field] || '').trim()
|
||||
return text || fallback
|
||||
}
|
||||
|
||||
function resolveAccessTokenStatus(config: Pick<KuaishouIndustrySourceConfig, 'accessToken' | 'accessTokenExpiresAt'>) {
|
||||
function resolveAccessTokenStatus(
|
||||
config: Pick<KuaishouIndustrySourceConfig, 'accessToken' | 'accessTokenExpiresAt'>,
|
||||
) {
|
||||
if (!config.accessToken) {
|
||||
return {
|
||||
status: 'missing',
|
||||
|
||||
@@ -94,23 +94,27 @@ function mapAdminNotificationConfig(config: JsonObject = {}) {
|
||||
bark: {
|
||||
enabled: bark.enabled !== false,
|
||||
serverUrl: String(bark.serverUrl || 'https://api.day.app').trim(),
|
||||
recipients: (Array.isArray(bark.recipients) ? bark.recipients : []).map((item: JsonObject) => ({
|
||||
id: String(item.id || '').trim(),
|
||||
name: String(item.name || '').trim(),
|
||||
deviceKey: String(item.deviceKey || '').trim(),
|
||||
deviceKeyMasked: maskSecret(item.deviceKey),
|
||||
enabled: item.enabled !== false,
|
||||
})),
|
||||
recipients: (Array.isArray(bark.recipients) ? bark.recipients : []).map(
|
||||
(item: JsonObject) => ({
|
||||
id: String(item.id || '').trim(),
|
||||
name: String(item.name || '').trim(),
|
||||
deviceKey: String(item.deviceKey || '').trim(),
|
||||
deviceKeyMasked: maskSecret(item.deviceKey),
|
||||
enabled: item.enabled !== false,
|
||||
}),
|
||||
),
|
||||
},
|
||||
wpush: {
|
||||
enabled: wpush.enabled !== false,
|
||||
recipients: (Array.isArray(wpush.recipients) ? wpush.recipients : []).map((item: JsonObject) => ({
|
||||
id: String(item.id || '').trim(),
|
||||
name: String(item.name || '').trim(),
|
||||
apiKey: String(item.apiKey || item.apikey || '').trim(),
|
||||
apiKeyMasked: maskSecret(item.apiKey || item.apikey),
|
||||
enabled: item.enabled !== false,
|
||||
})),
|
||||
recipients: (Array.isArray(wpush.recipients) ? wpush.recipients : []).map(
|
||||
(item: JsonObject) => ({
|
||||
id: String(item.id || '').trim(),
|
||||
name: String(item.name || '').trim(),
|
||||
apiKey: String(item.apiKey || item.apikey || '').trim(),
|
||||
apiKeyMasked: maskSecret(item.apiKey || item.apikey),
|
||||
enabled: item.enabled !== false,
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -118,8 +122,7 @@ function mapAdminNotificationConfig(config: JsonObject = {}) {
|
||||
|
||||
function mapAdminScheduledJobsConfig(config: JsonObject = {}) {
|
||||
const cloudtentaclesAccountMap = new Map(
|
||||
listAdminCloudtentaclesMonitorAccounts()
|
||||
.map((item) => [item.sourceKey, item]),
|
||||
listAdminCloudtentaclesMonitorAccounts().map((item) => [item.sourceKey, item]),
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -147,21 +150,23 @@ function listAdminCloudtentaclesMonitorAccounts() {
|
||||
const sessionsConfig = getAllCloudtentaclesSessionStates()
|
||||
const sessions: Record<string, JsonObject> = sessionsConfig.sessions || {}
|
||||
|
||||
return (Array.isArray(sourcesConfig.sources) ? sourcesConfig.sources : []).map((source) => {
|
||||
const sourceKey = String(source.key || '').trim()
|
||||
const session = sessions[sourceKey] || {}
|
||||
const label = String(source.label || source.username || sourceKey).trim() || sourceKey
|
||||
return (Array.isArray(sourcesConfig.sources) ? sourcesConfig.sources : [])
|
||||
.map((source) => {
|
||||
const sourceKey = String(source.key || '').trim()
|
||||
const session = sessions[sourceKey] || {}
|
||||
const label = String(source.label || source.username || sourceKey).trim() || sourceKey
|
||||
|
||||
return {
|
||||
sourceKey,
|
||||
label,
|
||||
enabled: source.enabled !== false,
|
||||
username: String(source.username || '').trim(),
|
||||
phoneMasked: maskPhone(source.phone || session.phone),
|
||||
hasToken: Boolean(String(session.token || '').trim()),
|
||||
loggedInAt: String(session.loggedInAt || '').trim(),
|
||||
}
|
||||
}).filter((item) => item.sourceKey)
|
||||
return {
|
||||
sourceKey,
|
||||
label,
|
||||
enabled: source.enabled !== false,
|
||||
username: String(source.username || '').trim(),
|
||||
phoneMasked: maskPhone(source.phone || session.phone),
|
||||
hasToken: Boolean(String(session.token || '').trim()),
|
||||
loggedInAt: String(session.loggedInAt || '').trim(),
|
||||
}
|
||||
})
|
||||
.filter((item) => item.sourceKey)
|
||||
}
|
||||
|
||||
function mapScheduledJobCloudtentaclesAccounts(
|
||||
@@ -193,30 +198,35 @@ function mapScheduledJobCloudtentaclesAccounts(
|
||||
},
|
||||
] as const
|
||||
})
|
||||
.filter(Boolean) as Array<readonly [string, {
|
||||
sourceKey: string
|
||||
label: string
|
||||
enabled: boolean
|
||||
assetThreshold: number
|
||||
}]>,
|
||||
.filter(Boolean) as Array<
|
||||
readonly [
|
||||
string,
|
||||
{
|
||||
sourceKey: string
|
||||
label: string
|
||||
enabled: boolean
|
||||
assetThreshold: number
|
||||
},
|
||||
]
|
||||
>,
|
||||
)
|
||||
|
||||
// 读配置时合并全部 kuaishou-lewan 账号,避免前端/任务只看到历史 default。
|
||||
const merged = Array.from(cloudtentaclesAccountMap.values()).map((option) => {
|
||||
const sourceKey = String(option.sourceKey || '').trim()
|
||||
const configured = configuredMap.get(sourceKey)
|
||||
return {
|
||||
sourceKey,
|
||||
label: String(configured?.label || option.label || sourceKey).trim(),
|
||||
enabled: configured
|
||||
? configured.enabled !== false
|
||||
: option.enabled !== false,
|
||||
assetThreshold: normalizeNonNegativeNumber(
|
||||
configured?.assetThreshold,
|
||||
defaultAssetThreshold,
|
||||
),
|
||||
}
|
||||
}).filter((item) => item.sourceKey)
|
||||
const merged = Array.from(cloudtentaclesAccountMap.values())
|
||||
.map((option) => {
|
||||
const sourceKey = String(option.sourceKey || '').trim()
|
||||
const configured = configuredMap.get(sourceKey)
|
||||
return {
|
||||
sourceKey,
|
||||
label: String(configured?.label || option.label || sourceKey).trim(),
|
||||
enabled: configured ? configured.enabled !== false : option.enabled !== false,
|
||||
assetThreshold: normalizeNonNegativeNumber(
|
||||
configured?.assetThreshold,
|
||||
defaultAssetThreshold,
|
||||
),
|
||||
}
|
||||
})
|
||||
.filter((item) => item.sourceKey)
|
||||
|
||||
const mergedKeys = new Set(merged.map((item) => item.sourceKey))
|
||||
for (const [sourceKey, configured] of configuredMap.entries()) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { buildClaimUrl, createTaskClaimToken } from '../claim/claim-service.js'
|
||||
import {
|
||||
maskCode as maskCodeValue,
|
||||
maskPhone as maskPhoneValue,
|
||||
} from '../../utils/masking.js'
|
||||
import { maskCode as maskCodeValue, maskPhone as maskPhoneValue } from '../../utils/masking.js'
|
||||
|
||||
import type { TaskRow } from '../../types/repository/rows.js'
|
||||
|
||||
@@ -31,7 +28,9 @@ export function isRecoverableTaskSessionCloseError(error: ErrorLike | null | und
|
||||
}
|
||||
|
||||
export function normalizeManualDispatchOutcome(value: unknown): 'delivered' | 'failed' {
|
||||
const normalized = String(value || '').trim().toLowerCase()
|
||||
const normalized = String(value || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
|
||||
if (normalized === 'failed') {
|
||||
return 'failed'
|
||||
|
||||
@@ -25,18 +25,13 @@ import {
|
||||
refreshFulfillmentRole,
|
||||
returnFulfillmentNumber,
|
||||
} from '../../fulfillment/executors/registry.js'
|
||||
import {
|
||||
isKuaishouCloudTask,
|
||||
normalizeKuaishouCloudFlow,
|
||||
} from './kuaishou-cloud-helpers.js'
|
||||
import { isKuaishouCloudTask, normalizeKuaishouCloudFlow } from './kuaishou-cloud-helpers.js'
|
||||
|
||||
import type {
|
||||
AdminEntityIdInput,
|
||||
AdminViewerSessionInput,
|
||||
} from '../../../types/admin/read-inputs.js'
|
||||
import type {
|
||||
AdminTaskKuaishouIndustryConsumeInput,
|
||||
} from '../../../types/admin/write-inputs.js'
|
||||
import type { AdminTaskKuaishouIndustryConsumeInput } from '../../../types/admin/write-inputs.js'
|
||||
import type { AdminTaskActionResponse } from '../../../types/admin/write-models.js'
|
||||
import type { KuaishouIndustryVoucherRow, TaskRow } from '../../../types/repository/rows.js'
|
||||
|
||||
@@ -443,8 +438,8 @@ export async function consumeAdminTaskKuaishouIndustryVoucher(
|
||||
const taskContext = parseTaskContext(consumeTargetTask)
|
||||
const hasCloudFulfillmentContext = Boolean(
|
||||
taskContext.kuaishouCloudFulfillment &&
|
||||
typeof taskContext.kuaishouCloudFulfillment === 'object' &&
|
||||
!Array.isArray(taskContext.kuaishouCloudFulfillment),
|
||||
typeof taskContext.kuaishouCloudFulfillment === 'object' &&
|
||||
!Array.isArray(taskContext.kuaishouCloudFulfillment),
|
||||
)
|
||||
const flow = hasCloudFulfillmentContext
|
||||
? normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
||||
@@ -491,7 +486,9 @@ export async function consumeAdminTaskKuaishouIndustryVoucher(
|
||||
return { task: mapTaskActionPayload(updatedTask || consumeTargetTask) }
|
||||
}
|
||||
|
||||
async function getRequiredIndustryVoucherForTask(task: TaskRow): Promise<KuaishouIndustryVoucherRow> {
|
||||
async function getRequiredIndustryVoucherForTask(
|
||||
task: TaskRow,
|
||||
): Promise<KuaishouIndustryVoucherRow> {
|
||||
const vouchers = await listKuaishouIndustryVouchersByTaskId(task.id)
|
||||
const firstVoucher = vouchers[0]
|
||||
if (firstVoucher) {
|
||||
@@ -499,7 +496,9 @@ async function getRequiredIndustryVoucherForTask(task: TaskRow): Promise<Kuaisho
|
||||
}
|
||||
|
||||
const taskContext = parseTaskContext(task)
|
||||
const voucherContext = normalizeAdminTaskIndustryVoucherContext(taskContext.kuaishouIndustryVoucher)
|
||||
const voucherContext = normalizeAdminTaskIndustryVoucherContext(
|
||||
taskContext.kuaishouIndustryVoucher,
|
||||
)
|
||||
const voucherCode = String(voucherContext.voucherCode || voucherContext.eticketId || '').trim()
|
||||
if (voucherCode) {
|
||||
const voucher = await findKuaishouIndustryVoucherByCode(voucherCode, task.platform_order_id)
|
||||
@@ -539,12 +538,12 @@ async function getRequiredIndustryVoucherForTask(task: TaskRow): Promise<Kuaisho
|
||||
}
|
||||
|
||||
function normalizeAdminTaskIndustryVoucherContext(value: unknown): JsonObject {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as JsonObject
|
||||
: {}
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonObject) : {}
|
||||
}
|
||||
|
||||
async function resolveIndustryVouchersForOrder(platformOrderId: string): Promise<KuaishouIndustryVoucherRow[]> {
|
||||
async function resolveIndustryVouchersForOrder(
|
||||
platformOrderId: string,
|
||||
): Promise<KuaishouIndustryVoucherRow[]> {
|
||||
const normalizedOid = String(platformOrderId || '').trim()
|
||||
if (!normalizedOid) {
|
||||
return []
|
||||
|
||||
@@ -52,7 +52,9 @@ export type PreparedKuaishouCloudBindResource = {
|
||||
bindUrl: string
|
||||
}
|
||||
|
||||
export function resolvePersistedCloudtentaclesContext(sourceKeys: unknown[]): CloudtentaclesContext {
|
||||
export function resolvePersistedCloudtentaclesContext(
|
||||
sourceKeys: unknown[],
|
||||
): CloudtentaclesContext {
|
||||
try {
|
||||
return resolvePersistedCloudtentaclesContextBySourceKeys(sourceKeys)
|
||||
} catch (error) {
|
||||
|
||||
@@ -4,14 +4,8 @@ import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { nowIso } from '../../../utils/time.js'
|
||||
import { TASK_STATUS } from '../../../domain/task-status.js'
|
||||
import {
|
||||
canViewerCloseTask,
|
||||
createAdminViewerContext,
|
||||
} from '../admin-read-shared-helpers.js'
|
||||
import {
|
||||
getRequiredTask,
|
||||
mapTaskActionPayload,
|
||||
} from '../admin-task-read-helpers.js'
|
||||
import { canViewerCloseTask, createAdminViewerContext } from '../admin-read-shared-helpers.js'
|
||||
import { getRequiredTask, mapTaskActionPayload } from '../admin-task-read-helpers.js'
|
||||
|
||||
import type {
|
||||
AdminEntityIdInput,
|
||||
@@ -62,16 +56,21 @@ export async function closeAdminTask(
|
||||
})
|
||||
}
|
||||
|
||||
await createTaskEvent(task.id, 'task_closed', {
|
||||
closedBy: session
|
||||
? {
|
||||
userId: session.userId,
|
||||
username: session.username,
|
||||
role: session.role,
|
||||
}
|
||||
: null,
|
||||
claimTokenClosed: claimTokenId > 0,
|
||||
}, now)
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'task_closed',
|
||||
{
|
||||
closedBy: session
|
||||
? {
|
||||
userId: session.userId,
|
||||
username: session.username,
|
||||
role: session.role,
|
||||
}
|
||||
: null,
|
||||
claimTokenClosed: claimTokenId > 0,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
return {
|
||||
task: mapTaskActionPayload(updatedTask),
|
||||
|
||||
Reference in New Issue
Block a user