优化后端代码质量基础
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
Generated
+17
@@ -15,6 +15,7 @@
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/pg": "^8.20.0",
|
||||
"prettier": "^3.8.3",
|
||||
"tsx": "^4.22.3",
|
||||
"typescript": "^6.0.2"
|
||||
}
|
||||
@@ -1299,6 +1300,22 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
|
||||
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"build": "tsc -p tsconfig.build.json && rm -rf dist/db/migrations && mkdir -p dist/db && cp -R src/db/migrations dist/db/migrations",
|
||||
"db:migrate": "tsx src/db/migrate.ts",
|
||||
"dev": "tsx watch --clear-screen=false src/index.ts",
|
||||
"format": "prettier --write .",
|
||||
"test": "node --import tsx --test $(find src \\( -name '*.test.ts' -o -name '*.test.js' \\) -print)",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"start": "node dist/index.js",
|
||||
@@ -21,6 +22,7 @@
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/pg": "^8.20.0",
|
||||
"prettier": "^3.8.3",
|
||||
"tsx": "^4.22.3",
|
||||
"typescript": "^6.0.2"
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
mapAdminCloudtentaclesSourceConfig,
|
||||
maskPhone,
|
||||
} from "./mappers.js";
|
||||
import { createHttpError } from "../../../../utils/http.js";
|
||||
|
||||
import type {
|
||||
AdminCloudtentaclesCatalogQueryInput,
|
||||
@@ -153,7 +154,10 @@ export function updateAdminCloudtentaclesSourceConfig(
|
||||
export function deleteAdminCloudtentaclesSource(sourceKey: string) {
|
||||
const key = String(sourceKey || "").trim();
|
||||
if (!key) {
|
||||
throw new Error("sourceKey is required for deletion");
|
||||
throw createHttpError("cloudtentacles sourceKey 不能为空", {
|
||||
statusCode: 400,
|
||||
errorCode: "cloudtentacles_source_key_required",
|
||||
});
|
||||
}
|
||||
deleteCloudtentaclesSessionStateByKey(key);
|
||||
deleteCloudtentaclesSourceByKey(key);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { runtimeConfig } from '../../config/runtime.js'
|
||||
import { createClaimToken } from '../../repositories/claim-token-repo.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { addHours, nowIso } from '../../utils/time.js'
|
||||
import { randomToken } from '../../utils/random.js'
|
||||
|
||||
@@ -23,7 +24,10 @@ export async function createTaskClaimToken(taskId: number | string): Promise<Tas
|
||||
})
|
||||
|
||||
if (!token) {
|
||||
throw new Error('领取 token 创建失败')
|
||||
throw createHttpError('领取 token 创建失败', {
|
||||
statusCode: 500,
|
||||
errorCode: 'claim_token_create_failed',
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createHttpError } from '../../utils/http.js'
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 10000
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type JsonObject = Record<string, unknown>
|
||||
type BarkSendInput = {
|
||||
serverUrl?: string
|
||||
recipient?: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { sendInternalNotification } from './notification-service.js'
|
||||
const DEFAULT_COOLDOWN_MS = 10 * 60 * 1000
|
||||
const notificationCooldownMap = new Map<string, number>()
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type JsonObject = Record<string, unknown>
|
||||
type InternalNotificationPayload = {
|
||||
title: string
|
||||
body?: string
|
||||
@@ -13,6 +13,55 @@ type InternalNotificationPayload = {
|
||||
cooldownKey?: string
|
||||
cooldownMs?: number
|
||||
}
|
||||
type KuaishouCloudAssetNotEnoughPayload = {
|
||||
task?: unknown
|
||||
flow?: unknown
|
||||
assetBefore?: unknown
|
||||
requiredAsset?: unknown
|
||||
skuName?: unknown
|
||||
}
|
||||
type CloudtentaclesAuthExpiredPayload = {
|
||||
pathname?: unknown
|
||||
errorCode?: unknown
|
||||
message?: unknown
|
||||
cooldownSeconds?: unknown
|
||||
sourceKey?: unknown
|
||||
accountLabel?: unknown
|
||||
}
|
||||
type CloudtentaclesAssetLowPayload = {
|
||||
asset?: unknown
|
||||
threshold?: unknown
|
||||
cooldownSeconds?: unknown
|
||||
sourceKey?: unknown
|
||||
accountLabel?: unknown
|
||||
}
|
||||
type Open91PendingConfigPayload = {
|
||||
order?: unknown
|
||||
orderNo?: unknown
|
||||
productNo?: unknown
|
||||
buyNum?: unknown
|
||||
reason?: unknown
|
||||
}
|
||||
type TaskNotificationPayload = {
|
||||
task?: unknown
|
||||
reason?: unknown
|
||||
source?: unknown
|
||||
errorMessage?: unknown
|
||||
}
|
||||
type KuaishouCloudConsumeFailedPayload = {
|
||||
task?: unknown
|
||||
order?: unknown
|
||||
ticketCodeMasked?: unknown
|
||||
shopId?: unknown
|
||||
shopName?: unknown
|
||||
errorMessage?: unknown
|
||||
}
|
||||
type ClaimRedeemNeedsAttentionPayload = {
|
||||
task?: unknown
|
||||
order?: unknown
|
||||
status?: unknown
|
||||
errorMessage?: unknown
|
||||
}
|
||||
|
||||
export async function notifyInternalSafely(payload: InternalNotificationPayload) {
|
||||
const cooldownKey = String(payload.cooldownKey || '').trim()
|
||||
@@ -52,7 +101,7 @@ export function notifyKuaishouCloudAssetNotEnough({
|
||||
assetBefore = 0,
|
||||
requiredAsset = 0,
|
||||
skuName = '',
|
||||
}: JsonObject = {}) {
|
||||
}: KuaishouCloudAssetNotEnoughPayload = {}) {
|
||||
const taskRecord = toRecord(task)
|
||||
const flowRecord = toRecord(flow)
|
||||
const binding = toRecord(flowRecord.binding)
|
||||
@@ -81,7 +130,7 @@ export function notifyCloudtentaclesAuthExpired({
|
||||
cooldownSeconds = 600,
|
||||
sourceKey = '',
|
||||
accountLabel = '',
|
||||
}: JsonObject = {}) {
|
||||
}: CloudtentaclesAuthExpiredPayload = {}) {
|
||||
const normalizedSourceKey = String(sourceKey || '').trim()
|
||||
const normalizedAccountLabel = String(accountLabel || '').trim()
|
||||
|
||||
@@ -107,7 +156,7 @@ export function notifyCloudtentaclesAssetLow({
|
||||
cooldownSeconds = 1800,
|
||||
sourceKey = '',
|
||||
accountLabel = '',
|
||||
}: JsonObject = {}) {
|
||||
}: CloudtentaclesAssetLowPayload = {}) {
|
||||
const normalizedSourceKey = String(sourceKey || '').trim()
|
||||
const normalizedAccountLabel = String(accountLabel || '').trim()
|
||||
|
||||
@@ -133,7 +182,7 @@ export function notifyOpen91PendingConfig({
|
||||
productNo = '',
|
||||
buyNum = 0,
|
||||
reason = '未命中履约配置',
|
||||
}: JsonObject = {}) {
|
||||
}: Open91PendingConfigPayload = {}) {
|
||||
const orderRecord = toRecord(order)
|
||||
|
||||
return notifyInternalSafely({
|
||||
@@ -152,7 +201,7 @@ export function notifyOpen91PendingConfig({
|
||||
export function notifyKuaishouCloudBindUrlRefreshFailed({
|
||||
task = {},
|
||||
errorMessage = '',
|
||||
}: JsonObject = {}) {
|
||||
}: TaskNotificationPayload = {}) {
|
||||
const taskRecord = toRecord(task)
|
||||
|
||||
return notifyInternalSafely({
|
||||
@@ -174,7 +223,7 @@ export function notifyKuaishouCloudConsumeFailed({
|
||||
shopId = '',
|
||||
shopName = '',
|
||||
errorMessage = '',
|
||||
}: JsonObject = {}) {
|
||||
}: KuaishouCloudConsumeFailedPayload = {}) {
|
||||
const taskRecord = toRecord(task)
|
||||
const orderRecord = toRecord(order)
|
||||
|
||||
@@ -196,7 +245,7 @@ export function notifyTaskAutoManualReview({
|
||||
task = {},
|
||||
reason = '',
|
||||
source = '',
|
||||
}: JsonObject = {}) {
|
||||
}: TaskNotificationPayload = {}) {
|
||||
const taskRecord = toRecord(task)
|
||||
|
||||
return notifyInternalSafely({
|
||||
@@ -216,7 +265,7 @@ export function notifyClaimRedeemNeedsAttention({
|
||||
order = {},
|
||||
status = '',
|
||||
errorMessage = '',
|
||||
}: JsonObject = {}) {
|
||||
}: ClaimRedeemNeedsAttentionPayload = {}) {
|
||||
const taskRecord = toRecord(task)
|
||||
const orderRecord = toRecord(order)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createHttpError } from '../../utils/http.js'
|
||||
const DEFAULT_WPUSH_ENDPOINT = 'https://api.wpush.cn/api/v1/send'
|
||||
const DEFAULT_TIMEOUT_MS = 10000
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type JsonObject = Record<string, unknown>
|
||||
type NodeHttpResponse = {
|
||||
ok: boolean
|
||||
status: number
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { runtimeConfig } from '../../config/runtime.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import type { RuntimeConfig } from '../../types/runtime-config.js'
|
||||
|
||||
export const OPEN_91_PROVIDER = '91kaquan'
|
||||
export const OPEN_91_PLATFORM = 'kuaishou'
|
||||
@@ -7,10 +8,13 @@ export const OPEN_91_SUCCESS_MESSAGE = '接口调用成功'
|
||||
export const OPEN_91_DEFAULT_FAIL_CODE = 1204
|
||||
export const OPEN_91_COST_EXCEED_FAIL_CODE = 1220
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type Open91RuntimeConfig = RuntimeConfig['platforms']['ninetyone']
|
||||
|
||||
export function getOpen91Config() {
|
||||
const config = (runtimeConfig.platforms?.ninetyone || {}) as JsonObject
|
||||
export function getOpen91Config(overrides: Partial<Open91RuntimeConfig> = {}) {
|
||||
const config = {
|
||||
...(runtimeConfig.platforms?.ninetyone || {}),
|
||||
...overrides,
|
||||
}
|
||||
|
||||
return {
|
||||
userId: String(config.userId || '').trim(),
|
||||
|
||||
@@ -9,6 +9,7 @@ import { syncDeliveryTasksForOrder } from './delivery-task-service.js'
|
||||
import { resolveOrderItemForFulfillment } from './product-match-service.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import { logIntegration } from '../../utils/logger.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import type { OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js'
|
||||
|
||||
type SourceOrderItem = {
|
||||
@@ -173,7 +174,10 @@ export async function upsertOrderFromSource(
|
||||
})
|
||||
|
||||
if (!order) {
|
||||
throw new Error('订单写入失败')
|
||||
throw createHttpError('订单写入失败', {
|
||||
statusCode: 500,
|
||||
errorCode: 'order_write_failed',
|
||||
})
|
||||
}
|
||||
|
||||
const orderItems = await replaceOrderItems(
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { PROJECT_ROOT } from '../../../config/runtime.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
|
||||
const CLOUDTENTACLES_SESSION_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-session.json')
|
||||
|
||||
@@ -58,7 +59,10 @@ export function saveCloudtentaclesSessionState(rawValue: unknown) {
|
||||
export function saveCloudtentaclesSessionStateByKey(sourceKey: unknown, rawValue: unknown) {
|
||||
const key = String(sourceKey || '').trim()
|
||||
if (!key) {
|
||||
throw new Error('saveCloudtentaclesSessionStateByKey: sourceKey is required')
|
||||
throw createHttpError('cloudtentacles sourceKey 不能为空', {
|
||||
statusCode: 400,
|
||||
errorCode: 'cloudtentacles_source_key_required',
|
||||
})
|
||||
}
|
||||
|
||||
const states = loadCloudtentaclesSessionStatesFromFile()
|
||||
@@ -75,7 +79,10 @@ export function saveCloudtentaclesSessionStateByKey(sourceKey: unknown, rawValue
|
||||
export function deleteCloudtentaclesSessionStateByKey(sourceKey: unknown) {
|
||||
const key = String(sourceKey || '').trim()
|
||||
if (!key) {
|
||||
throw new Error('deleteCloudtentaclesSessionStateByKey: sourceKey is required')
|
||||
throw createHttpError('cloudtentacles sourceKey 不能为空', {
|
||||
statusCode: 400,
|
||||
errorCode: 'cloudtentacles_source_key_required',
|
||||
})
|
||||
}
|
||||
|
||||
const states = loadCloudtentaclesSessionStatesFromFile()
|
||||
@@ -99,7 +106,10 @@ export function clearCloudtentaclesSessionState() {
|
||||
export function clearCloudtentaclesSessionStateByKey(sourceKey: unknown) {
|
||||
const key = String(sourceKey || '').trim()
|
||||
if (!key) {
|
||||
throw new Error('clearCloudtentaclesSessionStateByKey: sourceKey is required')
|
||||
throw createHttpError('cloudtentacles sourceKey 不能为空', {
|
||||
statusCode: 400,
|
||||
errorCode: 'cloudtentacles_source_key_required',
|
||||
})
|
||||
}
|
||||
|
||||
const cleared = createDefaultCloudtentaclesSessionState()
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { PROJECT_ROOT } from '../../../config/runtime.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
|
||||
const CLOUDTENTACLES_SOURCES_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-sources.json')
|
||||
|
||||
@@ -63,7 +64,10 @@ export function saveCloudtentaclesSourcesList(rawValue: unknown) {
|
||||
export function saveCloudtentaclesSourceByKey(sourceKey: unknown, data: JsonObject = {}) {
|
||||
const key = String(sourceKey || '').trim()
|
||||
if (!key) {
|
||||
throw new Error('saveCloudtentaclesSourceByKey: sourceKey is required')
|
||||
throw createHttpError('cloudtentacles sourceKey 不能为空', {
|
||||
statusCode: 400,
|
||||
errorCode: 'cloudtentacles_source_key_required',
|
||||
})
|
||||
}
|
||||
|
||||
const config = loadCloudtentaclesSourcesConfigFromFile()
|
||||
@@ -87,10 +91,16 @@ export function saveCloudtentaclesSourceByKey(sourceKey: unknown, data: JsonObje
|
||||
export function deleteCloudtentaclesSourceByKey(sourceKey: unknown) {
|
||||
const key = String(sourceKey || '').trim()
|
||||
if (!key) {
|
||||
throw new Error('deleteCloudtentaclesSourceByKey: sourceKey is required')
|
||||
throw createHttpError('cloudtentacles sourceKey 不能为空', {
|
||||
statusCode: 400,
|
||||
errorCode: 'cloudtentacles_source_key_required',
|
||||
})
|
||||
}
|
||||
if (key === 'default') {
|
||||
throw new Error('deleteCloudtentaclesSourceByKey: cannot delete the default source')
|
||||
throw createHttpError('不能删除默认 cloudtentacles 账号', {
|
||||
statusCode: 409,
|
||||
errorCode: 'cloudtentacles_default_source_forbidden',
|
||||
})
|
||||
}
|
||||
|
||||
const config = loadCloudtentaclesSourcesConfigFromFile()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { logError, logInfo, logWarn } from '../../utils/logger.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import {
|
||||
getCloudtentaclesHealthJob,
|
||||
getScheduledJobsConfig,
|
||||
@@ -53,7 +54,10 @@ export async function runScheduledJobNow(jobId: unknown) {
|
||||
const job = config.jobs.find((item) => String(item.id || '').trim() === String(jobId || '').trim())
|
||||
|
||||
if (!job) {
|
||||
throw new Error('定时任务不存在')
|
||||
throw createHttpError('定时任务不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'scheduled_job_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
const currentTimer = timers.get(job.id)
|
||||
@@ -171,7 +175,10 @@ function dispatchJob(job: JsonObject) {
|
||||
return runCloudtentaclesHealthJob(job)
|
||||
}
|
||||
|
||||
throw new Error(`不支持的定时任务类型:${job.type}`)
|
||||
throw createHttpError(`不支持的定时任务类型:${job.type}`, {
|
||||
statusCode: 400,
|
||||
errorCode: 'unsupported_scheduled_job_type',
|
||||
})
|
||||
}
|
||||
|
||||
function updateJobState(jobId: string, patch: JsonObject) {
|
||||
|
||||
Reference in New Issue
Block a user