import { NextRequest, NextResponse } from 'next/server'; import { auth } from '@/app/(auth)/auth'; import { db } from '@/lib/db'; import { canvasProject } from '@/lib/db/schema'; import { syncCanvasNodesForProject } from '@/lib/db/queries'; import { resolveArchivedImageUrls } from '@/lib/storage/image-archive-db'; import { eq, and } from 'drizzle-orm'; import { buildStaleCanvasSaveConflictPayload, isStaleCanvasSaveBaseRevision, } from '@/lib/canvas/canvas-save-conflict'; function resolveWorkspaceType(input: unknown) { return input === "agent" || input === "video" ? input : "canvas"; } function getDefaultWorkspaceTitle(workspaceType: ReturnType) { return workspaceType === "video" ? "未命名视频画布" : "未命名画布"; } function shouldUseLocalCanvasE2E(request: NextRequest) { const hostname = request.nextUrl.hostname; return ( process.env.CANVAS_E2E_LOCAL === "true" && (hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1") ); } function createLocalCanvasProject( id: string, input: { canvasData?: unknown; description?: unknown; thumbnail?: unknown; title?: unknown; workspaceType?: unknown; } = {} ) { const now = new Date().toISOString(); const workspaceType = resolveWorkspaceType(input.workspaceType || "video"); return { id, userId: "local-video-canvas-e2e-user", title: String(input.title || getDefaultWorkspaceTitle(workspaceType)), description: typeof input.description === "string" ? input.description : null, thumbnail: typeof input.thumbnail === "string" ? input.thumbnail : null, workspaceType, canvasData: input.canvasData && typeof input.canvasData === "object" ? input.canvasData : { nodes: [], edges: [], selectedNodeIds: [], viewport: { x: 0, y: 0, zoom: 1 }, tool: "select", }, createdAt: now, updatedAt: now, }; } // 获取单个画布 export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params; if (shouldUseLocalCanvasE2E(request)) { return NextResponse.json({ success: true, data: createLocalCanvasProject(id), }); } const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ success: false, error: '未登录' }, { status: 401 }); } const [project] = await db .select() .from(canvasProject) .where( and( eq(canvasProject.id, id), eq(canvasProject.userId, session.user.id) ) ); if (!project) { return NextResponse.json({ success: false, error: '画布不存在' }, { status: 404 }); } return NextResponse.json({ success: true, data: project }); } catch (error) { console.error('获取画布失败:', error); return NextResponse.json({ success: false, error: '获取画布失败' }, { status: 500 }); } } // 更新画布(支持 upsert:如果不存在则创建) export async function PUT( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params; const body = await request.json(); if (shouldUseLocalCanvasE2E(request)) { return NextResponse.json({ success: true, data: createLocalCanvasProject(id, body), }); } const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ success: false, error: '未登录' }, { status: 401 }); } const { title, description, canvasData } = body; const thumbnail = await resolveArchivedImageUrls(body.thumbnail); const baseCanvasRevision = body?.baseCanvasUpdatedAt || body?.baseCanvasRevision; const workspaceType = body && Object.prototype.hasOwnProperty.call(body, "workspaceType") ? resolveWorkspaceType(body.workspaceType) : undefined; // 先检查画布是否存在 const [existingProject] = await db .select() .from(canvasProject) .where( and( eq(canvasProject.id, id), eq(canvasProject.userId, session.user.id) ) ); if (existingProject) { if ( canvasData !== undefined && baseCanvasRevision && isStaleCanvasSaveBaseRevision({ baseCanvasRevision: body?.baseCanvasRevision, baseCanvasUpdatedAt: body?.baseCanvasUpdatedAt, currentUpdatedAt: existingProject.updatedAt, }) ) { return NextResponse.json( buildStaleCanvasSaveConflictPayload(existingProject.updatedAt), { status: 409 } ); } const syncedCanvasData = canvasData !== undefined ? await syncCanvasNodesForProject({ projectId: id, userId: session.user.id, canvasData, }) : undefined; // 更新现有画布 const updateData: any = { updatedAt: new Date() }; if (title !== undefined) updateData.title = title; if (description !== undefined) updateData.description = description; if (thumbnail !== undefined) updateData.thumbnail = thumbnail; if (workspaceType !== undefined) updateData.workspaceType = workspaceType; if (syncedCanvasData !== undefined) updateData.canvasData = syncedCanvasData; const [updatedProject] = await db .update(canvasProject) .set(updateData) .where( and( eq(canvasProject.id, id), eq(canvasProject.userId, session.user.id) ) ) .returning(); return NextResponse.json({ success: true, data: updatedProject }); } else { const syncedCanvasData = canvasData !== undefined ? await syncCanvasNodesForProject({ projectId: id, userId: session.user.id, canvasData, }) : undefined; const nextWorkspaceType = workspaceType || "canvas"; const nextCanvasData = syncedCanvasData || { nodes: [], edges: [], viewport: { x: 0, y: 0, zoom: 1 }, }; const nextTitle = title || getDefaultWorkspaceTitle(nextWorkspaceType); const insertData: any = { id, // 使用指定的 ID userId: session.user.id, title: nextTitle, workspaceType: nextWorkspaceType, canvasData: nextCanvasData, }; const conflictUpdateData: any = { title: nextTitle, workspaceType: nextWorkspaceType, canvasData: nextCanvasData, updatedAt: new Date(), }; if (description !== undefined) { insertData.description = description; conflictUpdateData.description = description; } if (thumbnail !== undefined) { insertData.thumbnail = thumbnail; conflictUpdateData.thumbnail = thumbnail; } // 创建新画布(使用指定 ID),并兼容同一用户并发创建同一画布的场景。 const [newProject] = await db .insert(canvasProject) .values(insertData) .onConflictDoUpdate({ target: canvasProject.id, set: conflictUpdateData, where: eq(canvasProject.userId, session.user.id), }) .returning(); if (!newProject) { return NextResponse.json( { success: false, error: '画布ID已被占用' }, { status: 409 } ); } return NextResponse.json({ success: true, data: newProject }); } } catch (error) { console.error('更新/创建画布失败:', error); return NextResponse.json({ success: false, error: '更新画布失败' }, { status: 500 }); } } // 删除画布 export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ success: false, error: '未登录' }, { status: 401 }); } const { id } = await params; const [deletedProject] = await db .delete(canvasProject) .where( and( eq(canvasProject.id, id), eq(canvasProject.userId, session.user.id) ) ) .returning(); if (!deletedProject) { return NextResponse.json({ success: false, error: '画布不存在' }, { status: 404 }); } return NextResponse.json({ success: true, data: deletedProject }); } catch (error) { console.error('删除画布失败:', error); return NextResponse.json({ success: false, error: '删除画布失败' }, { status: 500 }); } }