后端迁移后台基础路由
This commit is contained in:
-2
@@ -1,5 +1,3 @@
|
||||
// @ts-check
|
||||
|
||||
import { Router } from 'express'
|
||||
|
||||
import { getAdminDashboardSummary } from '../../services/admin/admin-dashboard-service.js'
|
||||
@@ -1,46 +1,46 @@
|
||||
// @ts-check
|
||||
|
||||
import { requireAdminRole, verifyAdminSessionToken } from '../../services/admin/admin-auth-service.js'
|
||||
import { writeAdminAuditLog } from '../../services/admin/admin-audit-service.js'
|
||||
import { buildSuccessPayload, createHttpError, sendRouteError } from '../../utils/http.js'
|
||||
import { logWarn } from '../../utils/logger.js'
|
||||
import type { AdminSession } from '../../services/admin/admin-auth-service.js'
|
||||
|
||||
/** @typedef {import('express').NextFunction} NextFunction */
|
||||
/** @typedef {import('express').Request} ExpressRequest */
|
||||
/** @typedef {import('express').Response} ExpressResponse */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminRouteAdminSession} AdminRouteAdminSession */
|
||||
/** @typedef {ExpressRequest & { adminSession?: AdminRouteAdminSession | null }} AdminExpressRequest */
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* action?: string
|
||||
* targetType?: string
|
||||
* targetId?: string
|
||||
* data?: Record<string, unknown>
|
||||
* }} AdminAuditPayload
|
||||
*/
|
||||
type AdminAuditPayload = {
|
||||
action?: string
|
||||
targetType?: string
|
||||
targetId?: string
|
||||
data?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* successMessage?: string
|
||||
* errorMessage?: string
|
||||
* scope?: string
|
||||
* audit?: (req: AdminExpressRequest, data: unknown) => (AdminAuditPayload | null | undefined)
|
||||
* }} AdminJsonHandlerOptions
|
||||
*/
|
||||
type AdminJsonHandlerOptions = {
|
||||
successMessage?: string
|
||||
errorMessage?: string
|
||||
scope?: string
|
||||
audit?: (req: AdminExpressRequest, data: unknown) => AdminAuditPayload | null | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* errorMessage?: string
|
||||
* scope?: string
|
||||
* }} AdminFileHandlerOptions
|
||||
*/
|
||||
type AdminFileHandlerOptions = {
|
||||
errorMessage?: string
|
||||
scope?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(req: AdminExpressRequest, res: ExpressResponse) => unknown | Promise<unknown>} action
|
||||
* @param {AdminJsonHandlerOptions} [options]
|
||||
*/
|
||||
export function createJsonHandler(action, { successMessage = 'ok', errorMessage, scope, audit } = {}) {
|
||||
export function createJsonHandler(
|
||||
action: (req: AdminExpressRequest, res: ExpressResponse) => unknown | Promise<unknown>,
|
||||
{ successMessage = 'ok', errorMessage, scope, audit }: AdminJsonHandlerOptions = {},
|
||||
) {
|
||||
return async (req, res) => {
|
||||
try {
|
||||
const data = await action(req, res)
|
||||
@@ -52,11 +52,10 @@ export function createJsonHandler(action, { successMessage = 'ok', errorMessage,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(req: AdminExpressRequest, res: ExpressResponse) => string | Promise<string>} action
|
||||
* @param {AdminFileHandlerOptions} [options]
|
||||
*/
|
||||
export function createFileHandler(action, { errorMessage, scope } = {}) {
|
||||
export function createFileHandler(
|
||||
action: (req: AdminExpressRequest, res: ExpressResponse) => string | Promise<string>,
|
||||
{ errorMessage, scope }: AdminFileHandlerOptions = {},
|
||||
) {
|
||||
return async (req, res) => {
|
||||
try {
|
||||
const filePath = await action(req, res)
|
||||
@@ -67,10 +66,7 @@ export function createFileHandler(action, { errorMessage, scope } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {AdminExpressRequest} req */
|
||||
/** @param {ExpressResponse} res */
|
||||
/** @param {NextFunction} next */
|
||||
export async function requireAdminSession(req, res, next) {
|
||||
export async function requireAdminSession(req: AdminExpressRequest, res: ExpressResponse, next: NextFunction) {
|
||||
try {
|
||||
req.adminSession = await verifyAdminSessionToken(extractBearerToken(req))
|
||||
next()
|
||||
@@ -79,8 +75,7 @@ export async function requireAdminSession(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string[]} allowedRoles */
|
||||
export function requireAdminRoles(allowedRoles) {
|
||||
export function requireAdminRoles(allowedRoles: string[]) {
|
||||
return (req, res, next) => {
|
||||
try {
|
||||
requireAdminRole(req.adminSession, allowedRoles)
|
||||
@@ -91,7 +86,12 @@ export function requireAdminRoles(allowedRoles) {
|
||||
}
|
||||
}
|
||||
|
||||
async function recordAdminAudit(session, req, data, audit) {
|
||||
async function recordAdminAudit(
|
||||
session: AdminSession | null | undefined,
|
||||
req: AdminExpressRequest,
|
||||
data: unknown,
|
||||
audit: AdminJsonHandlerOptions['audit'],
|
||||
) {
|
||||
if (!audit) {
|
||||
return
|
||||
}
|
||||
@@ -114,7 +114,7 @@ async function recordAdminAudit(session, req, data, audit) {
|
||||
}
|
||||
}
|
||||
|
||||
export function extractBearerToken(req) {
|
||||
export function extractBearerToken(req: AdminExpressRequest) {
|
||||
const authorization = String(req.headers.authorization || '').trim()
|
||||
const matched = authorization.match(/^Bearer\s+(.+)$/i)
|
||||
|
||||
@@ -10,17 +10,15 @@ import {
|
||||
} from '../../services/admin/admin-auth-service.js'
|
||||
import { createJsonHandler, requireAdminRoles } from './shared.js'
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* user: {
|
||||
* userId: number
|
||||
* username: string
|
||||
* role: string
|
||||
* status?: string
|
||||
* inventoryGroupCodes?: string[]
|
||||
* }
|
||||
* }} AdminUserMutationResult
|
||||
*/
|
||||
type AdminUserMutationResult = {
|
||||
user: {
|
||||
userId: number
|
||||
username: string
|
||||
role: string
|
||||
status?: string
|
||||
inventoryGroupCodes?: string[]
|
||||
}
|
||||
}
|
||||
|
||||
const router = Router()
|
||||
|
||||
@@ -42,16 +40,16 @@ router.post('/users', createJsonHandler(
|
||||
errorMessage: '创建后台用户失败',
|
||||
scope: '[admin/users:create]',
|
||||
audit: (_req, data) => {
|
||||
const result = /** @type {AdminUserMutationResult} */ (data)
|
||||
const result = data as AdminUserMutationResult
|
||||
return {
|
||||
action: 'admin_user_created',
|
||||
targetType: 'admin_user',
|
||||
targetId: String(result.user.userId),
|
||||
data: {
|
||||
username: result.user.username,
|
||||
role: result.user.role,
|
||||
status: result.user.status,
|
||||
},
|
||||
action: 'admin_user_created',
|
||||
targetType: 'admin_user',
|
||||
targetId: String(result.user.userId),
|
||||
data: {
|
||||
username: result.user.username,
|
||||
role: result.user.role,
|
||||
status: result.user.status,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -64,15 +62,15 @@ router.post('/users/:userId/role', createJsonHandler(
|
||||
errorMessage: '更新用户角色失败',
|
||||
scope: '[admin/users/:userId/role]',
|
||||
audit: (_req, data) => {
|
||||
const result = /** @type {AdminUserMutationResult} */ (data)
|
||||
const result = data as AdminUserMutationResult
|
||||
return {
|
||||
action: 'admin_user_role_updated',
|
||||
targetType: 'admin_user',
|
||||
targetId: String(result.user.userId),
|
||||
data: {
|
||||
username: result.user.username,
|
||||
role: result.user.role,
|
||||
},
|
||||
action: 'admin_user_role_updated',
|
||||
targetType: 'admin_user',
|
||||
targetId: String(result.user.userId),
|
||||
data: {
|
||||
username: result.user.username,
|
||||
role: result.user.role,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -85,15 +83,15 @@ router.post('/users/:userId/status', createJsonHandler(
|
||||
errorMessage: '更新用户状态失败',
|
||||
scope: '[admin/users/:userId/status]',
|
||||
audit: (_req, data) => {
|
||||
const result = /** @type {AdminUserMutationResult} */ (data)
|
||||
const result = data as AdminUserMutationResult
|
||||
return {
|
||||
action: 'admin_user_status_updated',
|
||||
targetType: 'admin_user',
|
||||
targetId: String(result.user.userId),
|
||||
data: {
|
||||
username: result.user.username,
|
||||
status: result.user.status,
|
||||
},
|
||||
action: 'admin_user_status_updated',
|
||||
targetType: 'admin_user',
|
||||
targetId: String(result.user.userId),
|
||||
data: {
|
||||
username: result.user.username,
|
||||
status: result.user.status,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -106,15 +104,15 @@ router.post('/users/:userId/inventory-groups', createJsonHandler(
|
||||
errorMessage: '更新用户库存组失败',
|
||||
scope: '[admin/users/:userId/inventory-groups]',
|
||||
audit: (_req, data) => {
|
||||
const result = /** @type {AdminUserMutationResult} */ (data)
|
||||
const result = data as AdminUserMutationResult
|
||||
return {
|
||||
action: 'admin_user_inventory_groups_updated',
|
||||
targetType: 'admin_user',
|
||||
targetId: String(result.user.userId),
|
||||
data: {
|
||||
username: result.user.username,
|
||||
inventoryGroupCodes: result.user.inventoryGroupCodes,
|
||||
},
|
||||
action: 'admin_user_inventory_groups_updated',
|
||||
targetType: 'admin_user',
|
||||
targetId: String(result.user.userId),
|
||||
data: {
|
||||
username: result.user.username,
|
||||
inventoryGroupCodes: result.user.inventoryGroupCodes,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -127,14 +125,14 @@ router.post('/users/:userId/reset-password', createJsonHandler(
|
||||
errorMessage: '重置用户密码失败',
|
||||
scope: '[admin/users/:userId/reset-password]',
|
||||
audit: (_req, data) => {
|
||||
const result = /** @type {AdminUserMutationResult} */ (data)
|
||||
const result = data as AdminUserMutationResult
|
||||
return {
|
||||
action: 'admin_user_password_reset',
|
||||
targetType: 'admin_user',
|
||||
targetId: String(result.user.userId),
|
||||
data: {
|
||||
username: result.user.username,
|
||||
},
|
||||
action: 'admin_user_password_reset',
|
||||
targetType: 'admin_user',
|
||||
targetId: String(result.user.userId),
|
||||
data: {
|
||||
username: result.user.username,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -18,7 +18,7 @@ type AdminUserRow = NonNullable<Awaited<ReturnType<typeof getAdminUserById>>>
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
type AdminSession = {
|
||||
export type AdminSession = {
|
||||
sessionId: string
|
||||
userId: number
|
||||
username: string
|
||||
|
||||
Reference in New Issue
Block a user