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