4-26 树rust-2
This commit is contained in:
@@ -1,23 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { api } from "@/lib/convex/api";
|
||||
import { getAuthedConvexClient } from "@/lib/convex/route";
|
||||
import {
|
||||
assertDocumentId,
|
||||
buildDocumentBridgeContext,
|
||||
buildDocumentCommandEnvelope,
|
||||
documentBridgeErrorResponse,
|
||||
} from "@/lib/documents/bridge";
|
||||
import {
|
||||
executePageLifecycleBridgeCommand,
|
||||
type DocumentCopyTreePayload,
|
||||
} from "@/lib/documents/page-command-adapter";
|
||||
import {
|
||||
copyMindmapFilesIfExists,
|
||||
ensureDocumentScaffold,
|
||||
} from "@/lib/documents/page-lifecycle-side-effects";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type CopyTreeItem = {
|
||||
documentId: string;
|
||||
@@ -25,102 +7,88 @@ type CopyTreeItem = {
|
||||
};
|
||||
|
||||
type CopyTreePayload = {
|
||||
items: CopyTreeItem[];
|
||||
targetParentId: string | null;
|
||||
items?: CopyTreeItem[] | null;
|
||||
targetParentId?: string | null;
|
||||
};
|
||||
|
||||
type TreeCopyResponse = {
|
||||
requestId?: string;
|
||||
traceId?: string;
|
||||
result?: {
|
||||
items?: Array<{
|
||||
oldId: string;
|
||||
newId: string;
|
||||
}>;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
if (isConvexEnabled()) {
|
||||
try {
|
||||
const { client } = await getAuthedConvexClient();
|
||||
const payload = (await request.json()) as CopyTreePayload;
|
||||
const normalizedItems = (payload.items ?? [])
|
||||
.filter((item) => item?.documentId)
|
||||
.map((item) => ({
|
||||
documentId: assertDocumentId(item.documentId),
|
||||
recursive: Boolean(item.recursive),
|
||||
}));
|
||||
|
||||
if (normalizedItems.length === 0) {
|
||||
return NextResponse.json({ error: "items 为空" }, { status: 400 });
|
||||
}
|
||||
|
||||
const normalizedTargetParentId = payload.targetParentId?.trim() || null;
|
||||
let workspaceId: string | null = null;
|
||||
|
||||
if (normalizedTargetParentId) {
|
||||
const targetDoc = await client.query(api.documents.getMeta, { id: normalizedTargetParentId });
|
||||
if (!targetDoc) {
|
||||
return NextResponse.json({ error: "目标页面不存在或无权限" }, { status: 404 });
|
||||
}
|
||||
workspaceId = targetDoc.workspace_id?.trim() || null;
|
||||
} else {
|
||||
const firstDoc = await client.query(api.documents.getMeta, { id: normalizedItems[0].documentId });
|
||||
if (!firstDoc) {
|
||||
return NextResponse.json({ error: "源页面不存在或无权限" }, { status: 404 });
|
||||
}
|
||||
workspaceId = firstDoc.workspace_id?.trim() || null;
|
||||
}
|
||||
|
||||
if (!workspaceId) {
|
||||
return NextResponse.json({ error: "缺少目标工作空间" }, { status: 400 });
|
||||
}
|
||||
|
||||
const bridgeContext = await buildDocumentBridgeContext({
|
||||
request,
|
||||
workspaceId,
|
||||
});
|
||||
const result = await executePageLifecycleBridgeCommand<
|
||||
DocumentCopyTreePayload,
|
||||
{
|
||||
items: Array<{
|
||||
oldId: string;
|
||||
newId: string;
|
||||
title?: string | null;
|
||||
}>;
|
||||
}
|
||||
>({
|
||||
context: bridgeContext,
|
||||
envelope: buildDocumentCommandEnvelope({
|
||||
name: "documents.copy_tree",
|
||||
payload: {
|
||||
workspaceId,
|
||||
targetParentId: normalizedTargetParentId,
|
||||
items: normalizedItems,
|
||||
},
|
||||
context: bridgeContext,
|
||||
target: {
|
||||
workspaceId,
|
||||
pageId: normalizedTargetParentId,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
result.result.items.map(async (item) => {
|
||||
await ensureDocumentScaffold(item.newId, item.title ?? "无标题");
|
||||
await copyMindmapFilesIfExists(item.oldId, item.newId);
|
||||
}),
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
items: result.result.items.map((item) => ({
|
||||
oldId: item.oldId,
|
||||
newId: item.newId,
|
||||
})),
|
||||
meta: {
|
||||
requestId: result.requestId,
|
||||
traceId: result.traceId,
|
||||
commandId: result.commandId,
|
||||
commandName: result.commandName,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return documentBridgeErrorResponse(error);
|
||||
}
|
||||
if (!isConvexEnabled()) {
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
try {
|
||||
const payload = (await request.json()) as CopyTreePayload;
|
||||
const normalizedItems = (payload.items ?? [])
|
||||
.filter((item) => item?.documentId)
|
||||
.map((item) => ({
|
||||
documentId: item.documentId.trim(),
|
||||
recursive: Boolean(item.recursive),
|
||||
}))
|
||||
.filter((item) => item.documentId.length > 0);
|
||||
|
||||
if (normalizedItems.length === 0) {
|
||||
return NextResponse.json({ error: "items 为空" }, { status: 400 });
|
||||
}
|
||||
|
||||
const upstreamUrl = new URL("/api/tree/commands", request.url);
|
||||
const response = await fetch(upstreamUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: new Headers({
|
||||
"content-type": "application/json",
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
action: "copy",
|
||||
targetParentId:
|
||||
typeof payload.targetParentId === "string" ? payload.targetParentId.trim() || null : null,
|
||||
items: normalizedItems,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = (await response.json().catch(() => null)) as
|
||||
| TreeCopyResponse
|
||||
| { error?: string }
|
||||
| null;
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
result && typeof result === "object" && "error" in result && typeof result.error === "string"
|
||||
? result.error
|
||||
: "复制页面失败,请稍后再试",
|
||||
},
|
||||
{ status: response.status },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
items: result?.result?.items ?? [],
|
||||
meta: {
|
||||
requestId: result?.requestId,
|
||||
traceId: result?.traceId,
|
||||
commandName: "tree.subtree.copy",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : "复制页面失败,请稍后再试",
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
@@ -1,59 +1,75 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import {
|
||||
assertDocumentId,
|
||||
buildDocumentBridgeContext,
|
||||
buildDocumentCommandEnvelope,
|
||||
documentBridgeErrorResponse,
|
||||
} from "@/lib/documents/bridge";
|
||||
import {
|
||||
executePageLifecycleBridgeCommand,
|
||||
type DocumentDeletePayload,
|
||||
} from "@/lib/documents/page-command-adapter";
|
||||
|
||||
type TreeArchiveResponse = {
|
||||
requestId?: string;
|
||||
traceId?: string;
|
||||
};
|
||||
|
||||
type DeletePayload = {
|
||||
documentId?: string | null;
|
||||
workspaceId?: string | null;
|
||||
};
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
if (isConvexEnabled()) {
|
||||
try {
|
||||
const body = (await request.json()) as { documentId?: string; workspaceId?: string | null };
|
||||
const normalizedDocumentId = assertDocumentId(body.documentId);
|
||||
const normalizedWorkspaceId = body.workspaceId?.trim() || null;
|
||||
const bridgeContext = await buildDocumentBridgeContext({
|
||||
request,
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
});
|
||||
const result = await executePageLifecycleBridgeCommand<DocumentDeletePayload, { ok: boolean }>({
|
||||
context: bridgeContext,
|
||||
envelope: buildDocumentCommandEnvelope({
|
||||
name: "documents.delete",
|
||||
payload: {
|
||||
documentId: normalizedDocumentId,
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
},
|
||||
context: bridgeContext,
|
||||
target: {
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
pageId: normalizedDocumentId,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
meta: {
|
||||
requestId: result.requestId,
|
||||
traceId: result.traceId,
|
||||
commandId: result.commandId,
|
||||
commandName: result.commandName,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return documentBridgeErrorResponse(error);
|
||||
}
|
||||
if (!isConvexEnabled()) {
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
try {
|
||||
const { documentId, workspaceId }: DeletePayload = await request.json();
|
||||
const normalizedDocumentId = typeof documentId === "string" ? documentId.trim() : "";
|
||||
if (!normalizedDocumentId) {
|
||||
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
|
||||
}
|
||||
|
||||
const upstreamUrl = new URL("/api/tree/commands", request.url);
|
||||
const response = await fetch(upstreamUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: new Headers({
|
||||
"content-type": "application/json",
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
action: "archive",
|
||||
documentId: normalizedDocumentId,
|
||||
workspaceId: typeof workspaceId === "string" ? workspaceId.trim() || null : null,
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = (await response.json().catch(() => null)) as
|
||||
| TreeArchiveResponse
|
||||
| { error?: string }
|
||||
| null;
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
payload && typeof payload === "object" && "error" in payload && typeof payload.error === "string"
|
||||
? payload.error
|
||||
: "删除失败,请稍后再试",
|
||||
},
|
||||
{ status: response.status },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
meta: {
|
||||
requestId: payload?.requestId,
|
||||
traceId: payload?.traceId,
|
||||
commandName: "tree.node.archive",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : "删除失败,请稍后再试",
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
@@ -1,11 +1,74 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { executeDocumentEmbedBridgeCommand } from "@/lib/documents/page-command-adapter";
|
||||
|
||||
type TreeEmbedResponse = {
|
||||
requestId?: string;
|
||||
traceId?: string;
|
||||
};
|
||||
|
||||
type EmbedPayload = {
|
||||
sourceId?: string | null;
|
||||
targetId?: string | null;
|
||||
};
|
||||
|
||||
export async function POST(request: Request) {
|
||||
if (isConvexEnabled()) {
|
||||
return executeDocumentEmbedBridgeCommand(request);
|
||||
if (!isConvexEnabled()) {
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
try {
|
||||
const { sourceId, targetId }: EmbedPayload = await request.json();
|
||||
const normalizedSourceId = typeof sourceId === "string" ? sourceId.trim() : "";
|
||||
const normalizedTargetId = typeof targetId === "string" ? targetId.trim() : "";
|
||||
if (!normalizedSourceId || !normalizedTargetId) {
|
||||
return NextResponse.json({ error: "缺少 sourceId 或 targetId" }, { status: 400 });
|
||||
}
|
||||
|
||||
const upstreamUrl = new URL("/api/tree/commands", request.url);
|
||||
const response = await fetch(upstreamUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: new Headers({
|
||||
"content-type": "application/json",
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
action: "embed",
|
||||
sourceId: normalizedSourceId,
|
||||
targetId: normalizedTargetId,
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = (await response.json().catch(() => null)) as
|
||||
| TreeEmbedResponse
|
||||
| { error?: string }
|
||||
| null;
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
payload && typeof payload === "object" && "error" in payload && typeof payload.error === "string"
|
||||
? payload.error
|
||||
: "嵌入失败,请稍后再试",
|
||||
},
|
||||
{ status: response.status },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
meta: {
|
||||
requestId: payload?.requestId,
|
||||
traceId: payload?.traceId,
|
||||
commandName: "tree.node.embed",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : "嵌入失败,请稍后再试",
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
@@ -1,13 +1,77 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { executeDocumentPurgeBridgeCommand } from "@/lib/documents/page-command-adapter";
|
||||
|
||||
type TreePurgeResponse = {
|
||||
requestId?: string;
|
||||
traceId?: string;
|
||||
result?: {
|
||||
purged?: boolean;
|
||||
} | null;
|
||||
};
|
||||
|
||||
type PurgePayload = {
|
||||
documentId?: string | null;
|
||||
};
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
if (isConvexEnabled()) {
|
||||
return executeDocumentPurgeBridgeCommand(request);
|
||||
if (!isConvexEnabled()) {
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
try {
|
||||
const { documentId }: PurgePayload = await request.json();
|
||||
const normalizedDocumentId = typeof documentId === "string" ? documentId.trim() : "";
|
||||
if (!normalizedDocumentId) {
|
||||
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
|
||||
}
|
||||
|
||||
const upstreamUrl = new URL("/api/tree/commands", request.url);
|
||||
const response = await fetch(upstreamUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: new Headers({
|
||||
"content-type": "application/json",
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
action: "purge",
|
||||
documentId: normalizedDocumentId,
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = (await response.json().catch(() => null)) as
|
||||
| TreePurgeResponse
|
||||
| { error?: string }
|
||||
| null;
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
payload && typeof payload === "object" && "error" in payload && typeof payload.error === "string"
|
||||
? payload.error
|
||||
: "彻底删除失败,请稍后再试",
|
||||
},
|
||||
{ status: response.status },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
purged: payload?.result?.purged ?? true,
|
||||
meta: {
|
||||
requestId: payload?.requestId,
|
||||
traceId: payload?.traceId,
|
||||
commandName: "tree.node.purge",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : "彻底删除失败,请稍后再试",
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
@@ -1,59 +1,75 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import {
|
||||
assertDocumentId,
|
||||
buildDocumentBridgeContext,
|
||||
buildDocumentCommandEnvelope,
|
||||
documentBridgeErrorResponse,
|
||||
} from "@/lib/documents/bridge";
|
||||
import {
|
||||
executePageLifecycleBridgeCommand,
|
||||
type DocumentRestorePayload,
|
||||
} from "@/lib/documents/page-command-adapter";
|
||||
|
||||
type TreeRestoreResponse = {
|
||||
requestId?: string;
|
||||
traceId?: string;
|
||||
};
|
||||
|
||||
type RestorePayload = {
|
||||
documentId?: string | null;
|
||||
workspaceId?: string | null;
|
||||
};
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
if (isConvexEnabled()) {
|
||||
try {
|
||||
const body = (await request.json()) as { documentId?: string; workspaceId?: string | null };
|
||||
const normalizedDocumentId = assertDocumentId(body.documentId);
|
||||
const normalizedWorkspaceId = body.workspaceId?.trim() || null;
|
||||
const bridgeContext = await buildDocumentBridgeContext({
|
||||
request,
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
});
|
||||
const result = await executePageLifecycleBridgeCommand<DocumentRestorePayload, { ok: boolean }>({
|
||||
context: bridgeContext,
|
||||
envelope: buildDocumentCommandEnvelope({
|
||||
name: "documents.restore",
|
||||
payload: {
|
||||
documentId: normalizedDocumentId,
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
},
|
||||
context: bridgeContext,
|
||||
target: {
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
pageId: normalizedDocumentId,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
meta: {
|
||||
requestId: result.requestId,
|
||||
traceId: result.traceId,
|
||||
commandId: result.commandId,
|
||||
commandName: result.commandName,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return documentBridgeErrorResponse(error);
|
||||
}
|
||||
if (!isConvexEnabled()) {
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
try {
|
||||
const { documentId, workspaceId }: RestorePayload = await request.json();
|
||||
const normalizedDocumentId = typeof documentId === "string" ? documentId.trim() : "";
|
||||
if (!normalizedDocumentId) {
|
||||
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
|
||||
}
|
||||
|
||||
const upstreamUrl = new URL("/api/tree/commands", request.url);
|
||||
const response = await fetch(upstreamUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: new Headers({
|
||||
"content-type": "application/json",
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
action: "restore",
|
||||
documentId: normalizedDocumentId,
|
||||
workspaceId: typeof workspaceId === "string" ? workspaceId.trim() || null : null,
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = (await response.json().catch(() => null)) as
|
||||
| TreeRestoreResponse
|
||||
| { error?: string }
|
||||
| null;
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
payload && typeof payload === "object" && "error" in payload && typeof payload.error === "string"
|
||||
? payload.error
|
||||
: "恢复失败,请稍后再试",
|
||||
},
|
||||
{ status: response.status },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
meta: {
|
||||
requestId: payload?.requestId,
|
||||
traceId: payload?.traceId,
|
||||
commandName: "tree.node.restore",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : "恢复失败,请稍后再试",
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
@@ -72,22 +72,23 @@ vi.mock("@/lib/documents/page-aggregate-loader", () => ({
|
||||
}));
|
||||
|
||||
import { POST as postCreateChild } from "@/app/api/documents/create-child/route";
|
||||
import { POST as postDelete } from "@/app/api/documents/delete/route";
|
||||
import { POST as postEmbed } from "@/app/api/documents/embed/route";
|
||||
import { POST as postTemplate } from "@/app/api/documents/template/route";
|
||||
import { POST as postEmptyTrash } from "@/app/api/documents/empty-trash/route";
|
||||
import { POST as postPurge } from "@/app/api/documents/purge/route";
|
||||
import { POST as postRestore } from "@/app/api/documents/restore/route";
|
||||
import { POST as postTitle } from "@/app/api/documents/title/route";
|
||||
import { POST as postOptions } from "@/app/api/documents/options/route";
|
||||
import { POST as postSave } from "@/app/api/documents/save/route";
|
||||
import { POST as postCopyTree } from "@/app/api/documents/copy-tree/route";
|
||||
import { POST as postCreate } from "@/app/api/documents/create/route";
|
||||
import { POST as postMove } from "@/app/api/documents/move/route";
|
||||
import { GET as getPage } from "@/app/api/documents/page/route";
|
||||
import {
|
||||
executeDocumentCreateChildBridgeCommand,
|
||||
executeDocumentEmbedBridgeCommand,
|
||||
executeDocumentTemplateBridgeCommand,
|
||||
executeDocumentEmptyTrashBridgeCommand,
|
||||
executeDocumentPurgeBridgeCommand,
|
||||
} from "@/lib/documents/page-command-adapter";
|
||||
import { executePageWriteBridgeCommand } from "@/lib/documents/page-write-command-adapter";
|
||||
import { loadPageAggregate } from "@/lib/documents/page-aggregate-loader";
|
||||
@@ -246,12 +247,115 @@ describe("documents route adapters", () => {
|
||||
expect(executeDocumentCreateChildBridgeCommand).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("embed route delegates to unified adapter", async () => {
|
||||
await postEmbed(new Request("http://localhost/api/documents/embed", {
|
||||
it("delete route 作为 compat 壳委托 tree commands 主路径", async () => {
|
||||
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
requestId: "req_tree_archive_1",
|
||||
traceId: "trace_tree_archive_1",
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
);
|
||||
|
||||
const response = await postDelete(new Request("http://localhost/api/documents/delete", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ documentId: "doc_1", workspaceId: "ws_1" }),
|
||||
}));
|
||||
const payload = await response.json() as {
|
||||
success: boolean;
|
||||
meta: { commandName: string };
|
||||
};
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"http://localhost/api/tree/commands",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.any(Headers),
|
||||
body: JSON.stringify({
|
||||
action: "archive",
|
||||
documentId: "doc_1",
|
||||
workspaceId: "ws_1",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(payload.success).toBe(true);
|
||||
expect(payload.meta.commandName).toBe("tree.node.archive");
|
||||
});
|
||||
|
||||
it("restore route 作为 compat 壳委托 tree commands 主路径", async () => {
|
||||
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
requestId: "req_tree_restore_1",
|
||||
traceId: "trace_tree_restore_1",
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
);
|
||||
|
||||
const response = await postRestore(new Request("http://localhost/api/documents/restore", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ documentId: "doc_1", workspaceId: "ws_1" }),
|
||||
}));
|
||||
const payload = await response.json() as {
|
||||
success: boolean;
|
||||
meta: { commandName: string };
|
||||
};
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"http://localhost/api/tree/commands",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.any(Headers),
|
||||
body: JSON.stringify({
|
||||
action: "restore",
|
||||
documentId: "doc_1",
|
||||
workspaceId: "ws_1",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(payload.success).toBe(true);
|
||||
expect(payload.meta.commandName).toBe("tree.node.restore");
|
||||
});
|
||||
|
||||
it("embed route 作为 compat 壳委托 tree commands 主路径", async () => {
|
||||
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
requestId: "req_tree_embed_1",
|
||||
traceId: "trace_tree_embed_1",
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
);
|
||||
|
||||
const response = await postEmbed(new Request("http://localhost/api/documents/embed", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ sourceId: "doc_1", targetId: "doc_2" }),
|
||||
}));
|
||||
expect(executeDocumentEmbedBridgeCommand).toHaveBeenCalled();
|
||||
const payload = await response.json() as {
|
||||
ok: boolean;
|
||||
meta: { commandName: string };
|
||||
};
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"http://localhost/api/tree/commands",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.any(Headers),
|
||||
body: JSON.stringify({
|
||||
action: "embed",
|
||||
sourceId: "doc_1",
|
||||
targetId: "doc_2",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(payload.ok).toBe(true);
|
||||
expect(payload.meta.commandName).toBe("tree.node.embed");
|
||||
});
|
||||
|
||||
it("template route delegates to unified adapter", async () => {
|
||||
@@ -270,12 +374,85 @@ describe("documents route adapters", () => {
|
||||
expect(executeDocumentEmptyTrashBridgeCommand).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("purge route delegates to unified adapter", async () => {
|
||||
await postPurge(new Request("http://localhost/api/documents/purge", {
|
||||
it("purge route 作为 compat 壳委托 tree commands 主路径", async () => {
|
||||
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
requestId: "req_tree_purge_1",
|
||||
traceId: "trace_tree_purge_1",
|
||||
result: {
|
||||
purged: true,
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
);
|
||||
|
||||
const response = await postPurge(new Request("http://localhost/api/documents/purge", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ documentId: "doc_1" }),
|
||||
}));
|
||||
expect(executeDocumentPurgeBridgeCommand).toHaveBeenCalled();
|
||||
const payload = await response.json() as {
|
||||
success: boolean;
|
||||
purged: boolean;
|
||||
meta: { commandName: string };
|
||||
};
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"http://localhost/api/tree/commands",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.any(Headers),
|
||||
body: JSON.stringify({
|
||||
action: "purge",
|
||||
documentId: "doc_1",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(payload.success).toBe(true);
|
||||
expect(payload.purged).toBe(true);
|
||||
expect(payload.meta.commandName).toBe("tree.node.purge");
|
||||
});
|
||||
|
||||
it("copy-tree route 作为 compat 壳委托 tree commands 主路径", async () => {
|
||||
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
requestId: "req_tree_copy_1",
|
||||
traceId: "trace_tree_copy_1",
|
||||
result: {
|
||||
items: [{ oldId: "doc_1", newId: "doc_2" }],
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
);
|
||||
|
||||
const response = await postCopyTree(new Request("http://localhost/api/documents/copy-tree", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ targetParentId: null, items: [{ documentId: "doc_1", recursive: true }] }),
|
||||
}));
|
||||
const payload = await response.json() as {
|
||||
items: Array<{ oldId: string; newId: string }>;
|
||||
meta: { commandName: string };
|
||||
};
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"http://localhost/api/tree/commands",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.any(Headers),
|
||||
body: JSON.stringify({
|
||||
action: "copy",
|
||||
targetParentId: null,
|
||||
items: [{ documentId: "doc_1", recursive: true }],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(payload.items).toEqual([{ oldId: "doc_1", newId: "doc_2" }]);
|
||||
expect(payload.meta.commandName).toBe("tree.subtree.copy");
|
||||
});
|
||||
|
||||
it("page route delegates to unified aggregate loader", async () => {
|
||||
|
||||
Reference in New Issue
Block a user