生成记录改为数据库分页,每页 40 条,默认最近 7 天;可见预览限制为 4 路并发并使用 192px 缩略图,翻页取消旧请求,视频在详情中按需播放。保留精选、分类和原图操作,新增画布任务失败记录,统一时间显示。 模型管理拆分模型、上游和运行健康视图,提供搜索、筛选、分页和完整故障详情;列表使用摘要,编辑按需获取完整配置。后台按当前设置页加载,模型 GET 不再自动修改健康状态,98K 同步先显示变更预览。 验证:600 项现有辅助测试和 3 项新增回归测试通过,TypeScript 和本次 API/组件 lint 通过;浏览器验证请求峰值 4、翻页、搜索、失败重试、详情、模型筛选与 390px 窄屏。只读生产查询:当天页约 29ms,历史视频页 122ms,第 7800 页 739ms。两个并发索引已在生产建成,SQL 随代码保留。 已知检查状态:全仓 lint 存量 1004 项诊断使 check:quality 停在 lint 阶段;日志、品牌、CI、环境、凭据、元数据、测试与制品检查单独通过。
110 lines
4.0 KiB
TypeScript
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 });
|
|
}
|
|
}
|