import { NextRequest, NextResponse } from "next/server"; import { and, eq, inArray, sql } from "drizzle-orm"; import { auth } from "@/app/(auth)/auth"; import { db } from "@/lib/db"; import { canvasProject, imageArchiveTask, generatedImage, canvasImageTask } from "@/lib/db/schema"; import { imageArchiveSourceKey } from "@/lib/storage/image-archive-db"; import { isGeneratedImageSource } from "@/lib/storage/image-archive-urls"; import { scheduleImageArchiveWorker } from "@/lib/storage/image-archive-worker"; export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const session = await auth(); if (!session?.user?.id) return NextResponse.json({ error: "未登录" }, { status: 401 }); const { id } = await params; const [project] = await db .select({ id: canvasProject.id }) .from(canvasProject) .where(and(eq(canvasProject.id, id), eq(canvasProject.userId, session.user.id))) .limit(1); if (!project) return NextResponse.json({ error: "画布不存在" }, { status: 404 }); const body = await request.json(); if ( !Array.isArray(body.urls) || body.urls.length > 100 || body.urls.some((url: unknown) => typeof url !== "string" || !isGeneratedImageSource(url)) ) { return NextResponse.json({ error: "图片地址格式错误" }, { status: 400 }); } const urls: string[] = [...new Set(body.urls)]; if (!urls.length) return NextResponse.json({ replacements: {}, pending: [] }); const source = imageArchiveTask.sourceUrl; const rows = await db .select({ sourceUrl: imageArchiveTask.sourceUrl, targetUrl: imageArchiveTask.targetUrl, status: imageArchiveTask.status, }) .from(imageArchiveTask) .where( and( inArray(imageArchiveTask.sourceUrl, urls.map(imageArchiveSourceKey)), sql`( exists (select 1 from ${generatedImage} where ${generatedImage.userId} = ${session.user.id} and (${generatedImage.originalUrl} = ${source} or ${generatedImage.thumbnailUrl} = ${source} or ${generatedImage.originalUrl} = ${imageArchiveTask.targetUrl} or ${generatedImage.thumbnailUrl} = ${imageArchiveTask.targetUrl})) or exists (select 1 from ${canvasImageTask} where ${canvasImageTask.userId} = ${session.user.id} and (position(${source} in ${canvasImageTask.result}::text) > 0 or position(${imageArchiveTask.targetUrl} in ${canvasImageTask.result}::text) > 0)) or exists (select 1 from ${canvasProject} where ${canvasProject.id} = ${id} and ${canvasProject.userId} = ${session.user.id} and (position(${source} in ${canvasProject.canvasData}::text) > 0 or position(${imageArchiveTask.targetUrl} in ${canvasProject.canvasData}::text) > 0)) )`, ), ); const byKey = new Map(rows.map((row) => [row.sourceUrl, row])); const replacements: Record = {}; const pending: string[] = []; for (const url of urls) { const row = byKey.get(imageArchiveSourceKey(url)); if (row?.targetUrl) replacements[url] = row.targetUrl; else if (row && row.status !== "abandoned") pending.push(url); } if (rows.some((row) => row.status === "pending")) scheduleImageArchiveWorker(true); return NextResponse.json( { replacements, pending }, { headers: { "Cache-Control": "private, no-store" } }, ); }