168 lines
5.4 KiB
TypeScript
168 lines
5.4 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { createSupabaseRouteClient } from "@/lib/supabase/server";
|
|
import { isConvexEnabled } from "@/lib/convex/enabled";
|
|
import { requireAuthContext } from "@/lib/auth/authContext";
|
|
import { getConvexHttpClient } from "@/lib/convex/server";
|
|
import { api } from "@/lib/convex/api";
|
|
import path from "path";
|
|
import { promises as fs } from "fs";
|
|
import { getDocumentsBaseDir } from "@/lib/server/local-paths";
|
|
|
|
interface DuplicatePayload {
|
|
documentId: string;
|
|
}
|
|
|
|
const documentsBaseDir = getDocumentsBaseDir();
|
|
|
|
async function ensureDocumentScaffold(id: string, title: string | null) {
|
|
const folder = path.join(documentsBaseDir, id);
|
|
const indexFile = path.join(folder, "index.md");
|
|
await fs.mkdir(folder, { recursive: true });
|
|
const safeTitle = title && title.trim() ? title.trim() : "无标题";
|
|
const content = `# ${safeTitle}\n`;
|
|
await fs.writeFile(indexFile, content, "utf8");
|
|
}
|
|
|
|
async function copyMindmapIfExists(sourceId: string, targetId: string) {
|
|
try {
|
|
const srcDir = path.join(documentsBaseDir, sourceId);
|
|
const destDir = path.join(documentsBaseDir, targetId);
|
|
const entries = await fs.readdir(srcDir);
|
|
const mindmapFiles = entries.filter((name) => name === "mindmap.json" || /^mindmap-.+\\.json$/i.test(name));
|
|
if (mindmapFiles.length === 0) return;
|
|
await fs.mkdir(destDir, { recursive: true });
|
|
await Promise.all(
|
|
mindmapFiles.map(async (name) => {
|
|
try {
|
|
const buf = await fs.readFile(path.join(srcDir, name));
|
|
await fs.writeFile(path.join(destDir, name), buf);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}),
|
|
);
|
|
} catch {
|
|
// 如果源不存在则忽略
|
|
}
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
if (isConvexEnabled()) {
|
|
const auth = await requireAuthContext();
|
|
const client = getConvexHttpClient();
|
|
|
|
const { documentId }: DuplicatePayload = await request.json();
|
|
if (!documentId) {
|
|
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
|
|
}
|
|
|
|
const sourceDoc = await client.query(api.documents.getMeta, { userId: auth.userId, id: documentId });
|
|
if (!sourceDoc) {
|
|
return NextResponse.json({ error: "页面不存在或无权访问" }, { status: 404 });
|
|
}
|
|
|
|
const fallbackTitle =
|
|
sourceDoc.title?.trim() && sourceDoc.title.trim().length > 0 ? sourceDoc.title.trim() : "无标题";
|
|
const duplicatedTitle = `${fallbackTitle} 副本`;
|
|
|
|
const newId = typeof crypto.randomUUID === "function"
|
|
? crypto.randomUUID()
|
|
: `${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
|
|
const duplicated = await client.mutation(api.documents.duplicate, {
|
|
userId: auth.userId,
|
|
sourceId: documentId,
|
|
newId,
|
|
title: duplicatedTitle,
|
|
});
|
|
|
|
await ensureDocumentScaffold(duplicated.id, duplicated.title ?? duplicatedTitle);
|
|
await client.mutation(api.mindmaps.copyByDocument, {
|
|
userId: auth.userId,
|
|
sourceDocId: documentId,
|
|
targetDocId: duplicated.id,
|
|
});
|
|
|
|
return NextResponse.json({
|
|
id: duplicated.id,
|
|
title: duplicated.title ?? duplicatedTitle,
|
|
parent_id: duplicated.parent_id ?? null,
|
|
sort_order: duplicated.sort_order ?? null,
|
|
});
|
|
}
|
|
|
|
const supabase = await createSupabaseRouteClient();
|
|
const {
|
|
data: { session },
|
|
} = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
|
}
|
|
|
|
const { documentId }: DuplicatePayload = await request.json();
|
|
if (!documentId) {
|
|
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
|
|
}
|
|
|
|
const { data: sourceDoc, error: sourceError } = await supabase
|
|
.from("documents")
|
|
.select("id,title,content,parent_id,workspace_id,access_scope")
|
|
.eq("id", documentId)
|
|
.eq("user_id", session.user.id)
|
|
.single();
|
|
|
|
if (sourceError || !sourceDoc) {
|
|
return NextResponse.json({ error: "页面不存在或无权访问" }, { status: 404 });
|
|
}
|
|
|
|
const siblingQuery = supabase
|
|
.from("documents")
|
|
.select("id", { head: true, count: "exact" })
|
|
.eq("workspace_id", sourceDoc.workspace_id);
|
|
|
|
if (sourceDoc.parent_id) {
|
|
siblingQuery.eq("parent_id", sourceDoc.parent_id);
|
|
} else {
|
|
siblingQuery.is("parent_id", null);
|
|
}
|
|
|
|
const { count: rawSiblingCount, error: countError } = await siblingQuery;
|
|
const siblingCount = rawSiblingCount ?? 0;
|
|
if (countError) {
|
|
return NextResponse.json({ error: countError.message }, { status: 500 });
|
|
}
|
|
|
|
const fallbackTitle =
|
|
sourceDoc.title?.trim() && sourceDoc.title.trim().length > 0
|
|
? sourceDoc.title.trim()
|
|
: "无标题";
|
|
const duplicatedTitle = `${fallbackTitle} 副本`;
|
|
|
|
const { data: duplicated, error: duplicateError } = await supabase
|
|
.from("documents")
|
|
.insert({
|
|
user_id: session.user.id,
|
|
workspace_id: sourceDoc.workspace_id,
|
|
parent_id: sourceDoc.parent_id,
|
|
access_scope: sourceDoc.access_scope ?? "private",
|
|
title: duplicatedTitle,
|
|
content: sourceDoc.content,
|
|
sort_order: siblingCount,
|
|
})
|
|
.select("id,title,parent_id,sort_order")
|
|
.single();
|
|
|
|
if (duplicateError || !duplicated) {
|
|
return NextResponse.json(
|
|
{ error: duplicateError?.message ?? "复制失败" },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
|
|
await ensureDocumentScaffold(duplicated.id, duplicated.title ?? duplicatedTitle);
|
|
await copyMindmapIfExists(sourceDoc.id, duplicated.id);
|
|
|
|
return NextResponse.json(duplicated);
|
|
}
|