WuYongJie af8189908d 优化后台生成记录分页与模型管理
生成记录改为数据库分页,每页 40 条,默认最近 7 天;可见预览限制为 4 路并发并使用 192px 缩略图,翻页取消旧请求,视频在详情中按需播放。保留精选、分类和原图操作,新增画布任务失败记录,统一时间显示。

模型管理拆分模型、上游和运行健康视图,提供搜索、筛选、分页和完整故障详情;列表使用摘要,编辑按需获取完整配置。后台按当前设置页加载,模型 GET 不再自动修改健康状态,98K 同步先显示变更预览。

验证:600 项现有辅助测试和 3 项新增回归测试通过,TypeScript 和本次 API/组件 lint 通过;浏览器验证请求峰值 4、翻页、搜索、失败重试、详情、模型筛选与 390px 窄屏。只读生产查询:当天页约 29ms,历史视频页 122ms,第 7800 页 739ms。两个并发索引已在生产建成,SQL 随代码保留。

已知检查状态:全仓 lint 存量 1004 项诊断使 check:quality 停在 lint 阶段;日志、品牌、CI、环境、凭据、元数据、测试与制品检查单独通过。
2026-11-03 17:41:34 +08:00

110 lines
4.0 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { generatedImage, user } from "@/lib/db/schema";
import { eq } from "drizzle-orm";
import { auth } from "@/app/(auth)/auth";
import { requireAdmin } from "@/lib/middleware/admin";
import { ChatSDKError } from "@/lib/errors";
import { mediaRecordsQuery, parseMediaQuery } from "@/lib/admin/media-records";
import { resolveArchivedImageUrls } from "@/lib/storage/image-archive-db";
export async function GET(request: NextRequest) {
try {
const session = await requireAdmin();
const operator = await db.query.user.findFirst({ where: eq(user.id, session.user.id) });
if (operator?.role !== "admin") return NextResponse.json({ error: "Forbidden" }, { status: 403 });
const query = parseMediaQuery(request.nextUrl.searchParams);
if (query.startDate && query.endDate && query.startDate > query.endDate) {
return NextResponse.json({ error: "开始日期不能晚于结束日期" }, { status: 400 });
}
const [result] = await db.execute(mediaRecordsQuery(query));
const images = await resolveArchivedImageUrls(result.records);
const total = Number(result.total);
return NextResponse.json({ images, pagination: { page: query.page, pageSize: query.pageSize, total,
totalPages: Math.ceil(total / query.pageSize), totalImages: Number(result.totalImages), totalVideos: Number(result.totalVideos) } });
} catch (error) {
if (error instanceof ChatSDKError) return error.toResponse();
console.error("[Admin Images API] GET failed", error);
return NextResponse.json({ error: "记录加载失败,请重试" }, { status: 500 });
}
}
// PATCH: 设置/取消图片精选
export async function PATCH(request: NextRequest) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// 检查是否为管理员
const currentUser = await db.query.user.findFirst({
where: eq(user.id, session.user.id),
});
if (currentUser?.role !== "admin") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const body = await request.json();
const { imageId, isFeatured, featuredCategory } = body;
if (!imageId) {
return NextResponse.json({ error: "Image ID is required" }, { status: 400 });
}
const updateData: Record<string, unknown> = {};
const shouldUpdateFeaturedState = typeof isFeatured === "boolean";
const shouldUpdateCategory = Object.prototype.hasOwnProperty.call(body, "featuredCategory");
if (!shouldUpdateFeaturedState && !shouldUpdateCategory) {
return NextResponse.json({ error: "No valid fields to update" }, { status: 400 });
}
// 更新精选状态
if (shouldUpdateFeaturedState) {
updateData.isFeatured = isFeatured;
if (isFeatured) {
updateData.featuredAt = new Date();
updateData.featuredBy = session.user.id;
} else {
updateData.featuredAt = null;
updateData.featuredBy = null;
}
}
if (shouldUpdateCategory) {
const normalizedCategory =
typeof featuredCategory === "string" && featuredCategory.trim().length > 0
? featuredCategory.trim().slice(0, 64)
: null;
updateData.featuredCategory = normalizedCategory;
}
await db
.update(generatedImage)
.set(updateData)
.where(eq(generatedImage.id, imageId));
console.log(
`[Admin Images API] Image ${imageId} updated by ${session.user.id}, featured=${String(
isFeatured
)}`
);
return NextResponse.json({
success: true,
message: shouldUpdateFeaturedState
? isFeatured
? "已添加到精选"
: "已取消精选"
: "精选分类已更新",
});
} catch (error) {
console.error("[Admin Images API] PATCH Error:", error);
return NextResponse.json({ error: "Failed to update featured status" }, { status: 500 });
}
}