0.2.1 onlyoffice修复

This commit is contained in:
liaibo
2026-01-17 10:12:53 +08:00
parent 94957dc361
commit 19907bccdc
102 changed files with 7188 additions and 186 deletions
@@ -1,9 +1,35 @@
import { NextResponse } from "next/server";
import { createSupabaseRouteClient } from "@/lib/supabase/server";
import { isConvexEnabled } from "@/lib/convex/enabled";
import { getConvexHttpClient } from "@/lib/convex/server";
import { api } from "@/lib/convex/api";
import { requireAuthContext } from "@/lib/auth/authContext";
export const dynamic = "force-dynamic";
export async function GET(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const url = new URL(request.url);
const documentId = url.searchParams.get("documentId") ?? "";
if (!documentId) {
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
}
const client = getConvexHttpClient();
const result = await client.query(api.documents.getContent, {
userId: auth.userId,
id: documentId,
});
if (!result) {
return NextResponse.json({ error: "页面不存在" }, { status: 404 });
}
return NextResponse.json({ content: result.content ?? null });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -37,4 +63,3 @@ export async function GET(request: Request) {
return NextResponse.json({ content: document.content ?? null });
}
@@ -1,5 +1,9 @@
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 type { Json } from "@/types/supabase";
@@ -158,6 +162,142 @@ function replaceAssetRefsInContent(content: Json | null, assetMap: Map<string, {
}
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const client = getConvexHttpClient();
const payload = (await request.json()) as CopyTreePayload;
if (!payload?.items?.length) {
return NextResponse.json({ error: "缺少 items" }, { status: 400 });
}
const normalizedItems = payload.items.filter((it) => it?.documentId);
if (normalizedItems.length === 0) {
return NextResponse.json({ error: "items 为空" }, { status: 400 });
}
const targetParentId = payload.targetParentId ?? null;
let workspaceId: string | null = null;
if (targetParentId) {
const targetDoc = await client.query(api.documents.getMeta, { userId: auth.userId, id: targetParentId });
if (!targetDoc) {
return NextResponse.json({ error: "目标页面不存在或无权限" }, { status: 404 });
}
workspaceId = targetDoc.workspace_id;
}
const sourceIds = Array.from(new Set(normalizedItems.map((it) => it.documentId)));
const firstMeta = await client.query(api.documents.getMeta, { userId: auth.userId, id: sourceIds[0] });
if (!firstMeta) {
return NextResponse.json({ error: "源页面不存在或无权限" }, { status: 404 });
}
if (!workspaceId) {
workspaceId = firstMeta.workspace_id;
}
if (!workspaceId) {
return NextResponse.json({ error: "缺少目标工作空间" }, { status: 400 });
}
const wid = workspaceId;
const allDocs = await client.query(api.documents.listAllForCopy, {
userId: auth.userId,
workspaceId: wid,
});
const sourceById = new Map<string, DocRow>();
(allDocs as unknown as DocRow[]).forEach((d) => sourceById.set(d.id, d));
const missing = sourceIds.find((id) => !sourceById.has(id));
if (missing) {
return NextResponse.json({ error: "源页面不存在或无权限" }, { status: 404 });
}
const childrenByParent = new Map<string | null, DocRow[]>();
(allDocs as unknown as DocRow[]).forEach((doc) => {
const list = childrenByParent.get(doc.parent_id) ?? [];
list.push(doc);
childrenByParent.set(doc.parent_id, list);
});
const existingTitleSetByParent = new Map<string | null, Set<string>>();
const seedTitleSet = (parent: string | null) => {
if (existingTitleSetByParent.has(parent)) return;
const titles = new Set<string>();
(childrenByParent.get(parent) ?? []).forEach((d) => titles.add(normalizeTitle(d.title)));
existingTitleSetByParent.set(parent, titles);
};
seedTitleSet(targetParentId);
const newIdByOldId = new Map<string, string>();
const copyQueue: Array<{ old: DocRow; newParentId: string | null; parentKey: string | null }> = [];
const enqueueTree = (root: DocRow, newParent: string | null, recursive: boolean) => {
const visit = (node: DocRow, parentNewId: string | null, parentKey: string | null) => {
const newId =
typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now()}_${Math.random().toString(16).slice(2)}`;
newIdByOldId.set(node.id, newId);
copyQueue.push({ old: node, newParentId: parentNewId, parentKey });
if (!recursive) return;
const children = getChildrenSorted(childrenByParent, node.id);
children.forEach((child) => visit(child, newId, newId));
};
visit(root, newParent, newParent);
};
normalizedItems.forEach((item) => {
const doc = sourceById.get(item.documentId) ?? null;
if (doc) {
enqueueTree(doc, targetParentId, Boolean(item.recursive));
}
});
if (copyQueue.length === 0) {
return NextResponse.json({ error: "没有可复制的页面" }, { status: 400 });
}
const insertedDocs: Array<{ oldId: string; newId: string }> = [];
for (const item of copyQueue) {
const newId = newIdByOldId.get(item.old.id)!;
const parentId = item.newParentId;
if (!existingTitleSetByParent.has(parentId)) {
seedTitleSet(parentId);
}
const titleSet = existingTitleSetByParent.get(parentId) ?? new Set<string>();
existingTitleSetByParent.set(parentId, titleSet);
const newTitle = makeUniqueTitle(normalizeTitle(item.old.title), titleSet);
titleSet.add(newTitle);
await client.mutation(api.documents.create, {
userId: auth.userId,
id: newId,
workspaceId: wid,
parentId,
accessScope: (item.old.access_scope ?? "private") as "private" | "shared" | "public",
title: newTitle,
content: item.old.content ?? [],
});
await ensureDocumentScaffold(newId, newTitle);
await client.mutation(api.mindmaps.copyByDocument, {
userId: auth.userId,
sourceDocId: item.old.id,
targetDocId: newId,
});
insertedDocs.push({ oldId: item.old.id, newId });
}
return NextResponse.json({
items: insertedDocs.map((d) => ({ oldId: d.oldId, newId: d.newId })),
});
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -1,6 +1,16 @@
import { NextResponse } from "next/server";
import { createSupabaseRouteClient } from "@/lib/supabase/server";
import { ensureDefaultWorkspace, resolveActiveWorkspaceId } from "@/lib/workspaces";
import { isConvexEnabled } from "@/lib/convex/enabled";
import { requireAuthContext } from "@/lib/auth/authContext";
import { getConvexHttpClient } from "@/lib/convex/server";
import { api } from "@/lib/convex/api";
function makeId(): string {
return typeof crypto.randomUUID === "function"
? crypto.randomUUID()
: `${Date.now()}_${Math.random().toString(16).slice(2)}`;
}
type CreateChildPayload = {
parentId: string | null;
@@ -11,6 +21,58 @@ type CreateChildPayload = {
export const dynamic = "force-dynamic";
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const client = getConvexHttpClient();
const { parentId, title, blocks }: CreateChildPayload = await request.json();
if (typeof parentId === "undefined") {
return NextResponse.json({ error: "缺少 parentId" }, { status: 400 });
}
let workspaceId: string;
let accessScope: "private" | "shared" | "public" = "private";
if (parentId) {
const parentDoc = await client.query(api.documents.getMeta, { userId: auth.userId, id: parentId });
if (!parentDoc) {
return NextResponse.json({ error: "父页面不存在" }, { status: 404 });
}
workspaceId = parentDoc.workspace_id;
accessScope = (parentDoc.access_scope ?? "private") as typeof accessScope;
} else {
const ensured = await client.mutation(api.workspaces.ensureDefaultWorkspace, {
userId: auth.userId,
fallbackName: auth.name ?? auth.email ?? "我的空间",
workspaceIdIfCreate: makeId(),
});
workspaceId = ensured.activeWorkspaceId;
if (!workspaceId) {
return NextResponse.json({ error: "缺少目标工作空间" }, { status: 400 });
}
}
const resolvedTitle = title && title.trim().length > 0 ? title.trim() : "未命名页面";
const contentPayload = Array.isArray(blocks) ? blocks : [];
const pageId = makeId();
const created = await client.mutation(api.documents.create, {
userId: auth.userId,
id: pageId,
workspaceId,
parentId: parentId ?? null,
title: resolvedTitle,
accessScope,
content: contentPayload,
});
return NextResponse.json({
pageId: created.id,
title: created.title ?? resolvedTitle,
});
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -7,6 +7,10 @@ import { composeContentWithBlocks, extractBlocksFromContent } from "@/lib/docume
import path from "path";
import { promises as fs } from "fs";
import { getDocumentsBaseDir } from "@/lib/server/local-paths";
import { isConvexEnabled } from "@/lib/convex/enabled";
import { getConvexHttpClient } from "@/lib/convex/server";
import { api } from "@/lib/convex/api";
import { requireAuthContext } from "@/lib/auth/authContext";
const documentsBaseDir = getDocumentsBaseDir();
@@ -25,6 +29,9 @@ async function ensureDocumentScaffold(id: string, title: string | null) {
export async function POST(request: Request) {
try {
if (isConvexEnabled()) {
return await handleCreateRequestConvex(request);
}
return await handleCreateRequest(request);
} catch (error) {
console.error("创建页面失败", error);
@@ -33,6 +40,83 @@ export async function POST(request: Request) {
}
}
async function handleCreateRequestConvex(request: Request) {
const auth = requireAuthContext();
const client = getConvexHttpClient();
const { parentId }: { parentId?: string | null } = await request.json();
const workspaceBootstrap = await client.mutation(api.workspaces.ensureDefaultWorkspace, {
userId: auth.userId,
fallbackName: auth.email ?? auth.name ?? "我的空间",
workspaceIdIfCreate: randomUUID(),
});
let workspaceId: string | null = null;
let parentContent: Json | null = null;
let accessScope: "private" | "shared" | "public" = "private";
if (parentId) {
const parentDoc = await client.query(api.documents.getMeta, {
userId: auth.userId,
id: parentId,
});
if (!parentDoc) {
return NextResponse.json({ error: "父页面不存在或无权限" }, { status: 404 });
}
workspaceId = parentDoc.workspace_id;
accessScope = (parentDoc.access_scope ?? "private") as typeof accessScope;
const parentContentRes = await client.query(api.documents.getContent, { userId: auth.userId, id: parentId });
parentContent = (parentContentRes?.content as Json | null) ?? null;
} else {
workspaceId = workspaceBootstrap.activeWorkspaceId || null;
}
if (!workspaceId) {
return NextResponse.json({ error: "缺少目标工作空间" }, { status: 400 });
}
const id = randomUUID();
const data = await client.mutation(api.documents.create, {
userId: auth.userId,
id,
workspaceId,
parentId: parentId ?? null,
title: "无标题",
accessScope,
content: [],
});
// 为文件树创建本地目录和 index.md
if (data?.id) {
await ensureDocumentScaffold(data.id, data.title ?? "无标题");
}
if (parentId && data) {
const existingBlocks = extractBlocksFromContent(parentContent);
const pageReferenceBlock = {
id: randomUUID(),
type: "pageReference",
props: {
pageId: data.id,
title: data.title ?? "无标题",
},
};
const nextBlocks = [...existingBlocks, pageReferenceBlock as Json];
const payload = composeContentWithBlocks(parentContent, nextBlocks);
await client.mutation(api.documents.updateContent, {
userId: auth.userId,
id: parentId,
content: payload,
});
}
return NextResponse.json(data);
}
async function handleCreateRequest(request: Request) {
const supabase = await createSupabaseRouteClient();
const {
@@ -1,9 +1,24 @@
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";
export const dynamic = "force-dynamic";
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const { documentId } = await request.json();
if (!documentId) {
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
}
const client = getConvexHttpClient();
await client.mutation(api.documents.softDelete, { userId: auth.userId, id: documentId });
return NextResponse.json({ success: true });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -1,5 +1,9 @@
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";
@@ -43,6 +47,50 @@ async function copyMindmapIfExists(sourceId: string, targetId: string) {
}
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = 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 },
@@ -3,6 +3,9 @@ import { NextResponse } from "next/server";
import { createSupabaseRouteClient } from "@/lib/supabase/server";
import type { Json } from "@/types/supabase";
import { composeContentWithBlocks, extractBlocksFromContent } from "@/lib/document-content";
import { isConvexEnabled } from "@/lib/convex/enabled";
import { getAuthedConvexClient } from "@/lib/convex/route";
import { api } from "@/lib/convex/api";
interface EmbedPayload {
sourceId: string;
@@ -10,6 +13,49 @@ interface EmbedPayload {
}
export async function POST(request: Request) {
if (isConvexEnabled()) {
const { auth, client } = getAuthedConvexClient();
const { sourceId, targetId }: EmbedPayload = await request.json();
if (!sourceId || !targetId) {
return NextResponse.json({ error: "缺少参数" }, { status: 400 });
}
const sourceDoc = await client.query(api.documents.getMeta, { userId: auth.userId, id: sourceId });
if (!sourceDoc) {
return NextResponse.json({ error: "原始页面不存在或无权限" }, { status: 404 });
}
const targetContent = await client.query(api.documents.getContent, { userId: auth.userId, id: targetId });
if (!targetContent) {
return NextResponse.json({ error: "目标页面不存在或无权限" }, { status: 404 });
}
const currentBlocks = extractBlocksFromContent(targetContent.content);
const nextBlocks: Json[] = [
...currentBlocks,
{
id: randomUUID(),
type: "pageReference",
props: {
pageId: sourceDoc.id,
title: sourceDoc.title ?? "无标题",
},
},
];
const payload: Json = composeContentWithBlocks(targetContent.content, nextBlocks);
await client.mutation(api.documents.updateContent, {
userId: auth.userId,
id: targetId,
content: payload,
});
return NextResponse.json({ ok: true });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -1,5 +1,9 @@
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";
export const dynamic = "force-dynamic";
@@ -8,6 +12,17 @@ interface EmptyTrashPayload {
}
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const { workspaceId }: EmptyTrashPayload = await request.json();
if (!workspaceId) {
return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 });
}
const client = getConvexHttpClient();
await client.mutation(api.documents.emptyTrashByWorkspace, { userId: auth.userId, workspaceId });
return NextResponse.json({ success: true });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -1,5 +1,9 @@
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";
interface MovePayload {
documentId: string;
@@ -8,6 +12,20 @@ interface MovePayload {
}
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const { documentId, parentId = null, position }: MovePayload = await request.json();
const sortOrder = Number.isFinite(position) ? Math.floor(position) : 0;
const client = getConvexHttpClient();
await client.mutation(api.documents.move, {
userId: auth.userId,
id: documentId,
parentId,
sortOrder,
});
return NextResponse.json({ ok: true });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -1,5 +1,9 @@
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 type { PageOptionsState } from "@/types/page-options";
import type { Database } from "@/types/supabase";
@@ -19,6 +23,31 @@ const COLUMN_MAP: Record<keyof PageOptionsState, keyof Database["public"]["Table
};
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const { documentId, options }: OptionsPayload = await request.json();
if (!documentId || !options) {
return NextResponse.json({ error: "缺少参数" }, { status: 400 });
}
const client = getConvexHttpClient();
await client.mutation(api.documents.updateOptions, {
userId: auth.userId,
id: documentId,
options: {
wideLayout: options.wideLayout,
smallText: options.smallText,
showHeadingNumbers: options.showHeadingNumbers,
showToc: options.showToc,
showStructure: options.showStructure,
protectEditing: options.protectEditing,
showWordCount: options.showWordCount,
},
});
return NextResponse.json({ ok: true });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -1,9 +1,24 @@
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";
export const dynamic = "force-dynamic";
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const { documentId } = await request.json();
if (!documentId) {
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
}
const client = getConvexHttpClient();
await client.mutation(api.documents.purge, { userId: auth.userId, id: documentId });
return NextResponse.json({ success: true });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -1,9 +1,24 @@
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";
export const dynamic = "force-dynamic";
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const { documentId } = await request.json();
if (!documentId) {
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
}
const client = getConvexHttpClient();
await client.mutation(api.documents.restore, { userId: auth.userId, id: documentId });
return NextResponse.json({ success: true });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -1,5 +1,9 @@
import { NextResponse } from "next/server";
import { createSupabaseRouteClient } from "@/lib/supabase/server";
import { isConvexEnabled } from "@/lib/convex/enabled";
import { getConvexHttpClient } from "@/lib/convex/server";
import { api } from "@/lib/convex/api";
import { requireAuthContext } from "@/lib/auth/authContext";
interface SavePayload {
documentId: string;
@@ -7,6 +11,18 @@ interface SavePayload {
}
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const { documentId, content }: SavePayload = await request.json();
const client = getConvexHttpClient();
await client.mutation(api.documents.updateContent, {
userId: auth.userId,
id: documentId,
content,
});
return NextResponse.json({ ok: true });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -1,5 +1,9 @@
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 type { DocumentStats } from "@/types/page-options";
interface StatsPayload {
@@ -8,6 +12,25 @@ interface StatsPayload {
}
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const { documentId, stats }: StatsPayload = await request.json();
if (!documentId || !stats) {
return NextResponse.json({ error: "缺少参数" }, { status: 400 });
}
const client = getConvexHttpClient();
await client.mutation(api.documents.updateStats, {
userId: auth.userId,
id: documentId,
wordCount: stats.wordCount,
characterCount: stats.characterCount,
blockCount: stats.blockCount,
});
return NextResponse.json({ ok: true });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },
@@ -1,5 +1,9 @@
import { NextResponse } from "next/server";
import { createSupabaseRouteClient } from "@/lib/supabase/server";
import { isConvexEnabled } from "@/lib/convex/enabled";
import { getConvexHttpClient } from "@/lib/convex/server";
import { api } from "@/lib/convex/api";
import { requireAuthContext } from "@/lib/auth/authContext";
interface RenamePayload {
documentId: string;
@@ -7,6 +11,18 @@ interface RenamePayload {
}
export async function POST(request: Request) {
if (isConvexEnabled()) {
const auth = requireAuthContext();
const { documentId, title }: RenamePayload = await request.json();
const client = getConvexHttpClient();
await client.mutation(api.documents.updateTitle, {
userId: auth.userId,
id: documentId,
title,
});
return NextResponse.json({ ok: true });
}
const supabase = await createSupabaseRouteClient();
const {
data: { session },