import { parseAdminAuth } from "@/lib/api/admin-auth-context"; import { prisma } from "@/lib/prisma"; import { fail, ok } from "@/lib/response"; import { AuthError } from "@/modules/auth/errors"; import { requirePermission } from "@/modules/auth/rbac"; import { serializeAlert } from "@/modules/alert/serializer"; import type { AlertStatus, AlertType } from "@/modules/alert/types"; const VALID_ALERT_TYPES: readonly AlertType[] = [ "PRICE_DEVIATION", "STALE_FALLBACK", "RPA_FAILED", "RPA_CAPTCHA", "STRUCT_CHANGE", "SESSION_EXPIRED", "SECURITY", "MANUAL_FALLBACK", ]; const VALID_ALERT_STATUSES: readonly AlertStatus[] = ["open", "resolved"]; function parsePagination(searchParams: URLSearchParams): { page: number; size: number; } | null { const pageRaw = searchParams.get("page"); const sizeRaw = searchParams.get("size"); if (!pageRaw || !sizeRaw) { return null; } const page = Number(pageRaw); const size = Number(sizeRaw); if ( !Number.isInteger(page) || !Number.isInteger(size) || page < 1 || size < 1 || size > 100 ) { return null; } return { page, size }; } function parseFilters(searchParams: URLSearchParams): { type?: AlertType; status?: AlertStatus; } | { error: string } { const typeRaw = searchParams.get("type"); const statusRaw = searchParams.get("status"); if (typeRaw && !VALID_ALERT_TYPES.includes(typeRaw as AlertType)) { return { error: "无效的预警类型" }; } if (statusRaw && !VALID_ALERT_STATUSES.includes(statusRaw as AlertStatus)) { return { error: "无效的预警状态" }; } return { type: typeRaw ? (typeRaw as AlertType) : undefined, status: statusRaw ? (statusRaw as AlertStatus) : undefined, }; } /** GET /api/alerts — 预警列表(task-056) */ export async function GET(request: Request) { let auth; try { auth = parseAdminAuth(request); requirePermission(auth, "alert:read"); } catch (error) { if (error instanceof AuthError) { return fail(error.code, error.message, error.httpStatus); } throw error; } void auth; const searchParams = new URL(request.url).searchParams; const pagination = parsePagination(searchParams); if (!pagination) { return fail("VALIDATION_FAILED", "请提供有效的 page 和 size 参数", 400); } const filters = parseFilters(searchParams); if ("error" in filters) { return fail("VALIDATION_FAILED", filters.error, 400); } const where = { ...(filters.type ? { alertType: filters.type } : {}), ...(filters.status ? { status: filters.status } : {}), }; const { page, size } = pagination; const [total, records] = await Promise.all([ prisma.alertLog.count({ where }), prisma.alertLog.findMany({ where, orderBy: { createdAt: "desc" }, skip: (page - 1) * size, take: size, }), ]); return ok({ list: records.map(serializeAlert), total, page, size, }); }