import type { Request, Response, NextFunction } from 'express' 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' type AdminAuditPayload = { action?: string targetType?: string targetId?: string data?: Record } type AdminJsonHandlerOptions = { successMessage?: string errorMessage?: string scope?: string audit?: (req: Request, data: unknown) => AdminAuditPayload | null | undefined } type AdminFileHandlerOptions = { errorMessage?: string scope?: string } export function createJsonHandler( action: (req: Request, res: Response) => unknown | Promise, { successMessage = 'ok', errorMessage, scope, audit }: AdminJsonHandlerOptions = {}, ) { return async (req: Request, res: Response): Promise => { try { const data = await action(req, res) await recordAdminAudit(req.adminSession, req, data, audit) res.json(buildSuccessPayload(data, successMessage)) } catch (error) { sendRouteError(res, error, errorMessage, scope) } } } export function createFileHandler( action: (req: Request, res: Response) => string | Promise, { errorMessage, scope }: AdminFileHandlerOptions = {}, ) { return async (req: Request, res: Response): Promise => { try { const filePath = await action(req, res) res.sendFile(filePath) } catch (error) { sendRouteError(res, error, errorMessage, scope) } } } export async function requireAdminSession(req: Request, res: Response, next: NextFunction): Promise { try { req.adminSession = await verifyAdminSessionToken(extractBearerToken(req)) next() } catch (error) { sendRouteError(res, error, '后台鉴权失败', '[admin/auth]') } } export function requireAdminRoles(allowedRoles: string[]) { return (req: Request, res: Response, next: NextFunction): void => { try { requireAdminRole(req.adminSession, allowedRoles) next() } catch (error) { sendRouteError(res, error, '后台鉴权失败', '[admin/auth/role]') } } } export function getRequiredAdminSession(req: Request): AdminSession { if (req.adminSession) { return req.adminSession } throw createHttpError('未登录或登录已失效', { statusCode: 401, errorCode: 'admin_auth_required', }) } async function recordAdminAudit( session: AdminSession | null | undefined, req: Request, data: unknown, audit: AdminJsonHandlerOptions['audit'], ) { if (!audit) { return } const payload = typeof audit === 'function' ? audit(req, data) : audit if (!payload) { return } try { await writeAdminAuditLog(session, payload) } catch (error) { logWarn('[admin/audit]', '后台审计日志写入失败,已跳过', { action: payload.action, targetType: payload.targetType, targetId: payload.targetId, error, }) } } export function extractBearerToken(req: Request): string { const authorization = String(req.headers.authorization || '').trim() const matched = authorization.match(/^Bearer\s+(.+)$/i) if (!matched) { throw createHttpError('未登录或登录已失效', { statusCode: 401, errorCode: 'admin_auth_required', }) } const token = matched[1]?.trim() if (!token) { throw createHttpError('未登录或登录已失效', { statusCode: 401, errorCode: 'admin_auth_required', }) } return token }