import { and, desc, eq, inArray, ne, sql } from "drizzle-orm"; import { NextResponse } from "next/server"; import { auth } from "@/app/(auth)/auth"; import { db } from "@/lib/db"; import { getSystemSetting } from "@/lib/db/queries"; import { mediaAsset } from "@/lib/db/schema"; import { createLogger } from "@/lib/observability/logger"; import { VIDEO_CANVAS_FUNCTION_CONFIG_KEY, defaultVideoCanvasFunctionConfig, normalizeVideoCanvasFunctionConfig, } from "@/lib/video-canvas/config"; import { VIDEO_CANVAS_HISTORY_HIDDEN_AT_KEY, VIDEO_CANVAS_HISTORY_MEDIA_TYPES, normalizeVideoCanvasHistoryAssetIds, } from "@/lib/video-canvas/history-assets"; import { VIDEO_CANVAS_SUBJECT_UPLOAD_SOURCE_TYPE, VIDEO_CANVAS_SUBJECT_VOICE_SOURCE_TYPE, } from "@/lib/video-canvas/subjects"; export const dynamic = "force-dynamic"; export const runtime = "nodejs"; const logger = createLogger("video-canvas-history-assets"); async function getHistoryConfig() { const setting = await getSystemSetting({ key: VIDEO_CANVAS_FUNCTION_CONFIG_KEY, }).catch(() => null); return setting?.value ? normalizeVideoCanvasFunctionConfig(setting.value).historyAssets : defaultVideoCanvasFunctionConfig.historyAssets; } export async function GET() { try { const session = await auth(); if (!session?.user?.id) { return NextResponse.json( { success: false, error: "未登录" }, { status: 401 }, ); } const [config, assets] = await Promise.all([ getHistoryConfig(), db .select({ createdAt: mediaAsset.createdAt, displayName: mediaAsset.displayName, durationMs: mediaAsset.durationMs, height: mediaAsset.height, id: mediaAsset.id, mediaType: mediaAsset.mediaType, metadata: mediaAsset.metadata, mimeType: mediaAsset.mimeType, modelId: mediaAsset.modelId, originalUrl: mediaAsset.originalUrl, previewUrl: mediaAsset.previewUrl, prompt: mediaAsset.prompt, sourceNodeId: mediaAsset.sourceNodeId, sourceType: mediaAsset.sourceType, thumbnailUrl: mediaAsset.thumbnailUrl, width: mediaAsset.width, }) .from(mediaAsset) .where( and( eq(mediaAsset.userId, session.user.id), eq(mediaAsset.status, "ready"), inArray(mediaAsset.mediaType, [ ...VIDEO_CANVAS_HISTORY_MEDIA_TYPES, ]), ne( mediaAsset.sourceType, VIDEO_CANVAS_SUBJECT_UPLOAD_SOURCE_TYPE, ), ne( mediaAsset.sourceType, VIDEO_CANVAS_SUBJECT_VOICE_SOURCE_TYPE, ), sql`coalesce(${mediaAsset.metadata}->>${VIDEO_CANVAS_HISTORY_HIDDEN_AT_KEY}, '') = ''`, ), ) .orderBy(desc(mediaAsset.createdAt)) .limit(500), ]); return NextResponse.json({ success: true, assets: config.enabled ? assets.slice(0, config.maxItems) : [], }); } catch (error) { logger.error("history asset list failed", { error }); return NextResponse.json( { success: false, error: "历史资产加载失败" }, { status: 500 }, ); } } export async function DELETE(request: Request) { try { const session = await auth(); if (!session?.user?.id) { return NextResponse.json( { success: false, error: "未登录" }, { status: 401 }, ); } const body = (await request.json().catch(() => null)) as { assetIds?: unknown; } | null; const assetIds = normalizeVideoCanvasHistoryAssetIds(body?.assetIds); if (assetIds.length === 0) { return NextResponse.json( { success: false, error: "请选择历史资产" }, { status: 400 }, ); } const hiddenAt = new Date().toISOString(); const hidden = await db .update(mediaAsset) .set({ metadata: sql`coalesce(${mediaAsset.metadata}, '{}'::jsonb) || ${JSON.stringify({ [VIDEO_CANVAS_HISTORY_HIDDEN_AT_KEY]: hiddenAt, })}::jsonb`, updatedAt: new Date(), }) .where( and( eq(mediaAsset.userId, session.user.id), eq(mediaAsset.status, "ready"), inArray(mediaAsset.id, assetIds), inArray(mediaAsset.mediaType, [...VIDEO_CANVAS_HISTORY_MEDIA_TYPES]), sql`coalesce(${mediaAsset.metadata}->>${VIDEO_CANVAS_HISTORY_HIDDEN_AT_KEY}, '') = ''`, ), ) .returning({ id: mediaAsset.id }); return NextResponse.json({ success: true, removed: hidden.length }); } catch (error) { logger.error("history asset delete failed", { error }); return NextResponse.json( { success: false, error: "历史记录删除失败" }, { status: 500 }, ); } }