154 lines
4.8 KiB
TypeScript

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 },
);
}
}