[Snow Team] express-types: auto-commit work
This commit is contained in:
Generated
+89
@@ -13,6 +13,7 @@
|
|||||||
"playwright": "1.42.1"
|
"playwright": "1.42.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/express": "^5.0.6",
|
||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"@types/pg": "^8.20.0",
|
"@types/pg": "^8.20.0",
|
||||||
"tsx": "^4.22.3",
|
"tsx": "^4.22.3",
|
||||||
@@ -461,6 +462,59 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/body-parser": {
|
||||||
|
"version": "1.19.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||||
|
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/connect": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/connect": {
|
||||||
|
"version": "3.4.38",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||||
|
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/express": {
|
||||||
|
"version": "5.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
|
||||||
|
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/body-parser": "*",
|
||||||
|
"@types/express-serve-static-core": "^5.0.0",
|
||||||
|
"@types/serve-static": "^2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/express-serve-static-core": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/qs": "*",
|
||||||
|
"@types/range-parser": "*",
|
||||||
|
"@types/send": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/http-errors": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "25.6.0",
|
"version": "25.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
||||||
@@ -483,6 +537,41 @@
|
|||||||
"pg-types": "^2.2.0"
|
"pg-types": "^2.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/qs": {
|
||||||
|
"version": "6.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz",
|
||||||
|
"integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/range-parser": {
|
||||||
|
"version": "1.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||||
|
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/send": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/serve-static": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/http-errors": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"playwright": "1.42.1"
|
"playwright": "1.42.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/express": "^5.0.6",
|
||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"@types/pg": "^8.20.0",
|
"@types/pg": "^8.20.0",
|
||||||
"tsx": "^4.22.3",
|
"tsx": "^4.22.3",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { Request, Response, NextFunction } from "express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ export function createApp({
|
|||||||
app.use("/api/v1/claim", claimsRouter);
|
app.use("/api/v1/claim", claimsRouter);
|
||||||
app.use("/api/v1/admin", adminRouter);
|
app.use("/api/v1/admin", adminRouter);
|
||||||
|
|
||||||
app.use((err, _req, res, _next) => {
|
app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => {
|
||||||
sendRouteError(res, err, "服务内部错误", "[global]");
|
sendRouteError(res, err, "服务内部错误", "[global]");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
|
import type { Request, Response, NextFunction } from "express";
|
||||||
import { createRequestId, logInfo } from "../utils/logger.js";
|
import { createRequestId, logInfo } from "../utils/logger.js";
|
||||||
|
|
||||||
export function accessLogMiddleware(req, res, next) {
|
export function accessLogMiddleware(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): void {
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
const requestId = resolveRequestId(req);
|
const requestId = resolveRequestId(req);
|
||||||
|
|
||||||
@@ -32,12 +37,15 @@ export function accessLogMiddleware(req, res, next) {
|
|||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveRequestId(req) {
|
function resolveRequestId(req: Request): string {
|
||||||
const fromHeader = String(req.headers["x-request-id"] || "").trim();
|
const fromHeader = String(req.headers["x-request-id"] || "").trim();
|
||||||
return fromHeader || createRequestId("req");
|
return fromHeader || createRequestId("req");
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldSkipAccessLog(originalUrl, statusCode) {
|
function shouldSkipAccessLog(originalUrl: string, statusCode: number): boolean {
|
||||||
const pathname = String(originalUrl || "").split("?")[0];
|
const pathname = String(originalUrl || "").split("?")[0];
|
||||||
return ["/health", "/health/live", "/health/ready"].includes(pathname) && Number(statusCode) < 400;
|
return (
|
||||||
|
["/health", "/health/live", "/health/ready"].includes(pathname) &&
|
||||||
|
Number(statusCode) < 400
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
export function corsMiddleware(req, res, next) {
|
import type { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
|
export function corsMiddleware(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): void {
|
||||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||||
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||||
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
|
res.setHeader(
|
||||||
|
"Access-Control-Allow-Methods",
|
||||||
|
"GET, POST, PUT, PATCH, DELETE, OPTIONS"
|
||||||
|
);
|
||||||
|
|
||||||
if (req.method === "OPTIONS") {
|
if (req.method === "OPTIONS") {
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
|
|||||||
@@ -1,23 +1,10 @@
|
|||||||
|
import type { Request, Response, NextFunction } from 'express'
|
||||||
import { requireAdminRole, verifyAdminSessionToken } from '../../services/admin/admin-auth-service.js'
|
import { requireAdminRole, verifyAdminSessionToken } from '../../services/admin/admin-auth-service.js'
|
||||||
import { writeAdminAuditLog } from '../../services/admin/admin-audit-service.js'
|
import { writeAdminAuditLog } from '../../services/admin/admin-audit-service.js'
|
||||||
import { buildSuccessPayload, createHttpError, sendRouteError } from '../../utils/http.js'
|
import { buildSuccessPayload, createHttpError, sendRouteError } from '../../utils/http.js'
|
||||||
import { logWarn } from '../../utils/logger.js'
|
import { logWarn } from '../../utils/logger.js'
|
||||||
import type { AdminSession } from '../../services/admin/admin-auth-service.js'
|
import type { AdminSession } from '../../services/admin/admin-auth-service.js'
|
||||||
|
|
||||||
type NextFunction = (error?: unknown) => void
|
|
||||||
type ExpressResponse = {
|
|
||||||
json: (body: unknown) => unknown
|
|
||||||
sendFile: (path: string) => unknown
|
|
||||||
}
|
|
||||||
export type AdminExpressRequest = {
|
|
||||||
headers: { authorization?: string | string[] }
|
|
||||||
body?: any
|
|
||||||
params?: Record<string, string | undefined>
|
|
||||||
query?: Record<string, unknown>
|
|
||||||
adminSession?: AdminSession | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
type AdminAuditPayload = {
|
type AdminAuditPayload = {
|
||||||
action?: string
|
action?: string
|
||||||
targetType?: string
|
targetType?: string
|
||||||
@@ -29,7 +16,7 @@ type AdminJsonHandlerOptions = {
|
|||||||
successMessage?: string
|
successMessage?: string
|
||||||
errorMessage?: string
|
errorMessage?: string
|
||||||
scope?: string
|
scope?: string
|
||||||
audit?: (req: AdminExpressRequest, data: unknown) => AdminAuditPayload | null | undefined
|
audit?: (req: Request, data: unknown) => AdminAuditPayload | null | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminFileHandlerOptions = {
|
type AdminFileHandlerOptions = {
|
||||||
@@ -38,10 +25,10 @@ type AdminFileHandlerOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createJsonHandler(
|
export function createJsonHandler(
|
||||||
action: (req: AdminExpressRequest, res: ExpressResponse) => unknown | Promise<unknown>,
|
action: (req: Request, res: Response) => unknown | Promise<unknown>,
|
||||||
{ successMessage = 'ok', errorMessage, scope, audit }: AdminJsonHandlerOptions = {},
|
{ successMessage = 'ok', errorMessage, scope, audit }: AdminJsonHandlerOptions = {},
|
||||||
) {
|
) {
|
||||||
return async (req, res) => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const data = await action(req, res)
|
const data = await action(req, res)
|
||||||
await recordAdminAudit(req.adminSession, req, data, audit)
|
await recordAdminAudit(req.adminSession, req, data, audit)
|
||||||
@@ -53,10 +40,10 @@ export function createJsonHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createFileHandler(
|
export function createFileHandler(
|
||||||
action: (req: AdminExpressRequest, res: ExpressResponse) => string | Promise<string>,
|
action: (req: Request, res: Response) => string | Promise<string>,
|
||||||
{ errorMessage, scope }: AdminFileHandlerOptions = {},
|
{ errorMessage, scope }: AdminFileHandlerOptions = {},
|
||||||
) {
|
) {
|
||||||
return async (req, res) => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const filePath = await action(req, res)
|
const filePath = await action(req, res)
|
||||||
res.sendFile(filePath)
|
res.sendFile(filePath)
|
||||||
@@ -66,7 +53,7 @@ export function createFileHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requireAdminSession(req: AdminExpressRequest, res: ExpressResponse, next: NextFunction) {
|
export async function requireAdminSession(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
try {
|
try {
|
||||||
req.adminSession = await verifyAdminSessionToken(extractBearerToken(req))
|
req.adminSession = await verifyAdminSessionToken(extractBearerToken(req))
|
||||||
next()
|
next()
|
||||||
@@ -76,7 +63,7 @@ export async function requireAdminSession(req: AdminExpressRequest, res: Express
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function requireAdminRoles(allowedRoles: string[]) {
|
export function requireAdminRoles(allowedRoles: string[]) {
|
||||||
return (req, res, next) => {
|
return (req: Request, res: Response, next: NextFunction): void => {
|
||||||
try {
|
try {
|
||||||
requireAdminRole(req.adminSession, allowedRoles)
|
requireAdminRole(req.adminSession, allowedRoles)
|
||||||
next()
|
next()
|
||||||
@@ -88,7 +75,7 @@ export function requireAdminRoles(allowedRoles: string[]) {
|
|||||||
|
|
||||||
async function recordAdminAudit(
|
async function recordAdminAudit(
|
||||||
session: AdminSession | null | undefined,
|
session: AdminSession | null | undefined,
|
||||||
req: AdminExpressRequest,
|
req: Request,
|
||||||
data: unknown,
|
data: unknown,
|
||||||
audit: AdminJsonHandlerOptions['audit'],
|
audit: AdminJsonHandlerOptions['audit'],
|
||||||
) {
|
) {
|
||||||
@@ -114,7 +101,7 @@ async function recordAdminAudit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractBearerToken(req: AdminExpressRequest) {
|
export function extractBearerToken(req: Request): string {
|
||||||
const authorization = String(req.headers.authorization || '').trim()
|
const authorization = String(req.headers.authorization || '').trim()
|
||||||
const matched = authorization.match(/^Bearer\s+(.+)$/i)
|
const matched = authorization.match(/^Bearer\s+(.+)$/i)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Router } from 'express'
|
import { Router } from "express";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
closeAdminTask,
|
closeAdminTask,
|
||||||
@@ -14,116 +14,125 @@ import {
|
|||||||
releaseAdminTaskInventory,
|
releaseAdminTaskInventory,
|
||||||
returnNumberAdminTaskKuaishouCloudFulfillment,
|
returnNumberAdminTaskKuaishouCloudFulfillment,
|
||||||
retryAdminTask,
|
retryAdminTask,
|
||||||
} from '../../services/admin/admin-write-service.js'
|
} from "../../services/admin/admin-write-service.js";
|
||||||
import {
|
import {
|
||||||
getAdminTaskDetail,
|
getAdminTaskDetail,
|
||||||
getAdminTaskScreenshotPath,
|
getAdminTaskScreenshotPath,
|
||||||
getAdminTasks,
|
getAdminTasks,
|
||||||
} from '../../services/admin/admin-read-service.js'
|
} from "../../services/admin/admin-read-service.js";
|
||||||
import { createFileHandler, createJsonHandler, requireAdminRoles } from './shared.js'
|
import {
|
||||||
import type { AdminExpressRequest } from './shared.js'
|
createFileHandler,
|
||||||
|
createJsonHandler,
|
||||||
|
requireAdminRoles,
|
||||||
|
} from "./shared.js";
|
||||||
|
import type { Request } from "express";
|
||||||
import type {
|
import type {
|
||||||
AdminTaskKuaishouCloudDispatchRouteBody,
|
AdminTaskKuaishouCloudDispatchRouteBody,
|
||||||
AdminTaskManualDispatchRouteBody,
|
AdminTaskManualDispatchRouteBody,
|
||||||
AdminTaskRouteParams,
|
AdminTaskRouteParams,
|
||||||
AdminTaskRouteQuery,
|
AdminTaskRouteQuery,
|
||||||
} from '../../types/admin-route-inputs.js'
|
} from "../../types/admin-route-inputs.js";
|
||||||
import type {
|
import type {
|
||||||
AdminTaskActionResponse,
|
AdminTaskActionResponse,
|
||||||
AdminTaskBindingReleaseResponse,
|
AdminTaskBindingReleaseResponse,
|
||||||
AdminTaskManualDispatchResponse,
|
AdminTaskManualDispatchResponse,
|
||||||
} from '../../types/admin-write-models.js'
|
} from "../../types/admin-write-models.js";
|
||||||
|
|
||||||
const router = Router()
|
const router = Router();
|
||||||
|
|
||||||
function getTaskId(req: AdminExpressRequest): string | undefined {
|
function getTaskId(req: Request): string | undefined {
|
||||||
return (req.params as AdminTaskRouteParams).taskId
|
return (req.params as AdminTaskRouteParams).taskId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBindingId(req: AdminExpressRequest): string | undefined {
|
function getBindingId(req: Request): string | undefined {
|
||||||
return (req.params as AdminTaskRouteParams).bindingId
|
return (req.params as AdminTaskRouteParams).bindingId;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get('/tasks', createJsonHandler(
|
router.get(
|
||||||
(req) => getAdminTasks(
|
"/tasks",
|
||||||
req.query as AdminTaskRouteQuery,
|
createJsonHandler(
|
||||||
req.adminSession || null,
|
(req) =>
|
||||||
),
|
getAdminTasks(req.query as AdminTaskRouteQuery, req.adminSession || null),
|
||||||
{
|
{
|
||||||
successMessage: 'ok',
|
successMessage: "ok",
|
||||||
errorMessage: '读取任务列表失败',
|
errorMessage: "读取任务列表失败",
|
||||||
scope: '[admin/tasks]',
|
scope: "[admin/tasks]",
|
||||||
},
|
}
|
||||||
))
|
)
|
||||||
|
);
|
||||||
|
|
||||||
router.get('/tasks/:taskId', createJsonHandler(
|
router.get(
|
||||||
(req) => getAdminTaskDetail(
|
"/tasks/:taskId",
|
||||||
getTaskId(req),
|
createJsonHandler(
|
||||||
req.adminSession || null,
|
(req) => getAdminTaskDetail(getTaskId(req), req.adminSession || null),
|
||||||
),
|
|
||||||
{
|
{
|
||||||
successMessage: 'ok',
|
successMessage: "ok",
|
||||||
errorMessage: '读取任务详情失败',
|
errorMessage: "读取任务详情失败",
|
||||||
scope: '[admin/tasks/:taskId]',
|
scope: "[admin/tasks/:taskId]",
|
||||||
},
|
}
|
||||||
))
|
)
|
||||||
|
);
|
||||||
|
|
||||||
router.get('/tasks/:taskId/screenshot', createFileHandler(
|
router.get(
|
||||||
(req) => getAdminTaskScreenshotPath(
|
"/tasks/:taskId/screenshot",
|
||||||
getTaskId(req),
|
createFileHandler(
|
||||||
req.adminSession || null,
|
(req) =>
|
||||||
),
|
getAdminTaskScreenshotPath(getTaskId(req), req.adminSession || null),
|
||||||
{
|
{
|
||||||
errorMessage: '读取任务截图失败',
|
errorMessage: "读取任务截图失败",
|
||||||
scope: '[admin/tasks/:taskId/screenshot]',
|
scope: "[admin/tasks/:taskId/screenshot]",
|
||||||
},
|
}
|
||||||
))
|
)
|
||||||
|
);
|
||||||
|
|
||||||
router.post('/tasks/:taskId/retry', requireAdminRoles(['admin', 'operator']), createJsonHandler(
|
router.post(
|
||||||
(req) => retryAdminTask(getTaskId(req)),
|
"/tasks/:taskId/retry",
|
||||||
{
|
requireAdminRoles(["admin", "operator"]),
|
||||||
successMessage: '任务已重试',
|
createJsonHandler((req) => retryAdminTask(getTaskId(req)), {
|
||||||
errorMessage: '重试任务失败',
|
successMessage: "任务已重试",
|
||||||
scope: '[admin/tasks/:taskId/retry]',
|
errorMessage: "重试任务失败",
|
||||||
},
|
scope: "[admin/tasks/:taskId/retry]",
|
||||||
))
|
})
|
||||||
|
);
|
||||||
|
|
||||||
router.post('/tasks/:taskId/release-inventory', requireAdminRoles(['admin']), createJsonHandler(
|
router.post(
|
||||||
(req) => releaseAdminTaskInventory(getTaskId(req)),
|
"/tasks/:taskId/release-inventory",
|
||||||
{
|
requireAdminRoles(["admin"]),
|
||||||
successMessage: '库存凭据已释放',
|
createJsonHandler((req) => releaseAdminTaskInventory(getTaskId(req)), {
|
||||||
errorMessage: '释放库存凭据失败',
|
successMessage: "库存凭据已释放",
|
||||||
scope: '[admin/tasks/:taskId/release-inventory]',
|
errorMessage: "释放库存凭据失败",
|
||||||
|
scope: "[admin/tasks/:taskId/release-inventory]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskActionResponse
|
const result = data as AdminTaskActionResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_release_inventory',
|
action: "task_release_inventory",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
taskNo: result.task.taskNo,
|
taskNo: result.task.taskNo,
|
||||||
inventoryItemId: result.task.inventoryItemId,
|
inventoryItemId: result.task.inventoryItemId,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
))
|
);
|
||||||
|
|
||||||
router.post('/tasks/:taskId/inventory-bindings/:bindingId/release', requireAdminRoles(['admin']), createJsonHandler(
|
router.post(
|
||||||
(req) => releaseAdminTaskInventoryBinding(
|
"/tasks/:taskId/inventory-bindings/:bindingId/release",
|
||||||
getTaskId(req),
|
requireAdminRoles(["admin"]),
|
||||||
getBindingId(req),
|
createJsonHandler(
|
||||||
),
|
(req) =>
|
||||||
|
releaseAdminTaskInventoryBinding(getTaskId(req), getBindingId(req)),
|
||||||
{
|
{
|
||||||
successMessage: '库存绑定已释放',
|
successMessage: "库存绑定已释放",
|
||||||
errorMessage: '释放库存绑定失败',
|
errorMessage: "释放库存绑定失败",
|
||||||
scope: '[admin/tasks/:taskId/inventory-bindings/:bindingId/release]',
|
scope: "[admin/tasks/:taskId/inventory-bindings/:bindingId/release]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskBindingReleaseResponse
|
const result = data as AdminTaskBindingReleaseResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_release_inventory_binding',
|
action: "task_release_inventory_binding",
|
||||||
targetType: 'task_inventory_binding',
|
targetType: "task_inventory_binding",
|
||||||
targetId: String(getBindingId(req)),
|
targetId: String(getBindingId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
@@ -131,75 +140,80 @@ router.post('/tasks/:taskId/inventory-bindings/:bindingId/release', requireAdmin
|
|||||||
bindingId: result.bindingId,
|
bindingId: result.bindingId,
|
||||||
inventoryItemId: result.inventoryItemId,
|
inventoryItemId: result.inventoryItemId,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
},
|
);
|
||||||
))
|
|
||||||
|
|
||||||
router.post('/tasks/:taskId/regenerate-claim-link', requireAdminRoles(['admin', 'operator', 'support']), createJsonHandler(
|
router.post(
|
||||||
(req) => regenerateAdminTaskClaimLink(
|
"/tasks/:taskId/regenerate-claim-link",
|
||||||
getTaskId(req),
|
requireAdminRoles(["admin", "operator", "support"]),
|
||||||
req.adminSession || null,
|
createJsonHandler(
|
||||||
),
|
(req) =>
|
||||||
|
regenerateAdminTaskClaimLink(getTaskId(req), req.adminSession || null),
|
||||||
{
|
{
|
||||||
successMessage: '领取链接已重新生成',
|
successMessage: "领取链接已重新生成",
|
||||||
errorMessage: '重新生成领取链接失败',
|
errorMessage: "重新生成领取链接失败",
|
||||||
scope: '[admin/tasks/:taskId/regenerate-claim-link]',
|
scope: "[admin/tasks/:taskId/regenerate-claim-link]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskActionResponse
|
const result = data as AdminTaskActionResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_regenerate_claim_link',
|
action: "task_regenerate_claim_link",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
taskNo: result.task.taskNo,
|
taskNo: result.task.taskNo,
|
||||||
primaryClaimTokenId: result.task.primaryClaimTokenId,
|
primaryClaimTokenId: result.task.primaryClaimTokenId,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
},
|
);
|
||||||
))
|
|
||||||
|
|
||||||
router.post('/tasks/:taskId/support-confirm-role', requireAdminRoles(['admin', 'operator', 'support']), createJsonHandler(
|
router.post(
|
||||||
(req) => confirmAdminTaskAssistedRole(
|
"/tasks/:taskId/support-confirm-role",
|
||||||
getTaskId(req),
|
requireAdminRoles(["admin", "operator", "support"]),
|
||||||
req.adminSession || null,
|
createJsonHandler(
|
||||||
),
|
(req) =>
|
||||||
|
confirmAdminTaskAssistedRole(getTaskId(req), req.adminSession || null),
|
||||||
{
|
{
|
||||||
successMessage: '角色已确认',
|
successMessage: "角色已确认",
|
||||||
errorMessage: '客服确认角色失败',
|
errorMessage: "客服确认角色失败",
|
||||||
scope: '[admin/tasks/:taskId/support-confirm-role]',
|
scope: "[admin/tasks/:taskId/support-confirm-role]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskActionResponse
|
const result = data as AdminTaskActionResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_support_confirm_role',
|
action: "task_support_confirm_role",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
taskNo: result.task.taskNo,
|
taskNo: result.task.taskNo,
|
||||||
status: result.task.status,
|
status: result.task.status,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
},
|
);
|
||||||
))
|
|
||||||
|
|
||||||
router.post('/tasks/:taskId/support-redeem', requireAdminRoles(['admin', 'operator', 'support']), createJsonHandler(
|
router.post(
|
||||||
(req) => redeemAdminTaskAssisted(
|
"/tasks/:taskId/support-redeem",
|
||||||
getTaskId(req),
|
requireAdminRoles(["admin", "operator", "support"]),
|
||||||
req.adminSession || null,
|
createJsonHandler(
|
||||||
),
|
(req) => redeemAdminTaskAssisted(getTaskId(req), req.adminSession || null),
|
||||||
{
|
{
|
||||||
successMessage: '兑换任务已启动',
|
successMessage: "兑换任务已启动",
|
||||||
errorMessage: '客服发起兑换失败',
|
errorMessage: "客服发起兑换失败",
|
||||||
scope: '[admin/tasks/:taskId/support-redeem]',
|
scope: "[admin/tasks/:taskId/support-redeem]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskActionResponse
|
const result = data as AdminTaskActionResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_support_redeem',
|
action: "task_support_redeem",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
@@ -207,70 +221,77 @@ router.post('/tasks/:taskId/support-redeem', requireAdminRoles(['admin', 'operat
|
|||||||
status: result.task.status,
|
status: result.task.status,
|
||||||
deliveryStatus: result.task.deliveryStatus,
|
deliveryStatus: result.task.deliveryStatus,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
},
|
);
|
||||||
))
|
|
||||||
|
|
||||||
router.post('/tasks/:taskId/close', requireAdminRoles(['admin', 'operator', 'support']), createJsonHandler(
|
router.post(
|
||||||
(req) => closeAdminTask(getTaskId(req)),
|
"/tasks/:taskId/close",
|
||||||
{
|
requireAdminRoles(["admin", "operator", "support"]),
|
||||||
successMessage: '任务已关闭',
|
createJsonHandler((req) => closeAdminTask(getTaskId(req)), {
|
||||||
errorMessage: '关闭任务失败',
|
successMessage: "任务已关闭",
|
||||||
scope: '[admin/tasks/:taskId/close]',
|
errorMessage: "关闭任务失败",
|
||||||
|
scope: "[admin/tasks/:taskId/close]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskActionResponse
|
const result = data as AdminTaskActionResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_closed',
|
action: "task_closed",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
taskNo: result.task.taskNo,
|
taskNo: result.task.taskNo,
|
||||||
status: result.task.status,
|
status: result.task.status,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
))
|
);
|
||||||
|
|
||||||
router.post('/tasks/:taskId/mark-manual-review', requireAdminRoles(['admin', 'operator']), createJsonHandler(
|
router.post(
|
||||||
(req) => markAdminTaskManualReview(getTaskId(req)),
|
"/tasks/:taskId/mark-manual-review",
|
||||||
{
|
requireAdminRoles(["admin", "operator"]),
|
||||||
successMessage: '任务已转人工处理',
|
createJsonHandler((req) => markAdminTaskManualReview(getTaskId(req)), {
|
||||||
errorMessage: '标记人工处理失败',
|
successMessage: "任务已转人工处理",
|
||||||
scope: '[admin/tasks/:taskId/mark-manual-review]',
|
errorMessage: "标记人工处理失败",
|
||||||
|
scope: "[admin/tasks/:taskId/mark-manual-review]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskActionResponse
|
const result = data as AdminTaskActionResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_mark_manual_review',
|
action: "task_mark_manual_review",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
taskNo: result.task.taskNo,
|
taskNo: result.task.taskNo,
|
||||||
status: result.task.status,
|
status: result.task.status,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
))
|
);
|
||||||
|
|
||||||
router.post('/tasks/:taskId/complete-manual-dispatch', requireAdminRoles(['admin', 'operator']), createJsonHandler(
|
router.post(
|
||||||
(req) => completeAdminTaskManualDispatch(
|
"/tasks/:taskId/complete-manual-dispatch",
|
||||||
|
requireAdminRoles(["admin", "operator"]),
|
||||||
|
createJsonHandler(
|
||||||
|
(req) =>
|
||||||
|
completeAdminTaskManualDispatch(
|
||||||
getTaskId(req),
|
getTaskId(req),
|
||||||
req.body as AdminTaskManualDispatchRouteBody,
|
req.body as AdminTaskManualDispatchRouteBody,
|
||||||
req.adminSession || null,
|
req.adminSession || null
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
successMessage: '人工履约结果已回写',
|
successMessage: "人工履约结果已回写",
|
||||||
errorMessage: '回写人工履约结果失败',
|
errorMessage: "回写人工履约结果失败",
|
||||||
scope: '[admin/tasks/:taskId/complete-manual-dispatch]',
|
scope: "[admin/tasks/:taskId/complete-manual-dispatch]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskManualDispatchResponse
|
const result = data as AdminTaskManualDispatchResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_complete_manual_dispatch',
|
action: "task_complete_manual_dispatch",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
@@ -279,113 +300,134 @@ router.post('/tasks/:taskId/complete-manual-dispatch', requireAdminRoles(['admin
|
|||||||
deliveryStatus: result.task.deliveryStatus,
|
deliveryStatus: result.task.deliveryStatus,
|
||||||
resultCode: result.task.resultCode,
|
resultCode: result.task.resultCode,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
},
|
);
|
||||||
))
|
|
||||||
|
|
||||||
router.post('/tasks/:taskId/kuaishou-cloud/prepare', requireAdminRoles(['admin', 'operator']), createJsonHandler(
|
router.post(
|
||||||
(req) => prepareAdminTaskKuaishouCloudFulfillment(
|
"/tasks/:taskId/kuaishou-cloud/prepare",
|
||||||
|
requireAdminRoles(["admin", "operator"]),
|
||||||
|
createJsonHandler(
|
||||||
|
(req) =>
|
||||||
|
prepareAdminTaskKuaishouCloudFulfillment(
|
||||||
getTaskId(req),
|
getTaskId(req),
|
||||||
req.adminSession || null,
|
req.adminSession || null
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
successMessage: '绑定资源已准备完成',
|
successMessage: "绑定资源已准备完成",
|
||||||
errorMessage: '准备绑定资源失败',
|
errorMessage: "准备绑定资源失败",
|
||||||
scope: '[admin/tasks/:taskId/kuaishou-cloud/prepare]',
|
scope: "[admin/tasks/:taskId/kuaishou-cloud/prepare]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskActionResponse
|
const result = data as AdminTaskActionResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_kuaishou_cloud_prepare',
|
action: "task_kuaishou_cloud_prepare",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
taskNo: result.task.taskNo,
|
taskNo: result.task.taskNo,
|
||||||
status: result.task.status,
|
status: result.task.status,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
},
|
);
|
||||||
))
|
|
||||||
|
|
||||||
router.post('/tasks/:taskId/kuaishou-cloud/refresh-role-info', requireAdminRoles(['admin', 'operator', 'support']), createJsonHandler(
|
router.post(
|
||||||
(req) => refreshAdminTaskKuaishouCloudRoleInfo(
|
"/tasks/:taskId/kuaishou-cloud/refresh-role-info",
|
||||||
|
requireAdminRoles(["admin", "operator", "support"]),
|
||||||
|
createJsonHandler(
|
||||||
|
(req) =>
|
||||||
|
refreshAdminTaskKuaishouCloudRoleInfo(
|
||||||
getTaskId(req),
|
getTaskId(req),
|
||||||
req.adminSession || null,
|
req.adminSession || null
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
successMessage: '角色信息已刷新',
|
successMessage: "角色信息已刷新",
|
||||||
errorMessage: '刷新角色信息失败',
|
errorMessage: "刷新角色信息失败",
|
||||||
scope: '[admin/tasks/:taskId/kuaishou-cloud/refresh-role-info]',
|
scope: "[admin/tasks/:taskId/kuaishou-cloud/refresh-role-info]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskActionResponse
|
const result = data as AdminTaskActionResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_kuaishou_cloud_role_info_refreshed',
|
action: "task_kuaishou_cloud_role_info_refreshed",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
taskNo: result.task.taskNo,
|
taskNo: result.task.taskNo,
|
||||||
status: result.task.status,
|
status: result.task.status,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
},
|
);
|
||||||
))
|
|
||||||
|
|
||||||
router.post('/tasks/:taskId/kuaishou-cloud/dispatch', requireAdminRoles(['admin', 'operator']), createJsonHandler(
|
router.post(
|
||||||
(req) => dispatchAdminTaskKuaishouCloudFulfillment(
|
"/tasks/:taskId/kuaishou-cloud/dispatch",
|
||||||
|
requireAdminRoles(["admin", "operator"]),
|
||||||
|
createJsonHandler(
|
||||||
|
(req) =>
|
||||||
|
dispatchAdminTaskKuaishouCloudFulfillment(
|
||||||
getTaskId(req),
|
getTaskId(req),
|
||||||
req.body as AdminTaskKuaishouCloudDispatchRouteBody,
|
req.body as AdminTaskKuaishouCloudDispatchRouteBody,
|
||||||
req.adminSession || null,
|
req.adminSession || null
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
successMessage: '已完成绑定确认并发货',
|
successMessage: "已完成绑定确认并发货",
|
||||||
errorMessage: '执行发货失败',
|
errorMessage: "执行发货失败",
|
||||||
scope: '[admin/tasks/:taskId/kuaishou-cloud/dispatch]',
|
scope: "[admin/tasks/:taskId/kuaishou-cloud/dispatch]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskActionResponse
|
const result = data as AdminTaskActionResponse;
|
||||||
const body = req.body as AdminTaskKuaishouCloudDispatchRouteBody
|
const body = req.body as AdminTaskKuaishouCloudDispatchRouteBody;
|
||||||
return {
|
return {
|
||||||
action: 'task_kuaishou_cloud_dispatch',
|
action: "task_kuaishou_cloud_dispatch",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
taskNo: result.task.taskNo,
|
taskNo: result.task.taskNo,
|
||||||
status: result.task.status,
|
status: result.task.status,
|
||||||
deliveryStatus: result.task.deliveryStatus,
|
deliveryStatus: result.task.deliveryStatus,
|
||||||
ticketCodeProvided: Boolean(String(body.ticketCode || '').trim()),
|
ticketCodeProvided: Boolean(String(body.ticketCode || "").trim()),
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
},
|
);
|
||||||
))
|
|
||||||
|
|
||||||
router.post('/tasks/:taskId/kuaishou-cloud/return-number', requireAdminRoles(['admin', 'operator']), createJsonHandler(
|
router.post(
|
||||||
(req) => returnNumberAdminTaskKuaishouCloudFulfillment(
|
"/tasks/:taskId/kuaishou-cloud/return-number",
|
||||||
|
requireAdminRoles(["admin", "operator"]),
|
||||||
|
createJsonHandler(
|
||||||
|
(req) =>
|
||||||
|
returnNumberAdminTaskKuaishouCloudFulfillment(
|
||||||
getTaskId(req),
|
getTaskId(req),
|
||||||
req.adminSession || null,
|
req.adminSession || null
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
successMessage: '号码已退还',
|
successMessage: "号码已退还",
|
||||||
errorMessage: '退还号码失败',
|
errorMessage: "退还号码失败",
|
||||||
scope: '[admin/tasks/:taskId/kuaishou-cloud/return-number]',
|
scope: "[admin/tasks/:taskId/kuaishou-cloud/return-number]",
|
||||||
audit: (req, data) => {
|
audit: (req, data) => {
|
||||||
const result = data as AdminTaskActionResponse
|
const result = data as AdminTaskActionResponse;
|
||||||
return {
|
return {
|
||||||
action: 'task_kuaishou_cloud_return_number',
|
action: "task_kuaishou_cloud_return_number",
|
||||||
targetType: 'task',
|
targetType: "task",
|
||||||
targetId: String(getTaskId(req)),
|
targetId: String(getTaskId(req)),
|
||||||
data: {
|
data: {
|
||||||
taskId: result.task.taskId,
|
taskId: result.task.taskId,
|
||||||
taskNo: result.task.taskNo,
|
taskNo: result.task.taskNo,
|
||||||
status: result.task.status,
|
status: result.task.status,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
},
|
);
|
||||||
))
|
|
||||||
|
|
||||||
export default router
|
export default router;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ router.post('/users', createJsonHandler(
|
|||||||
))
|
))
|
||||||
|
|
||||||
router.post('/users/:userId/role', createJsonHandler(
|
router.post('/users/:userId/role', createJsonHandler(
|
||||||
(req) => updateManagedAdminUserRole(req.params.userId, req.body, req.adminSession),
|
(req) => updateManagedAdminUserRole(String(req.params.userId), req.body, req.adminSession),
|
||||||
{
|
{
|
||||||
successMessage: '用户角色已更新',
|
successMessage: '用户角色已更新',
|
||||||
errorMessage: '更新用户角色失败',
|
errorMessage: '更新用户角色失败',
|
||||||
@@ -77,7 +77,7 @@ router.post('/users/:userId/role', createJsonHandler(
|
|||||||
))
|
))
|
||||||
|
|
||||||
router.post('/users/:userId/status', createJsonHandler(
|
router.post('/users/:userId/status', createJsonHandler(
|
||||||
(req) => updateManagedAdminUserStatus(req.params.userId, req.body, req.adminSession),
|
(req) => updateManagedAdminUserStatus(String(req.params.userId), req.body, req.adminSession),
|
||||||
{
|
{
|
||||||
successMessage: '用户状态已更新',
|
successMessage: '用户状态已更新',
|
||||||
errorMessage: '更新用户状态失败',
|
errorMessage: '更新用户状态失败',
|
||||||
@@ -98,7 +98,7 @@ router.post('/users/:userId/status', createJsonHandler(
|
|||||||
))
|
))
|
||||||
|
|
||||||
router.post('/users/:userId/inventory-groups', createJsonHandler(
|
router.post('/users/:userId/inventory-groups', createJsonHandler(
|
||||||
(req) => updateManagedAdminUserInventoryGroups(req.params.userId, req.body, req.adminSession),
|
(req) => updateManagedAdminUserInventoryGroups(String(req.params.userId), req.body, req.adminSession),
|
||||||
{
|
{
|
||||||
successMessage: '用户库存组已更新',
|
successMessage: '用户库存组已更新',
|
||||||
errorMessage: '更新用户库存组失败',
|
errorMessage: '更新用户库存组失败',
|
||||||
@@ -119,7 +119,7 @@ router.post('/users/:userId/inventory-groups', createJsonHandler(
|
|||||||
))
|
))
|
||||||
|
|
||||||
router.post('/users/:userId/reset-password', createJsonHandler(
|
router.post('/users/:userId/reset-password', createJsonHandler(
|
||||||
(req) => resetManagedAdminUserPassword(req.params.userId, req.body),
|
(req) => resetManagedAdminUserPassword(String(req.params.userId), req.body),
|
||||||
{
|
{
|
||||||
successMessage: '用户密码已重置',
|
successMessage: '用户密码已重置',
|
||||||
errorMessage: '重置用户密码失败',
|
errorMessage: '重置用户密码失败',
|
||||||
|
|||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
import type { AdminSession } from '../services/admin/admin-auth-service.js'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Express {
|
||||||
|
interface Request {
|
||||||
|
adminSession?: AdminSession | null
|
||||||
|
requestId?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {}
|
||||||
+139
-112
@@ -1,33 +1,34 @@
|
|||||||
import { logError, logWarn } from './logger.js'
|
import type { Request, Response } from "express";
|
||||||
|
import { logError, logWarn } from "./logger.js";
|
||||||
|
|
||||||
const RESPONSE_TIME_ZONE = 'Asia/Shanghai'
|
const RESPONSE_TIME_ZONE = "Asia/Shanghai";
|
||||||
|
|
||||||
export type HttpErrorLike = {
|
export type HttpErrorLike = {
|
||||||
statusCode?: number
|
statusCode?: number;
|
||||||
errorCode?: string
|
errorCode?: string;
|
||||||
cause?: unknown
|
cause?: unknown;
|
||||||
context?: unknown
|
context?: unknown;
|
||||||
[key: string]: unknown
|
[key: string]: unknown;
|
||||||
}
|
};
|
||||||
|
|
||||||
type CreateHttpErrorOptions = {
|
type CreateHttpErrorOptions = {
|
||||||
statusCode?: number
|
statusCode?: number;
|
||||||
errorCode?: string
|
errorCode?: string;
|
||||||
cause?: unknown
|
cause?: unknown;
|
||||||
context?: unknown
|
context?: unknown;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function buildSuccessPayload(data, msg = 'ok') {
|
export function buildSuccessPayload(data: unknown, msg: string = "ok") {
|
||||||
return {
|
return {
|
||||||
code: 0,
|
code: 0,
|
||||||
msg,
|
msg,
|
||||||
time: Math.floor(Date.now() / 1000),
|
time: Math.floor(Date.now() / 1000),
|
||||||
data: normalizeResponseDateTime(data),
|
data: normalizeResponseDateTime(data),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildErrorPayload(error, fallbackMessage) {
|
export function buildErrorPayload(error: unknown, fallbackMessage: string) {
|
||||||
const classification = classifyRouteError(error)
|
const classification = classifyRouteError(error);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 1,
|
code: 1,
|
||||||
@@ -35,214 +36,240 @@ export function buildErrorPayload(error, fallbackMessage) {
|
|||||||
errorCode: classification.errorCode,
|
errorCode: classification.errorCode,
|
||||||
time: Math.floor(Date.now() / 1000),
|
time: Math.floor(Date.now() / 1000),
|
||||||
data: null,
|
data: null,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendRouteError(res, error, fallbackMessage, scope) {
|
export function sendRouteError(
|
||||||
const classification = classifyRouteError(error)
|
res: Response,
|
||||||
const logger = classification.statusCode >= 500 ? logError : logWarn
|
error: unknown,
|
||||||
|
fallbackMessage: string,
|
||||||
|
scope: string
|
||||||
|
): void {
|
||||||
|
const classification = classifyRouteError(error);
|
||||||
|
const logger = classification.statusCode >= 500 ? logError : logWarn;
|
||||||
logger(scope, fallbackMessage, {
|
logger(scope, fallbackMessage, {
|
||||||
statusCode: classification.statusCode,
|
statusCode: classification.statusCode,
|
||||||
errorCode: classification.errorCode,
|
errorCode: classification.errorCode,
|
||||||
error,
|
error,
|
||||||
})
|
});
|
||||||
res.status(classification.statusCode).json(buildErrorPayload(error, fallbackMessage))
|
res
|
||||||
|
.status(classification.statusCode)
|
||||||
|
.json(buildErrorPayload(error, fallbackMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildNotFoundPayload(req) {
|
export function buildNotFoundPayload(req: Request) {
|
||||||
return {
|
return {
|
||||||
code: 1,
|
code: 1,
|
||||||
msg: `未实现接口: ${req.method} ${req.originalUrl}`,
|
msg: `未实现接口: ${req.method} ${req.originalUrl}`,
|
||||||
time: Math.floor(Date.now() / 1000),
|
time: Math.floor(Date.now() / 1000),
|
||||||
data: null,
|
data: null,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createHttpError(message: string, options: CreateHttpErrorOptions = {}): Error & HttpErrorLike {
|
export function createHttpError(
|
||||||
const { statusCode = 500, errorCode = '', cause, context } = options
|
message: string,
|
||||||
const error = new Error(message) as Error & HttpErrorLike
|
options: CreateHttpErrorOptions = {}
|
||||||
error.statusCode = statusCode
|
): Error & HttpErrorLike {
|
||||||
error.errorCode = errorCode || defaultErrorCodeForStatus(statusCode)
|
const { statusCode = 500, errorCode = "", cause, context } = options;
|
||||||
|
const error = new Error(message) as Error & HttpErrorLike;
|
||||||
|
error.statusCode = statusCode;
|
||||||
|
error.errorCode = errorCode || defaultErrorCodeForStatus(statusCode);
|
||||||
|
|
||||||
if (typeof cause !== 'undefined') {
|
if (typeof cause !== "undefined") {
|
||||||
error.cause = cause
|
error.cause = cause;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof context !== 'undefined') {
|
if (typeof context !== "undefined") {
|
||||||
error.context = context
|
error.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
return error
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
function classifyRouteError(error) {
|
function classifyRouteError(error: unknown): {
|
||||||
if (error && typeof error === 'object') {
|
statusCode: number;
|
||||||
const currentError = error as HttpErrorLike
|
errorCode: string;
|
||||||
const statusCode = Number(currentError.statusCode)
|
} {
|
||||||
const errorCode = String(currentError.errorCode || '').trim()
|
if (error && typeof error === "object") {
|
||||||
|
const currentError = error as HttpErrorLike;
|
||||||
|
const statusCode = Number(currentError.statusCode);
|
||||||
|
const errorCode = String(currentError.errorCode || "").trim();
|
||||||
|
|
||||||
if (Number.isInteger(statusCode) && statusCode >= 400 && statusCode < 600) {
|
if (Number.isInteger(statusCode) && statusCode >= 400 && statusCode < 600) {
|
||||||
return {
|
return {
|
||||||
statusCode,
|
statusCode,
|
||||||
errorCode: errorCode || defaultErrorCodeForStatus(statusCode),
|
errorCode: errorCode || defaultErrorCodeForStatus(statusCode),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = error instanceof Error ? error.message : String(error || '')
|
const message = error instanceof Error ? error.message : String(error || "");
|
||||||
|
|
||||||
if (message.includes('缺少') || message.includes('无效') || message.includes('验签失败')) {
|
if (
|
||||||
return { statusCode: 400, errorCode: 'invalid_request' }
|
message.includes("缺少") ||
|
||||||
|
message.includes("无效") ||
|
||||||
|
message.includes("验签失败")
|
||||||
|
) {
|
||||||
|
return { statusCode: 400, errorCode: "invalid_request" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.includes('不存在')) {
|
if (message.includes("不存在")) {
|
||||||
return { statusCode: 404, errorCode: 'not_found' }
|
return { statusCode: 404, errorCode: "not_found" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.includes('已关闭')) {
|
if (message.includes("已关闭")) {
|
||||||
return { statusCode: 410, errorCode: 'gone' }
|
return { statusCode: 410, errorCode: "gone" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.includes('不能') || message.includes('冲突')) {
|
if (message.includes("不能") || message.includes("冲突")) {
|
||||||
return { statusCode: 409, errorCode: 'conflict' }
|
return { statusCode: 409, errorCode: "conflict" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
message.includes('OCR') ||
|
message.includes("OCR") ||
|
||||||
message.includes('uv sync') ||
|
message.includes("uv sync") ||
|
||||||
message.includes('spawn uv ENOENT') ||
|
message.includes("spawn uv ENOENT") ||
|
||||||
message.includes("Executable doesn't exist") ||
|
message.includes("Executable doesn't exist") ||
|
||||||
message.includes('please run the following command to download new browsers')
|
message.includes(
|
||||||
|
"please run the following command to download new browsers"
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return { statusCode: 503, errorCode: 'dependency_unavailable' }
|
return { statusCode: 503, errorCode: "dependency_unavailable" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { statusCode: 500, errorCode: 'internal_error' }
|
return { statusCode: 500, errorCode: "internal_error" };
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultErrorCodeForStatus(statusCode) {
|
function defaultErrorCodeForStatus(statusCode: number): string {
|
||||||
if (statusCode === 400) {
|
if (statusCode === 400) {
|
||||||
return 'invalid_request'
|
return "invalid_request";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusCode === 401) {
|
if (statusCode === 401) {
|
||||||
return 'unauthorized'
|
return "unauthorized";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusCode === 403) {
|
if (statusCode === 403) {
|
||||||
return 'forbidden'
|
return "forbidden";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusCode === 404) {
|
if (statusCode === 404) {
|
||||||
return 'not_found'
|
return "not_found";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusCode === 409) {
|
if (statusCode === 409) {
|
||||||
return 'conflict'
|
return "conflict";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusCode === 410) {
|
if (statusCode === 410) {
|
||||||
return 'gone'
|
return "gone";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusCode === 503) {
|
if (statusCode === 503) {
|
||||||
return 'dependency_unavailable'
|
return "dependency_unavailable";
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'internal_error'
|
return "internal_error";
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeResponseDateTime(value) {
|
function normalizeResponseDateTime(value: unknown): unknown {
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
return formatResponseDateTime(value.toISOString())
|
return formatResponseDateTime(value.toISOString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.map((item) => normalizeResponseDateTime(item))
|
return value.map((item) => normalizeResponseDateTime(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value && typeof value === 'object') {
|
if (value && typeof value === "object") {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(value).map(([key, currentValue]) => [key, normalizeResponseDateTime(currentValue)]),
|
Object.entries(value).map(([key, currentValue]) => [
|
||||||
)
|
key,
|
||||||
|
normalizeResponseDateTime(currentValue),
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isIsoDateTimeString(value)) {
|
if (isIsoDateTimeString(value)) {
|
||||||
return formatResponseDateTime(value)
|
return formatResponseDateTime(value as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIsoDateTimeString(value) {
|
function isIsoDateTimeString(value: unknown): boolean {
|
||||||
return (
|
return (
|
||||||
typeof value === 'string' &&
|
typeof value === "string" &&
|
||||||
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/.test(value.trim())
|
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/.test(
|
||||||
|
value.trim()
|
||||||
)
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatResponseDateTime(value) {
|
function formatResponseDateTime(value: string): string {
|
||||||
const date = new Date(value)
|
const date = new Date(value);
|
||||||
|
|
||||||
if (Number.isNaN(date.getTime())) {
|
if (Number.isNaN(date.getTime())) {
|
||||||
return String(value || '').replace('T', ' ').replace('Z', '')
|
return String(value || "")
|
||||||
|
.replace("T", " ")
|
||||||
|
.replace("Z", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = new Intl.DateTimeFormat('zh-CN', {
|
const parts = new Intl.DateTimeFormat("zh-CN", {
|
||||||
timeZone: RESPONSE_TIME_ZONE,
|
timeZone: RESPONSE_TIME_ZONE,
|
||||||
hour12: false,
|
hour12: false,
|
||||||
year: 'numeric',
|
year: "numeric",
|
||||||
month: '2-digit',
|
month: "2-digit",
|
||||||
day: '2-digit',
|
day: "2-digit",
|
||||||
hour: '2-digit',
|
hour: "2-digit",
|
||||||
minute: '2-digit',
|
minute: "2-digit",
|
||||||
second: '2-digit',
|
second: "2-digit",
|
||||||
}).formatToParts(date)
|
}).formatToParts(date);
|
||||||
|
|
||||||
const tokens = Object.fromEntries(
|
const tokens = Object.fromEntries(
|
||||||
parts
|
parts
|
||||||
.filter((item) => item.type !== 'literal')
|
.filter((item) => item.type !== "literal")
|
||||||
.map((item) => [item.type, item.value]),
|
.map((item) => [item.type, item.value])
|
||||||
)
|
);
|
||||||
|
|
||||||
return `${tokens.year}-${tokens.month}-${tokens.day} ${tokens.hour}:${tokens.minute}:${tokens.second}`
|
return `${tokens.year}-${tokens.month}-${tokens.day} ${tokens.hour}:${tokens.minute}:${tokens.second}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteHandlerOptions = {
|
type RouteHandlerOptions = {
|
||||||
successMessage?: string
|
successMessage?: string;
|
||||||
errorMessage?: string
|
errorMessage?: string;
|
||||||
scope?: string
|
scope?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
type RouteFileHandlerOptions = {
|
type RouteFileHandlerOptions = {
|
||||||
errorMessage?: string
|
errorMessage?: string;
|
||||||
scope?: string
|
scope?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function createRouteHandler(
|
export function createRouteHandler(
|
||||||
action: (req: any, res: any) => unknown | Promise<unknown>,
|
action: (req: Request, res: Response) => unknown | Promise<unknown>,
|
||||||
{ successMessage = 'ok', errorMessage, scope }: RouteHandlerOptions = {},
|
{ successMessage = "ok", errorMessage, scope }: RouteHandlerOptions = {}
|
||||||
) {
|
) {
|
||||||
return async (req, res) => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const data = await action(req, res)
|
const data = await action(req, res);
|
||||||
res.json(buildSuccessPayload(data, successMessage))
|
res.json(buildSuccessPayload(data, successMessage));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
sendRouteError(res, error, errorMessage, scope)
|
sendRouteError(res, error, errorMessage, scope);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRouteFileHandler(
|
export function createRouteFileHandler(
|
||||||
action: (req: any, res: any) => string | Promise<string>,
|
action: (req: Request, res: Response) => string | Promise<string>,
|
||||||
{ errorMessage, scope }: RouteFileHandlerOptions = {},
|
{ errorMessage, scope }: RouteFileHandlerOptions = {}
|
||||||
) {
|
) {
|
||||||
return async (req, res) => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const filePath = await action(req, res)
|
const filePath = await action(req, res);
|
||||||
res.sendFile(filePath)
|
res.sendFile(filePath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
sendRouteError(res, error, errorMessage, scope)
|
sendRouteError(res, error, errorMessage, scope);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user