267 lines
7.7 KiB
TypeScript
267 lines
7.7 KiB
TypeScript
import { and, desc, eq } from "drizzle-orm";
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { auth } from "@/app/(auth)/auth";
|
|
import {
|
|
duplicateVideoCanvasData,
|
|
getExplicitVideoCanvasWorkspaceMeta,
|
|
getNextVideoCanvasName,
|
|
getVideoCanvasCopyName,
|
|
getVideoCanvasWorkspaceProjects,
|
|
normalizeVideoCanvasName,
|
|
resolveVideoCanvasWorkspaceMeta,
|
|
withVideoCanvasWorkspaceMeta,
|
|
} from "@/lib/canvas/video-workspace";
|
|
import { db } from "@/lib/db";
|
|
import { syncCanvasNodesForProject } from "@/lib/db/queries";
|
|
import { canvasProject } from "@/lib/db/schema";
|
|
|
|
type CanvasProjectRow = typeof canvasProject.$inferSelect;
|
|
|
|
function toCanvasItem(project: CanvasProjectRow, currentId: string) {
|
|
const meta = resolveVideoCanvasWorkspaceMeta(project);
|
|
return {
|
|
createdAt: project.createdAt.toISOString(),
|
|
id: project.id,
|
|
isCurrent: project.id === currentId,
|
|
name: meta.canvasName,
|
|
};
|
|
}
|
|
|
|
async function getWorkspaceContext(userId: string, currentId: string) {
|
|
const projects = await db
|
|
.select()
|
|
.from(canvasProject)
|
|
.where(
|
|
and(
|
|
eq(canvasProject.userId, userId),
|
|
eq(canvasProject.workspaceType, "video"),
|
|
),
|
|
)
|
|
.orderBy(desc(canvasProject.createdAt));
|
|
const currentProject = projects.find((project) => project.id === currentId);
|
|
if (!currentProject) return null;
|
|
return {
|
|
currentProject,
|
|
projects: getVideoCanvasWorkspaceProjects(projects, currentProject),
|
|
};
|
|
}
|
|
|
|
async function ensureExplicitWorkspaceMetadata(projects: CanvasProjectRow[]) {
|
|
for (const project of projects) {
|
|
if (getExplicitVideoCanvasWorkspaceMeta(project.canvasData)) continue;
|
|
const canvasData = withVideoCanvasWorkspaceMeta(
|
|
project.canvasData,
|
|
resolveVideoCanvasWorkspaceMeta(project),
|
|
);
|
|
await db
|
|
.update(canvasProject)
|
|
.set({ canvasData })
|
|
.where(eq(canvasProject.id, project.id));
|
|
project.canvasData = canvasData;
|
|
}
|
|
}
|
|
|
|
export async function GET(
|
|
_request: NextRequest,
|
|
{ params }: { params: Promise<{ id: string }> },
|
|
) {
|
|
const session = await auth();
|
|
if (!session?.user?.id) {
|
|
return NextResponse.json({ success: false, error: "未登录" }, { status: 401 });
|
|
}
|
|
const { id } = await params;
|
|
const context = await getWorkspaceContext(session.user.id, id);
|
|
if (!context) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "画布不存在" },
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: {
|
|
canvases: context.projects.map((project) => toCanvasItem(project, id)),
|
|
currentCanvasId: id,
|
|
workspaceTitle: context.currentProject.title,
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function POST(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ id: string }> },
|
|
) {
|
|
const session = await auth();
|
|
if (!session?.user?.id) {
|
|
return NextResponse.json({ success: false, error: "未登录" }, { status: 401 });
|
|
}
|
|
const { id } = await params;
|
|
const body = await request.json();
|
|
if (body?.action !== "create" && body?.action !== "copy") {
|
|
return NextResponse.json(
|
|
{ success: false, error: "不支持的画布操作" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
const action = body.action;
|
|
const context = await getWorkspaceContext(session.user.id, id);
|
|
if (!context) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "画布不存在" },
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
await ensureExplicitWorkspaceMetadata(context.projects);
|
|
const currentMeta = resolveVideoCanvasWorkspaceMeta(context.currentProject);
|
|
const existingNames = context.projects.map(
|
|
(project) => resolveVideoCanvasWorkspaceMeta(project).canvasName,
|
|
);
|
|
const source =
|
|
action === "copy"
|
|
? context.projects.find((project) => project.id === body?.targetId)
|
|
: context.currentProject;
|
|
if (!source) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "要复制的画布不存在" },
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
const canvasName =
|
|
action === "copy"
|
|
? getVideoCanvasCopyName(
|
|
resolveVideoCanvasWorkspaceMeta(source).canvasName,
|
|
existingNames,
|
|
)
|
|
: getNextVideoCanvasName(existingNames);
|
|
const meta = {
|
|
canvasName,
|
|
workspaceId: currentMeta.workspaceId,
|
|
};
|
|
const canvasData =
|
|
action === "copy"
|
|
? duplicateVideoCanvasData(source.canvasData, meta)
|
|
: withVideoCanvasWorkspaceMeta(null, meta);
|
|
const [insertedProject] = await db
|
|
.insert(canvasProject)
|
|
.values({
|
|
canvasData,
|
|
description: context.currentProject.description,
|
|
title: context.currentProject.title,
|
|
userId: session.user.id,
|
|
workspaceType: "video",
|
|
})
|
|
.returning();
|
|
const syncedCanvasData = await syncCanvasNodesForProject({
|
|
canvasData,
|
|
projectId: insertedProject.id,
|
|
userId: session.user.id,
|
|
});
|
|
const [createdProject] = await db
|
|
.update(canvasProject)
|
|
.set({ canvasData: syncedCanvasData })
|
|
.where(eq(canvasProject.id, insertedProject.id))
|
|
.returning();
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: toCanvasItem(createdProject || insertedProject, id),
|
|
});
|
|
}
|
|
|
|
export async function PATCH(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ id: string }> },
|
|
) {
|
|
const session = await auth();
|
|
if (!session?.user?.id) {
|
|
return NextResponse.json({ success: false, error: "未登录" }, { status: 401 });
|
|
}
|
|
const { id } = await params;
|
|
const body = await request.json();
|
|
const context = await getWorkspaceContext(session.user.id, id);
|
|
if (!context) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "画布不存在" },
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
const target = context.projects.find((project) => project.id === body?.targetId);
|
|
if (!target) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "画布不存在" },
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
const currentMeta = resolveVideoCanvasWorkspaceMeta(context.currentProject);
|
|
const targetMeta = resolveVideoCanvasWorkspaceMeta(target);
|
|
const canvasName = normalizeVideoCanvasName(body?.name, targetMeta.canvasName);
|
|
const [updatedProject] = await db
|
|
.update(canvasProject)
|
|
.set({
|
|
canvasData: withVideoCanvasWorkspaceMeta(target.canvasData, {
|
|
canvasName,
|
|
workspaceId: currentMeta.workspaceId,
|
|
}),
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(
|
|
and(
|
|
eq(canvasProject.id, target.id),
|
|
eq(canvasProject.userId, session.user.id),
|
|
),
|
|
)
|
|
.returning();
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: toCanvasItem(updatedProject, id),
|
|
});
|
|
}
|
|
|
|
export async function DELETE(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ id: string }> },
|
|
) {
|
|
const session = await auth();
|
|
if (!session?.user?.id) {
|
|
return NextResponse.json({ success: false, error: "未登录" }, { status: 401 });
|
|
}
|
|
const { id } = await params;
|
|
const body = await request.json();
|
|
const context = await getWorkspaceContext(session.user.id, id);
|
|
if (!context) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "画布不存在" },
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
const target = context.projects.find((project) => project.id === body?.targetId);
|
|
if (!target) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "画布不存在" },
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
if (context.projects.length <= 1) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "至少保留一个画布" },
|
|
{ status: 409 },
|
|
);
|
|
}
|
|
await db
|
|
.delete(canvasProject)
|
|
.where(
|
|
and(
|
|
eq(canvasProject.id, target.id),
|
|
eq(canvasProject.userId, session.user.id),
|
|
),
|
|
);
|
|
const remaining = context.projects.filter((project) => project.id !== target.id);
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: {
|
|
nextCanvasId: target.id === id ? remaining[0]?.id || null : id,
|
|
},
|
|
});
|
|
}
|