0.3.1 UI修复
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createSupabaseRouteClient } from "@/lib/supabase/server";
|
||||
import { applyMindmapOps, type MindmapOp } from "@/lib/mindmap/mindmapOps";
|
||||
import { readMindmapLocal, writeMindmapLocal } from "@/lib/mindmap/mindmapLocalStore";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { getAuthedConvexClient } from "@/lib/convex/route";
|
||||
import { api } from "@/lib/convex/api";
|
||||
@@ -24,7 +22,7 @@ export async function POST(
|
||||
const { docId, mindmapId } = await params;
|
||||
|
||||
if (isConvexEnabled()) {
|
||||
const { auth, client } = await getAuthedConvexClient();
|
||||
const { client } = await getAuthedConvexClient();
|
||||
|
||||
const payload = (await request.json().catch(() => null)) as RequestPayload | null;
|
||||
const ops = Array.isArray(payload?.ops) ? payload!.ops : [];
|
||||
@@ -32,21 +30,15 @@ export async function POST(
|
||||
return NextResponse.json({ error: "缺少 ops" }, { status: 400 });
|
||||
}
|
||||
if (ops.length > 80) {
|
||||
return NextResponse.json({ error: "ops 过多(最多 80)" }, { status: 400 });
|
||||
return NextResponse.json({ error: "ops 过多(最大 80)" }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const current = await client.query(api.mindmaps.get, {
|
||||
userId: auth.userId,
|
||||
docId,
|
||||
mindmapId,
|
||||
});
|
||||
|
||||
const current = await client.query(api.mindmaps.get, { docId, mindmapId });
|
||||
const baseData = current?.data ?? defaultMindmapData;
|
||||
const { data: nextData, applied, errors } = applyMindmapOps(baseData, ops);
|
||||
|
||||
await client.mutation(api.mindmaps.put, {
|
||||
userId: auth.userId,
|
||||
docId,
|
||||
mindmapId,
|
||||
data: nextData,
|
||||
@@ -69,54 +61,6 @@ export async function POST(
|
||||
}
|
||||
}
|
||||
|
||||
const supabase = await createSupabaseRouteClient();
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
// 校验页面归属
|
||||
const { data: doc, error: docError } = await supabase
|
||||
.from("documents")
|
||||
.select("id,title,mindmap_data")
|
||||
.eq("id", docId)
|
||||
.eq("user_id", session.user.id)
|
||||
.maybeSingle();
|
||||
if (docError) {
|
||||
return NextResponse.json({ error: docError.message }, { status: 400 });
|
||||
}
|
||||
if (!doc) {
|
||||
return NextResponse.json({ error: "页面不存在" }, { status: 404 });
|
||||
}
|
||||
|
||||
const payload = (await request.json().catch(() => null)) as RequestPayload | null;
|
||||
const ops = Array.isArray(payload?.ops) ? payload!.ops : [];
|
||||
if (!ops.length) {
|
||||
return NextResponse.json({ error: "缺少 ops" }, { status: 400 });
|
||||
}
|
||||
if (ops.length > 80) {
|
||||
return NextResponse.json({ error: "ops 过多(最多 80)" }, { status: 400 });
|
||||
}
|
||||
|
||||
const local = await readMindmapLocal(docId, mindmapId);
|
||||
const baseData = local.ok ? local.data : (doc.mindmap_data ?? defaultMindmapData);
|
||||
|
||||
const { data: nextData, applied, errors } = applyMindmapOps(baseData, ops);
|
||||
|
||||
await writeMindmapLocal(docId, mindmapId, nextData, doc.title ?? "无标题");
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
applied,
|
||||
errors,
|
||||
data: nextData,
|
||||
meta: {
|
||||
documentId: docId,
|
||||
mindmapId,
|
||||
actor: payload?.actor ?? null,
|
||||
reason: payload?.reason ?? null,
|
||||
},
|
||||
});
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createSupabaseRouteClient } from "@/lib/supabase/server";
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { getDocumentsBaseDir, getLegacyMindmapsBaseDir } from "@/lib/server/local-paths";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { getAuthedConvexClient } from "@/lib/convex/route";
|
||||
import { api } from "@/lib/convex/api";
|
||||
@@ -12,104 +8,6 @@ const defaultMindmapData = {
|
||||
children: [],
|
||||
};
|
||||
|
||||
const preferredBaseDir = getDocumentsBaseDir();
|
||||
const legacyBaseDir = getLegacyMindmapsBaseDir();
|
||||
|
||||
async function ensureDir(dir: string) {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
}
|
||||
|
||||
async function removeFileSafe(file: string) {
|
||||
try {
|
||||
await fs.rm(file, { force: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureIndexFile(folder: string, title = "无标题") {
|
||||
const indexFile = path.join(folder, "index.md");
|
||||
try {
|
||||
await fs.access(indexFile);
|
||||
} catch {
|
||||
await fs.writeFile(indexFile, `# ${title}\n`, "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
function resolveMindmapFileName(mindmapId: string) {
|
||||
if (mindmapId === "legacy" || mindmapId.startsWith("legacy-")) {
|
||||
return "mindmap.json";
|
||||
}
|
||||
return `mindmap-${mindmapId}.json`;
|
||||
}
|
||||
|
||||
async function tryReadJson(file: string) {
|
||||
try {
|
||||
const content = await fs.readFile(file, "utf8");
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
type TrashedMindmapMeta = {
|
||||
docId: string;
|
||||
mindmapId: string;
|
||||
originalFileName: string;
|
||||
originalPath: string;
|
||||
trashedFileName: string;
|
||||
deleted_at: string;
|
||||
};
|
||||
|
||||
async function fileExists(file: string) {
|
||||
try {
|
||||
await fs.access(file);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function moveToTrash(file: string, meta: Omit<TrashedMindmapMeta, "trashedFileName">) {
|
||||
if (!(await fileExists(file))) {
|
||||
return null;
|
||||
}
|
||||
const dir = path.dirname(file);
|
||||
const trashDir = path.join(dir, ".trash");
|
||||
await ensureDir(trashDir);
|
||||
const originalFileName = path.basename(file);
|
||||
const trashedFileName = `${originalFileName}.${Date.now()}.deleted`;
|
||||
const trashedPath = path.join(trashDir, trashedFileName);
|
||||
const metaPath = path.join(trashDir, `${trashedFileName}.json`);
|
||||
await fs.rename(file, trashedPath);
|
||||
await fs.writeFile(
|
||||
metaPath,
|
||||
JSON.stringify({ ...meta, originalFileName, trashedFileName }, null, 2),
|
||||
"utf8",
|
||||
);
|
||||
return { trashedPath, metaPath };
|
||||
}
|
||||
|
||||
async function listTrashMetas(folder: string): Promise<Array<{ meta: TrashedMindmapMeta; metaPath: string; trashedPath: string }>> {
|
||||
const trashDir = path.join(folder, ".trash");
|
||||
try {
|
||||
const entries = await fs.readdir(trashDir);
|
||||
const results: Array<{ meta: TrashedMindmapMeta; metaPath: string; trashedPath: string }> = [];
|
||||
for (const name of entries) {
|
||||
if (!name.endsWith(".deleted.json")) continue;
|
||||
const metaPath = path.join(trashDir, name);
|
||||
const metaRaw = await tryReadJson(metaPath);
|
||||
const meta = metaRaw as TrashedMindmapMeta | null;
|
||||
if (!meta?.docId || !meta?.mindmapId || !meta?.trashedFileName || !meta?.originalPath) continue;
|
||||
const trashedPath = path.join(trashDir, meta.trashedFileName);
|
||||
results.push({ meta, metaPath, trashedPath });
|
||||
}
|
||||
return results;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
{ params }: { params: Promise<{ docId: string; mindmapId: string }> },
|
||||
@@ -117,51 +15,12 @@ export async function GET(
|
||||
const { docId, mindmapId } = await params;
|
||||
|
||||
if (isConvexEnabled()) {
|
||||
const { auth, client } = await getAuthedConvexClient();
|
||||
const res = await client.query(api.mindmaps.get, {
|
||||
userId: auth.userId,
|
||||
docId,
|
||||
mindmapId,
|
||||
});
|
||||
const { client } = await getAuthedConvexClient();
|
||||
const res = await client.query(api.mindmaps.get, { docId, mindmapId });
|
||||
return NextResponse.json({ data: res?.data ?? defaultMindmapData, source: "convex" });
|
||||
}
|
||||
|
||||
const supabase = await createSupabaseRouteClient();
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
const folder = path.join(preferredBaseDir, docId);
|
||||
const file = path.join(folder, resolveMindmapFileName(mindmapId));
|
||||
const legacyFile = path.join(folder, "mindmap.json");
|
||||
const legacyDirFile =
|
||||
path.basename(file) === "mindmap.json"
|
||||
? path.join(legacyBaseDir, docId, "mindmap.json")
|
||||
: null;
|
||||
|
||||
const localData =
|
||||
(await tryReadJson(file)) ??
|
||||
(await tryReadJson(legacyFile)) ??
|
||||
(legacyDirFile ? await tryReadJson(legacyDirFile) : null);
|
||||
if (localData) {
|
||||
return NextResponse.json({ data: localData, source: "local" });
|
||||
}
|
||||
|
||||
// 兼容旧版:没有本地文件时,回退到 documents.mindmap_data(仅能表示单个旧导图)
|
||||
const { data, error } = await supabase
|
||||
.from("documents")
|
||||
.select("mindmap_data")
|
||||
.eq("id", docId)
|
||||
.eq("user_id", session.user.id)
|
||||
.maybeSingle();
|
||||
if (error) {
|
||||
return NextResponse.json({ error: error.message }, { status: 400 });
|
||||
}
|
||||
const payload = data?.mindmap_data ?? defaultMindmapData;
|
||||
return NextResponse.json({ data: payload, source: "supabase" });
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
@@ -171,7 +30,7 @@ export async function POST(
|
||||
const { docId, mindmapId } = await params;
|
||||
|
||||
if (isConvexEnabled()) {
|
||||
const { auth, client } = await getAuthedConvexClient();
|
||||
const { client } = await getAuthedConvexClient();
|
||||
const { data, createOnly } = (await request.json().catch(() => ({ data: null }))) as {
|
||||
data?: unknown;
|
||||
createOnly?: boolean;
|
||||
@@ -179,59 +38,18 @@ export async function POST(
|
||||
|
||||
try {
|
||||
const result = await client.mutation(api.mindmaps.put, {
|
||||
userId: auth.userId,
|
||||
docId,
|
||||
mindmapId,
|
||||
data: data ?? defaultMindmapData,
|
||||
...(typeof createOnly === "boolean" ? { createOnly } : {}),
|
||||
});
|
||||
return NextResponse.json({ ok: true, ...(result ?? {}) });
|
||||
return NextResponse.json(result ?? { ok: true });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: (error as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
const supabase = await createSupabaseRouteClient();
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
// 校验页面归属(避免任意写入)
|
||||
const { data: doc, error: docError } = await supabase
|
||||
.from("documents")
|
||||
.select("id,title")
|
||||
.eq("id", docId)
|
||||
.eq("user_id", session.user.id)
|
||||
.maybeSingle();
|
||||
if (docError) {
|
||||
return NextResponse.json({ error: docError.message }, { status: 400 });
|
||||
}
|
||||
if (!doc) {
|
||||
return NextResponse.json({ error: "页面不存在" }, { status: 404 });
|
||||
}
|
||||
|
||||
const { data, createOnly } = (await request.json().catch(() => ({ data: null }))) as {
|
||||
data?: unknown;
|
||||
createOnly?: boolean;
|
||||
};
|
||||
const folder = path.join(preferredBaseDir, docId);
|
||||
const file = path.join(folder, resolveMindmapFileName(mindmapId));
|
||||
try {
|
||||
await ensureDir(folder);
|
||||
await ensureIndexFile(folder, doc.title ?? "无标题");
|
||||
// 仅创建:避免“初始化写入”覆盖用户/AI 刚保存的内容(典型于快速操作 + 异步时序)
|
||||
if (createOnly && (await fileExists(file))) {
|
||||
return NextResponse.json({ ok: true, created: false, skipped: true });
|
||||
}
|
||||
await fs.writeFile(file, JSON.stringify(data ?? defaultMindmapData, null, 2), "utf8");
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: String(error) }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true, created: true });
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
@@ -241,72 +59,16 @@ export async function DELETE(
|
||||
const { docId, mindmapId } = await params;
|
||||
|
||||
if (isConvexEnabled()) {
|
||||
const { auth, client } = await getAuthedConvexClient();
|
||||
const { client } = await getAuthedConvexClient();
|
||||
try {
|
||||
const result = await client.mutation(api.mindmaps.softDelete, {
|
||||
userId: auth.userId,
|
||||
docId,
|
||||
mindmapId,
|
||||
});
|
||||
return NextResponse.json({ ok: true, ...(result ?? {}) });
|
||||
const result = await client.mutation(api.mindmaps.softDelete, { docId, mindmapId });
|
||||
return NextResponse.json(result ?? { ok: true });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: (error as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
const supabase = await createSupabaseRouteClient();
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
// 校验页面归属
|
||||
const { data: doc, error: docError } = await supabase
|
||||
.from("documents")
|
||||
.select("id")
|
||||
.eq("id", docId)
|
||||
.eq("user_id", session.user.id)
|
||||
.maybeSingle();
|
||||
if (docError) {
|
||||
return NextResponse.json({ error: docError.message }, { status: 400 });
|
||||
}
|
||||
if (!doc) {
|
||||
return NextResponse.json({ error: "页面不存在" }, { status: 404 });
|
||||
}
|
||||
|
||||
const deletedAt = new Date().toISOString();
|
||||
const fileName = resolveMindmapFileName(mindmapId);
|
||||
|
||||
const preferredFolder = path.join(preferredBaseDir, docId);
|
||||
const preferredFile = path.join(preferredFolder, fileName);
|
||||
|
||||
const candidates: string[] = [preferredFile];
|
||||
if (fileName === "mindmap.json") {
|
||||
candidates.push(path.join(legacyBaseDir, docId, "mindmap.json"));
|
||||
}
|
||||
|
||||
let moved = 0;
|
||||
for (const file of candidates) {
|
||||
const movedInfo = await moveToTrash(file, {
|
||||
docId,
|
||||
mindmapId,
|
||||
originalFileName: path.basename(file),
|
||||
originalPath: file,
|
||||
deleted_at: deletedAt,
|
||||
});
|
||||
if (movedInfo) {
|
||||
moved += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (moved === 0) {
|
||||
// 兼容:文件不存在也视为成功(避免前端卡死)
|
||||
return NextResponse.json({ ok: true, moved: 0 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true, moved });
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
@@ -316,7 +78,7 @@ export async function PATCH(
|
||||
const { docId, mindmapId } = await params;
|
||||
|
||||
if (isConvexEnabled()) {
|
||||
const { auth, client } = await getAuthedConvexClient();
|
||||
const { client } = await getAuthedConvexClient();
|
||||
const { action } = (await request.json().catch(() => ({}))) as { action?: string };
|
||||
if (action !== "restore" && action !== "purge") {
|
||||
return NextResponse.json({ error: "不支持的操作" }, { status: 400 });
|
||||
@@ -324,20 +86,11 @@ export async function PATCH(
|
||||
|
||||
try {
|
||||
if (action === "purge") {
|
||||
const result = await client.mutation(api.mindmaps.purge, {
|
||||
userId: auth.userId,
|
||||
docId,
|
||||
mindmapId,
|
||||
});
|
||||
return NextResponse.json({ ok: true, ...(result ?? {}) });
|
||||
const result = await client.mutation(api.mindmaps.purge, { docId, mindmapId });
|
||||
return NextResponse.json(result ?? { ok: true });
|
||||
}
|
||||
|
||||
const result = await client.mutation(api.mindmaps.restore, {
|
||||
userId: auth.userId,
|
||||
docId,
|
||||
mindmapId,
|
||||
});
|
||||
return NextResponse.json({ ok: true, ...(result ?? {}) });
|
||||
const result = await client.mutation(api.mindmaps.restore, { docId, mindmapId });
|
||||
return NextResponse.json(result ?? { ok: true });
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message ?? "操作失败";
|
||||
const status = msg.includes("未找到") ? 404 : 400;
|
||||
@@ -345,68 +98,6 @@ export async function PATCH(
|
||||
}
|
||||
}
|
||||
|
||||
const supabase = await createSupabaseRouteClient();
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { action } = (await request.json().catch(() => ({}))) as { action?: string };
|
||||
if (action !== "restore" && action !== "purge") {
|
||||
return NextResponse.json({ error: "不支持的操作" }, { status: 400 });
|
||||
}
|
||||
|
||||
const { data: doc, error: docError } = await supabase
|
||||
.from("documents")
|
||||
.select("id")
|
||||
.eq("id", docId)
|
||||
.eq("user_id", session.user.id)
|
||||
.maybeSingle();
|
||||
if (docError) {
|
||||
return NextResponse.json({ error: docError.message }, { status: 400 });
|
||||
}
|
||||
if (!doc) {
|
||||
return NextResponse.json({ error: "页面不存在" }, { status: 404 });
|
||||
}
|
||||
|
||||
const preferredFolder = path.join(preferredBaseDir, docId);
|
||||
const legacyFolder = path.join(legacyBaseDir, docId);
|
||||
const entries = [
|
||||
...(await listTrashMetas(preferredFolder)),
|
||||
...(await listTrashMetas(legacyFolder)),
|
||||
].filter((item) => item.meta.mindmapId === mindmapId && item.meta.docId === docId);
|
||||
|
||||
if (entries.length === 0) {
|
||||
return NextResponse.json({ error: "未找到可操作的垃圾桶记录" }, { status: 404 });
|
||||
}
|
||||
|
||||
if (action === "purge") {
|
||||
let purged = 0;
|
||||
for (const item of entries) {
|
||||
await removeFileSafe(item.trashedPath);
|
||||
await removeFileSafe(item.metaPath);
|
||||
purged += 1;
|
||||
}
|
||||
return NextResponse.json({ ok: true, purged });
|
||||
}
|
||||
|
||||
// restore:恢复最新的一条
|
||||
const latest = entries
|
||||
.slice()
|
||||
.sort((a, b) => (b.meta.deleted_at ?? "").localeCompare(a.meta.deleted_at ?? ""))[0];
|
||||
|
||||
if (!latest?.meta?.originalPath) {
|
||||
return NextResponse.json({ error: "垃圾桶记录损坏" }, { status: 500 });
|
||||
}
|
||||
|
||||
if (await fileExists(latest.meta.originalPath)) {
|
||||
return NextResponse.json({ error: "目标文件已存在,无法恢复" }, { status: 409 });
|
||||
}
|
||||
|
||||
await ensureDir(path.dirname(latest.meta.originalPath));
|
||||
await fs.rename(latest.trashedPath, latest.meta.originalPath);
|
||||
await removeFileSafe(latest.metaPath);
|
||||
return NextResponse.json({ ok: true });
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createSupabaseRouteClient } from "@/lib/supabase/server";
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { getDocumentsBaseDir, getLegacyMindmapsBaseDir } from "@/lib/server/local-paths";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { getAuthedConvexClient } from "@/lib/convex/route";
|
||||
import { api } from "@/lib/convex/api";
|
||||
@@ -12,189 +8,56 @@ const defaultMindmapData = {
|
||||
children: [],
|
||||
};
|
||||
|
||||
// 新版:与页面文件夹(index.md 所在处)对齐,放在 public/documents/<docId>/mindmap.json
|
||||
// 旧版遗留:public/mindmaps/<docId>/mindmap.json
|
||||
const preferredBaseDir = getDocumentsBaseDir();
|
||||
const legacyBaseDir = getLegacyMindmapsBaseDir();
|
||||
|
||||
async function ensureDir(dir: string) {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
}
|
||||
|
||||
async function removeFileSafe(file: string) {
|
||||
try {
|
||||
await fs.rm(file, { force: true });
|
||||
} catch {
|
||||
// 忽略删除失败(例如文件不存在)
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureIndexFile(folder: string, title = "无标题") {
|
||||
const indexFile = path.join(folder, "index.md");
|
||||
try {
|
||||
await fs.access(indexFile);
|
||||
} catch {
|
||||
await fs.writeFile(indexFile, `# ${title}\n`, "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
{ params }: { params: Promise<{ docId: string }> },
|
||||
) {
|
||||
const { docId: id } = await params;
|
||||
const { docId } = await params;
|
||||
|
||||
if (isConvexEnabled()) {
|
||||
const { auth, client } = await getAuthedConvexClient();
|
||||
const mindmapId = `legacy-${id}`;
|
||||
const res = await client.query(api.mindmaps.get, {
|
||||
userId: auth.userId,
|
||||
docId: id,
|
||||
mindmapId,
|
||||
});
|
||||
const { client } = await getAuthedConvexClient();
|
||||
const mindmapId = `legacy-${docId}`;
|
||||
const res = await client.query(api.mindmaps.get, { docId, mindmapId });
|
||||
return NextResponse.json({ data: res?.data ?? defaultMindmapData, source: "convex" });
|
||||
}
|
||||
|
||||
const supabase = await createSupabaseRouteClient();
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
const preferredFolder = path.join(preferredBaseDir, id);
|
||||
const preferredFile = path.join(preferredFolder, "mindmap.json");
|
||||
const legacyFolder = path.join(legacyBaseDir, id);
|
||||
const legacyFile = path.join(legacyFolder, "mindmap.json");
|
||||
|
||||
const tryRead = async (file: string) => {
|
||||
try {
|
||||
const content = await fs.readFile(file, "utf8");
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const localData =
|
||||
(await tryRead(preferredFile)) ??
|
||||
(await tryRead(legacyFile));
|
||||
|
||||
if (localData) {
|
||||
return NextResponse.json({ data: localData, source: "local" });
|
||||
}
|
||||
|
||||
// fallback Supabase
|
||||
const { data, error } = await supabase
|
||||
.from("documents")
|
||||
.select("mindmap_data")
|
||||
.eq("id", id)
|
||||
.eq("user_id", session.user.id)
|
||||
.maybeSingle();
|
||||
if (error) {
|
||||
return NextResponse.json({ error: error.message }, { status: 400 });
|
||||
}
|
||||
const payload = data?.mindmap_data ?? defaultMindmapData;
|
||||
return NextResponse.json({ data: payload, source: "supabase" });
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ docId: string }> },
|
||||
) {
|
||||
const { docId: id } = await params;
|
||||
const { docId } = await params;
|
||||
|
||||
if (isConvexEnabled()) {
|
||||
const { auth, client } = await getAuthedConvexClient();
|
||||
const mindmapId = `legacy-${id}`;
|
||||
const { data } = await request.json();
|
||||
const { client } = await getAuthedConvexClient();
|
||||
const mindmapId = `legacy-${docId}`;
|
||||
const payload = (await request.json().catch(() => ({}))) as { data?: unknown };
|
||||
const result = await client.mutation(api.mindmaps.put, {
|
||||
userId: auth.userId,
|
||||
docId: id,
|
||||
docId,
|
||||
mindmapId,
|
||||
data: data ?? defaultMindmapData,
|
||||
data: payload.data ?? defaultMindmapData,
|
||||
});
|
||||
return NextResponse.json({ ok: true, ...(result ?? {}) });
|
||||
return NextResponse.json(result ?? { ok: true });
|
||||
}
|
||||
|
||||
const supabase = await createSupabaseRouteClient();
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { data } = await request.json();
|
||||
const folder = path.join(preferredBaseDir, id);
|
||||
const file = path.join(folder, "mindmap.json");
|
||||
try {
|
||||
await ensureDir(folder);
|
||||
await ensureIndexFile(folder);
|
||||
await fs.writeFile(
|
||||
file,
|
||||
JSON.stringify(data ?? defaultMindmapData, null, 2),
|
||||
"utf8",
|
||||
);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: String(error) }, { status: 500 });
|
||||
}
|
||||
|
||||
const { error } = await supabase
|
||||
.from("documents")
|
||||
.update({ mindmap_data: data ?? defaultMindmapData })
|
||||
.eq("id", id)
|
||||
.eq("user_id", session.user.id);
|
||||
|
||||
if (error) {
|
||||
return NextResponse.json({ error: error.message }, { status: 400 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
_req: Request,
|
||||
{ params }: { params: Promise<{ docId: string }> },
|
||||
) {
|
||||
const { docId: id } = await params;
|
||||
const { docId } = await params;
|
||||
|
||||
if (isConvexEnabled()) {
|
||||
const { auth, client } = await getAuthedConvexClient();
|
||||
const mindmapId = `legacy-${id}`;
|
||||
const result = await client.mutation(api.mindmaps.softDelete, {
|
||||
userId: auth.userId,
|
||||
docId: id,
|
||||
mindmapId,
|
||||
});
|
||||
return NextResponse.json({ ok: true, ...(result ?? {}) });
|
||||
const { client } = await getAuthedConvexClient();
|
||||
const mindmapId = `legacy-${docId}`;
|
||||
const result = await client.mutation(api.mindmaps.softDelete, { docId, mindmapId });
|
||||
return NextResponse.json(result ?? { ok: true });
|
||||
}
|
||||
|
||||
const supabase = await createSupabaseRouteClient();
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
const preferredFolder = path.join(preferredBaseDir, id);
|
||||
const preferredFile = path.join(preferredFolder, "mindmap.json");
|
||||
const legacyFolder = path.join(legacyBaseDir, id);
|
||||
const legacyFile = path.join(legacyFolder, "mindmap.json");
|
||||
|
||||
await Promise.all([removeFileSafe(preferredFile), removeFileSafe(legacyFile)]);
|
||||
|
||||
const { error } = await supabase
|
||||
.from("documents")
|
||||
.update({ mindmap_data: null })
|
||||
.eq("id", id)
|
||||
.eq("user_id", session.user.id);
|
||||
|
||||
if (error) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user