4-10 树域 Rust 家族化
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { NextResponse } from "next/server";
|
||||
import { api } from "@/lib/convex/api";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { getAuthedConvexClient } from "@/lib/convex/route";
|
||||
import {
|
||||
assertDocumentId,
|
||||
assertTitle,
|
||||
buildDocumentBridgeContext,
|
||||
buildDocumentCommandEnvelope,
|
||||
documentBridgeErrorResponse,
|
||||
} from "@/lib/documents/bridge";
|
||||
import { ensureDocumentScaffold } from "@/lib/documents/page-lifecycle-side-effects";
|
||||
import {
|
||||
executeRustBridgeMutationTransport,
|
||||
resolveRustBridgeCommandPlan,
|
||||
} from "@/lib/documents/rust-runtime";
|
||||
|
||||
type TreeCommandPayload = {
|
||||
action?: "create" | "move" | "rename";
|
||||
workspaceId?: string | null;
|
||||
documentId?: string | null;
|
||||
parentId?: string | null;
|
||||
title?: string | null;
|
||||
accessScope?: "private" | "shared" | "public" | null;
|
||||
content?: unknown;
|
||||
sortOrder?: number | null;
|
||||
};
|
||||
|
||||
function trimOrNull(value: unknown) {
|
||||
if (typeof value !== "string") return null;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
function normalizeTitle(value: unknown) {
|
||||
const title = typeof value === "string" ? value.trim() : "";
|
||||
return title || "无标题";
|
||||
}
|
||||
|
||||
function normalizeSortOrder(value: unknown) {
|
||||
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
if (!isConvexEnabled()) {
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = (await request.json()) as TreeCommandPayload;
|
||||
switch (payload.action) {
|
||||
case "create":
|
||||
return handleCreate(request, payload);
|
||||
case "move":
|
||||
return handleMove(request, payload);
|
||||
case "rename":
|
||||
return handleRename(request, payload);
|
||||
default:
|
||||
return NextResponse.json({ error: "不支持的 tree action" }, { status: 400 });
|
||||
}
|
||||
} catch (error) {
|
||||
return documentBridgeErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreate(request: Request, payload: TreeCommandPayload) {
|
||||
const { auth, client } = await getAuthedConvexClient();
|
||||
const parentId = trimOrNull(payload.parentId);
|
||||
const workspaceBootstrap = await client.mutation(api.workspaces.ensureDefaultWorkspace, {
|
||||
fallbackName: auth.email ?? auth.name ?? "我的空间",
|
||||
workspaceIdIfCreate: randomUUID(),
|
||||
});
|
||||
|
||||
let workspaceId: string | null = trimOrNull(payload.workspaceId);
|
||||
let accessScope: "private" | "shared" | "public" = "private";
|
||||
if (parentId) {
|
||||
const parentDoc = await client.query(api.documents.getMeta, { id: parentId });
|
||||
if (!parentDoc) {
|
||||
return NextResponse.json({ error: "父页面不存在或无权限" }, { status: 404 });
|
||||
}
|
||||
workspaceId = trimOrNull(parentDoc.workspace_id);
|
||||
accessScope = (parentDoc.access_scope ?? "private") as typeof accessScope;
|
||||
} else if (!workspaceId) {
|
||||
workspaceId = trimOrNull(workspaceBootstrap.activeWorkspaceId);
|
||||
}
|
||||
|
||||
if (!workspaceId) {
|
||||
return NextResponse.json({ error: "缺少目标工作空间" }, { status: 400 });
|
||||
}
|
||||
|
||||
const documentId = trimOrNull(payload.documentId) ?? randomUUID();
|
||||
const title = normalizeTitle(payload.title);
|
||||
const context = await buildDocumentBridgeContext({
|
||||
request,
|
||||
workspaceId,
|
||||
});
|
||||
const envelope = buildDocumentCommandEnvelope({
|
||||
name: "tree.node.create",
|
||||
payload: {
|
||||
documentId,
|
||||
workspaceId,
|
||||
parentId,
|
||||
title,
|
||||
accessScope: trimOrNull(payload.accessScope) ?? accessScope,
|
||||
content: Array.isArray(payload.content) ? payload.content : [],
|
||||
},
|
||||
context,
|
||||
target: {
|
||||
workspaceId,
|
||||
pageId: documentId,
|
||||
},
|
||||
reason: "tree-route create",
|
||||
refs: ["next-tree-route"],
|
||||
});
|
||||
const plan = await resolveRustBridgeCommandPlan({
|
||||
context,
|
||||
envelope,
|
||||
});
|
||||
const result = await executeRustBridgeMutationTransport<{
|
||||
id: string;
|
||||
title: string | null;
|
||||
parent_id: string | null;
|
||||
sort_order: number | null;
|
||||
workspace_id: string;
|
||||
access_scope: "private" | "shared" | "public";
|
||||
is_template: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}>({
|
||||
client,
|
||||
plan,
|
||||
});
|
||||
|
||||
await ensureDocumentScaffold(result.id, result.title ?? title);
|
||||
|
||||
return NextResponse.json({
|
||||
requestId: context.requestId,
|
||||
traceId: context.traceId,
|
||||
result: {
|
||||
action: "create",
|
||||
workspaceId,
|
||||
documentId: result.id,
|
||||
parentId: result.parent_id ?? parentId,
|
||||
title: result.title ?? title,
|
||||
sortOrder: result.sort_order ?? null,
|
||||
updatedAt: result.updated_at ?? null,
|
||||
execution: result,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleMove(request: Request, payload: TreeCommandPayload) {
|
||||
const { client } = await getAuthedConvexClient();
|
||||
const documentId = assertDocumentId(payload.documentId ?? null);
|
||||
const sourceDoc = await client.query(api.documents.getMeta, { id: documentId });
|
||||
if (!sourceDoc) {
|
||||
return NextResponse.json({ error: "页面不存在或无权访问" }, { status: 404 });
|
||||
}
|
||||
|
||||
const workspaceId = trimOrNull(payload.workspaceId) ?? trimOrNull(sourceDoc.workspace_id);
|
||||
const parentId = trimOrNull(payload.parentId);
|
||||
const sortOrder = normalizeSortOrder(payload.sortOrder);
|
||||
const context = await buildDocumentBridgeContext({
|
||||
request,
|
||||
workspaceId,
|
||||
});
|
||||
const envelope = buildDocumentCommandEnvelope({
|
||||
name: "tree.subtree.move",
|
||||
payload: {
|
||||
documentId,
|
||||
parentId,
|
||||
sortOrder,
|
||||
},
|
||||
context,
|
||||
target: {
|
||||
workspaceId,
|
||||
pageId: documentId,
|
||||
},
|
||||
reason: "tree-route move",
|
||||
refs: ["next-tree-route"],
|
||||
});
|
||||
const plan = await resolveRustBridgeCommandPlan({
|
||||
context,
|
||||
envelope,
|
||||
});
|
||||
const result = await executeRustBridgeMutationTransport<{ ok?: boolean; updated_at?: string | null }>({
|
||||
client,
|
||||
plan,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
requestId: context.requestId,
|
||||
traceId: context.traceId,
|
||||
result: {
|
||||
action: "move",
|
||||
workspaceId,
|
||||
documentId,
|
||||
parentId,
|
||||
sortOrder,
|
||||
updatedAt: result?.updated_at ?? null,
|
||||
execution: result ?? null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRename(request: Request, payload: TreeCommandPayload) {
|
||||
const { client } = await getAuthedConvexClient();
|
||||
const documentId = assertDocumentId(payload.documentId ?? null);
|
||||
const sourceDoc = await client.query(api.documents.getMeta, { id: documentId });
|
||||
if (!sourceDoc) {
|
||||
return NextResponse.json({ error: "页面不存在或无权访问" }, { status: 404 });
|
||||
}
|
||||
|
||||
const workspaceId = trimOrNull(payload.workspaceId) ?? trimOrNull(sourceDoc.workspace_id);
|
||||
const title = assertTitle(payload.title ?? null);
|
||||
const context = await buildDocumentBridgeContext({
|
||||
request,
|
||||
workspaceId,
|
||||
});
|
||||
const envelope = buildDocumentCommandEnvelope({
|
||||
name: "tree.node.rename",
|
||||
payload: {
|
||||
documentId,
|
||||
workspaceId,
|
||||
title,
|
||||
},
|
||||
context,
|
||||
target: {
|
||||
workspaceId,
|
||||
pageId: documentId,
|
||||
},
|
||||
reason: "tree-route rename",
|
||||
refs: ["next-tree-route"],
|
||||
});
|
||||
const plan = await resolveRustBridgeCommandPlan({
|
||||
context,
|
||||
envelope,
|
||||
});
|
||||
const result = await executeRustBridgeMutationTransport<{ ok?: boolean; updated_at?: string | null }>({
|
||||
client,
|
||||
plan,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
requestId: context.requestId,
|
||||
traceId: context.traceId,
|
||||
result: {
|
||||
action: "rename",
|
||||
workspaceId,
|
||||
documentId,
|
||||
title,
|
||||
updatedAt: result?.updated_at ?? null,
|
||||
execution: result ?? null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const runtime = "nodejs";
|
||||
Reference in New Issue
Block a user