diff --git a/services/rag_gateway/app/core/config.py b/services/rag_gateway/app/core/config.py index 3455bacd..cef2e620 100644 --- a/services/rag_gateway/app/core/config.py +++ b/services/rag_gateway/app/core/config.py @@ -10,8 +10,6 @@ class Settings(BaseSettings): environment: str = Field("development", env="RAG_GATEWAY_ENV") lightrag_url: str = Field("http://127.0.0.1:7777", env="LIGHTRAG_URL") lightrag_api_key: str = Field("", env="LIGHTRAG_API_KEY") - supabase_rest_url: str = Field("http://127.0.0.1:54321/rest/v1", env="SUPABASE_REST_URL") - supabase_key: str = Field("", env="SUPABASE_SERVICE_ROLE_KEY") embedding_base_url: str = Field("http://127.0.0.1:11434/v1", env="EMBEDDING_BASE_URL") embedding_model: str = Field("qwen3-embedding:8b", env="EMBEDDING_MODEL") diff --git a/wolai-frontend/convex/documents.ts b/wolai-frontend/convex/documents.ts index 84180cdb..ecbc4867 100644 --- a/wolai-frontend/convex/documents.ts +++ b/wolai-frontend/convex/documents.ts @@ -1,11 +1,54 @@ -import { mutation, query } from "./_generated/server"; +import { internalQuery, mutation, query } from "./_generated/server"; import { v } from "convex/values"; +import { getAuthUserId } from "@convex-dev/auth/server"; import { nowIso } from "./_utils/time"; import { enqueueIngestDocumentJob } from "./_utils/ingestJobs"; const accessScope = v.union(v.literal("private"), v.literal("shared"), v.literal("public")); +async function requireUserId(ctx: any): Promise { + const userId = await getAuthUserId(ctx); + if (userId === null) { + throw new Error("未登录"); + } + return userId; +} + export const getMeta = query({ + args: { id: v.string() }, + handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + + const doc = await ctx.db + .query("documents") + .withIndex("by_document_id", (q) => q.eq("id", args.id)) + .first(); + if (!doc) return null; + if (doc.user_id !== userId) return null; + return { + id: doc.id, + user_id: doc.user_id, + workspace_id: doc.workspace_id, + access_scope: doc.access_scope, + title: doc.title ?? null, + parent_id: doc.parent_id ?? null, + created_at: doc.created_at, + updated_at: doc.updated_at ?? null, + wide_layout: doc.wide_layout ?? null, + use_small_text: doc.use_small_text ?? null, + show_heading_numbers: doc.show_heading_numbers ?? null, + show_toc: doc.show_toc ?? null, + show_structure: doc.show_structure ?? null, + protect_editing: doc.protect_editing ?? null, + show_word_count: doc.show_word_count ?? null, + word_count: doc.word_count ?? null, + character_count: doc.character_count ?? null, + block_count: doc.block_count ?? null, + }; + }, +}); + +export const getMetaForIngest = internalQuery({ args: { userId: v.string(), id: v.string() }, handler: async (ctx, args) => { const doc = await ctx.db @@ -38,6 +81,21 @@ export const getMeta = query({ }); export const getContent = query({ + args: { id: v.string() }, + handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + + const doc = await ctx.db + .query("documents") + .withIndex("by_document_id", (q) => q.eq("id", args.id)) + .first(); + if (!doc) return null; + if (doc.user_id !== userId) return null; + return { content: doc.content ?? null }; + }, +}); + +export const getContentForIngest = internalQuery({ args: { userId: v.string(), id: v.string() }, handler: async (ctx, args) => { const doc = await ctx.db @@ -51,8 +109,10 @@ export const getContent = query({ }); export const listByWorkspace = query({ - args: { userId: v.string(), workspaceId: v.string() }, + args: { workspaceId: v.string() }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const docs = await ctx.db .query("documents") .withIndex("by_workspace", (q) => q.eq("workspace_id", args.workspaceId)) @@ -60,7 +120,7 @@ export const listByWorkspace = query({ // 说明:阶段 4 先不做垃圾桶(deleted_at != null),因此这里直接过滤。 return docs - .filter((d) => d.user_id === args.userId) + .filter((d) => d.user_id === userId) .filter((d) => d.deleted_at == null) .map((d) => ({ access_scope: d.access_scope, @@ -78,15 +138,17 @@ export const listByWorkspace = query({ }); export const listTrashedByWorkspace = query({ - args: { userId: v.string(), workspaceId: v.string() }, + args: { workspaceId: v.string() }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const docs = await ctx.db .query("documents") .withIndex("by_workspace", (q) => q.eq("workspace_id", args.workspaceId)) .collect(); return docs - .filter((d) => d.user_id === args.userId) + .filter((d) => d.user_id === userId) .filter((d) => d.deleted_at != null) .sort((a, b) => (b.deleted_at ?? "").localeCompare(a.deleted_at ?? "")) .slice(0, 100) @@ -102,7 +164,6 @@ export const listTrashedByWorkspace = query({ export const create = mutation({ args: { - userId: v.string(), id: v.string(), workspaceId: v.string(), parentId: v.union(v.string(), v.null()), @@ -111,6 +172,8 @@ export const create = mutation({ content: v.optional(v.any()), }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const siblings = await ctx.db .query("documents") .withIndex("by_workspace_parent", (q) => q.eq("workspace_id", args.workspaceId).eq("parent_id", args.parentId)) @@ -124,7 +187,7 @@ export const create = mutation({ await ctx.db.insert("documents", { id: args.id, - user_id: args.userId, + user_id: userId, workspace_id: args.workspaceId, parent_id: args.parentId, title, @@ -166,33 +229,37 @@ export const create = mutation({ }); export const updateContent = mutation({ - args: { userId: v.string(), id: v.string(), content: v.any() }, + args: { id: v.string(), content: v.any() }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const doc = await ctx.db .query("documents") .withIndex("by_document_id", (q) => q.eq("id", args.id)) .first(); if (!doc) throw new Error("页面不存在"); - if (doc.user_id !== args.userId) throw new Error("无权限"); + if (doc.user_id !== userId) throw new Error("无权限"); const ts = nowIso(); await ctx.db.patch(doc._id, { content: args.content, updated_at: ts }); - // 说明:在 Convex 模式下,把“自动入库/LightRAG 触发”迁到 Convex jobs/actions。 + // 说明:在 Convex 模式下,把"自动入库/LightRAG 触发"迁到 Convex jobs/actions。 // 采用 debounce,避免频繁保存时触发过多任务。 - await enqueueIngestDocumentJob(ctx, { userId: args.userId, documentId: args.id, debounceMs: 1500 }); + await enqueueIngestDocumentJob(ctx, { userId, documentId: args.id, debounceMs: 1500 }); return { ok: true, updated_at: ts }; }, }); export const updateTitle = mutation({ - args: { userId: v.string(), id: v.string(), title: v.union(v.string(), v.null()) }, + args: { id: v.string(), title: v.union(v.string(), v.null()) }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const doc = await ctx.db .query("documents") .withIndex("by_document_id", (q) => q.eq("id", args.id)) .first(); if (!doc) throw new Error("页面不存在"); - if (doc.user_id !== args.userId) throw new Error("无权限"); + if (doc.user_id !== userId) throw new Error("无权限"); const ts = nowIso(); await ctx.db.patch(doc._id, { title: args.title, updated_at: ts }); return { ok: true, updated_at: ts }; @@ -201,18 +268,19 @@ export const updateTitle = mutation({ export const move = mutation({ args: { - userId: v.string(), id: v.string(), parentId: v.union(v.string(), v.null()), sortOrder: v.number(), }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const doc = await ctx.db .query("documents") .withIndex("by_document_id", (q) => q.eq("id", args.id)) .first(); if (!doc) throw new Error("页面不存在"); - if (doc.user_id !== args.userId) throw new Error("无权限"); + if (doc.user_id !== userId) throw new Error("无权限"); const ts = nowIso(); await ctx.db.patch(doc._id, { parent_id: args.parentId, @@ -224,29 +292,33 @@ export const move = mutation({ }); export const softDelete = mutation({ - args: { userId: v.string(), id: v.string() }, + args: { id: v.string() }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const doc = await ctx.db .query("documents") .withIndex("by_document_id", (q) => q.eq("id", args.id)) .first(); if (!doc) throw new Error("页面不存在"); - if (doc.user_id !== args.userId) throw new Error("无权限"); + if (doc.user_id !== userId) throw new Error("无权限"); const ts = nowIso(); - await ctx.db.patch(doc._id, { deleted_at: ts, deleted_by: args.userId, updated_at: ts }); + await ctx.db.patch(doc._id, { deleted_at: ts, deleted_by: userId, updated_at: ts }); return { ok: true }; }, }); export const restore = mutation({ - args: { userId: v.string(), id: v.string() }, + args: { id: v.string() }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const doc = await ctx.db .query("documents") .withIndex("by_document_id", (q) => q.eq("id", args.id)) .first(); if (!doc) throw new Error("页面不存在"); - if (doc.user_id !== args.userId) throw new Error("无权限"); + if (doc.user_id !== userId) throw new Error("无权限"); const ts = nowIso(); await ctx.db.patch(doc._id, { deleted_at: null, @@ -260,26 +332,30 @@ export const restore = mutation({ }); export const purge = mutation({ - args: { userId: v.string(), id: v.string() }, + args: { id: v.string() }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const doc = await ctx.db .query("documents") .withIndex("by_document_id", (q) => q.eq("id", args.id)) .first(); if (!doc) throw new Error("页面不存在"); - if (doc.user_id !== args.userId) throw new Error("无权限"); + if (doc.user_id !== userId) throw new Error("无权限"); await ctx.db.delete(doc._id); return { ok: true }; }, }); export const emptyTrashByWorkspace = mutation({ - args: { userId: v.string(), workspaceId: v.string() }, + args: { workspaceId: v.string() }, handler: async (ctx, args) => { - // 说明:阶段 4/5 先用“membership 存在即可”的规则,避免引入复杂权限模型。 + const userId = await requireUserId(ctx); + + // 说明:阶段 4/5 先用"membership 存在即可"的规则,避免引入复杂权限模型。 const membership = await ctx.db .query("workspace_members") - .withIndex("by_workspace_user", (q) => q.eq("workspace_id", args.workspaceId).eq("user_id", args.userId)) + .withIndex("by_workspace_user", (q) => q.eq("workspace_id", args.workspaceId).eq("user_id", userId)) .first(); if (!membership) { @@ -291,7 +367,7 @@ export const emptyTrashByWorkspace = mutation({ .withIndex("by_workspace", (q) => q.eq("workspace_id", args.workspaceId)) .collect(); - const toDelete = docs.filter((d) => d.user_id === args.userId && d.deleted_at != null); + const toDelete = docs.filter((d) => d.user_id === userId && d.deleted_at != null); for (const d of toDelete) { await ctx.db.delete(d._id); } @@ -302,7 +378,6 @@ export const emptyTrashByWorkspace = mutation({ export const updateOptions = mutation({ args: { - userId: v.string(), id: v.string(), options: v.object({ wideLayout: v.optional(v.boolean()), @@ -315,12 +390,14 @@ export const updateOptions = mutation({ }), }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const doc = await ctx.db .query("documents") .withIndex("by_document_id", (q) => q.eq("id", args.id)) .first(); if (!doc) throw new Error("页面不存在"); - if (doc.user_id !== args.userId) throw new Error("无权限"); + if (doc.user_id !== userId) throw new Error("无权限"); const patch: Record = {}; if (typeof args.options.wideLayout === "boolean") patch.wide_layout = args.options.wideLayout; @@ -344,19 +421,20 @@ export const updateOptions = mutation({ export const updateStats = mutation({ args: { - userId: v.string(), id: v.string(), wordCount: v.number(), characterCount: v.number(), blockCount: v.number(), }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const doc = await ctx.db .query("documents") .withIndex("by_document_id", (q) => q.eq("id", args.id)) .first(); if (!doc) throw new Error("页面不存在"); - if (doc.user_id !== args.userId) throw new Error("无权限"); + if (doc.user_id !== userId) throw new Error("无权限"); const ts = nowIso(); await ctx.db.patch(doc._id, { word_count: args.wordCount, @@ -370,18 +448,19 @@ export const updateStats = mutation({ export const duplicate = mutation({ args: { - userId: v.string(), sourceId: v.string(), newId: v.string(), title: v.optional(v.union(v.string(), v.null())), }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const source = await ctx.db .query("documents") .withIndex("by_document_id", (q) => q.eq("id", args.sourceId)) .first(); if (!source) throw new Error("页面不存在或无权限访问"); - if (source.user_id !== args.userId) throw new Error("页面不存在或无权限访问"); + if (source.user_id !== userId) throw new Error("页面不存在或无权限访问"); const siblings = await ctx.db .query("documents") @@ -397,7 +476,7 @@ export const duplicate = mutation({ await ctx.db.insert("documents", { id: args.newId, - user_id: args.userId, + user_id: userId, workspace_id: source.workspace_id, parent_id: source.parent_id, title, @@ -438,15 +517,17 @@ export const duplicate = mutation({ }); export const listAllForCopy = query({ - args: { userId: v.string(), workspaceId: v.string() }, + args: { workspaceId: v.string() }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const docs = await ctx.db .query("documents") .withIndex("by_workspace", (q) => q.eq("workspace_id", args.workspaceId)) .collect(); return docs - .filter((d) => d.user_id === args.userId) + .filter((d) => d.user_id === userId) .filter((d) => d.deleted_at == null) .map((d) => ({ id: d.id, diff --git a/wolai-frontend/convex/jobs.ts b/wolai-frontend/convex/jobs.ts index 7b6806b7..eaea24f8 100644 --- a/wolai-frontend/convex/jobs.ts +++ b/wolai-frontend/convex/jobs.ts @@ -122,8 +122,8 @@ export const run = internalAction({ if (!documentId) throw new Error("缺少 documentId"); const [meta, contentRes] = await Promise.all([ - ctx.runQuery(api.documents.getMeta, { userId: job.user_id, id: documentId }), - ctx.runQuery(api.documents.getContent, { userId: job.user_id, id: documentId }), + ctx.runQuery(internal.documents.getMetaForIngest, { userId: job.user_id, id: documentId }), + ctx.runQuery(internal.documents.getContentForIngest, { userId: job.user_id, id: documentId }), ]); if (!meta) throw new Error("页面不存在或无权限"); @@ -147,8 +147,8 @@ export const run = internalAction({ if (!mindmapId) throw new Error("缺少 mindmapId"); const [docMeta, mindmapRes] = await Promise.all([ - ctx.runQuery(api.documents.getMeta, { userId: job.user_id, id: docId }), - ctx.runQuery(api.mindmaps.get, { userId: job.user_id, docId, mindmapId }), + ctx.runQuery(internal.documents.getMetaForIngest, { userId: job.user_id, id: docId }), + ctx.runQuery(internal.mindmaps.getForIngest, { userId: job.user_id, docId, mindmapId }), ]); if (!docMeta) throw new Error("页面不存在或无权限"); diff --git a/wolai-frontend/convex/mindmaps.ts b/wolai-frontend/convex/mindmaps.ts index 89365e49..d8b6a0f9 100644 --- a/wolai-frontend/convex/mindmaps.ts +++ b/wolai-frontend/convex/mindmaps.ts @@ -1,5 +1,6 @@ -import { mutation, query } from "./_generated/server"; +import { internalQuery, mutation, query } from "./_generated/server"; import { v } from "convex/values"; +import { getAuthUserId } from "@convex-dev/auth/server"; import { nowIso } from "./_utils/time"; import { enqueueIngestMindmapJob } from "./_utils/ingestJobs"; @@ -8,6 +9,14 @@ const defaultMindmapData = { children: [], }; +async function requireUserId(ctx: any): Promise { + const userId = await getAuthUserId(ctx); + if (userId === null) { + throw new Error("未登录"); + } + return userId; +} + function normalizeMindmapId(docId: string, mindmapId: string): string { const raw = String(mindmapId ?? "").trim(); if (raw) return raw; @@ -30,9 +39,11 @@ async function requireOwnedDocument(ctx: any, userId: string, docId: string) { } export const get = query({ - args: { userId: v.string(), docId: v.string(), mindmapId: v.string() }, + args: { docId: v.string(), mindmapId: v.string() }, handler: async (ctx, args) => { - const doc = await requireOwnedDocument(ctx, args.userId, args.docId); + const userId = await requireUserId(ctx); + + const doc = await requireOwnedDocument(ctx, userId, args.docId); const mindmapId = normalizeMindmapId(args.docId, args.mindmapId); const row = await ctx.db @@ -71,16 +82,58 @@ export const get = query({ }, }); +export const getForIngest = internalQuery({ + args: { userId: v.string(), docId: v.string(), mindmapId: v.string() }, + handler: async (ctx, args) => { + const doc = await requireOwnedDocument(ctx, args.userId, args.docId); + const mindmapId = normalizeMindmapId(args.docId, args.mindmapId); + + const row = await ctx.db + .query("mindmaps") + .withIndex("by_doc_mindmap", (q) => q.eq("document_id", args.docId).eq("mindmap_id", mindmapId)) + .first(); + + if (!row || row.deleted_at != null) { + return { + ok: true, + data: defaultMindmapData, + meta: { + workspace_id: doc.workspace_id, + document_id: args.docId, + mindmap_id: mindmapId, + exists: false, + deleted_at: row?.deleted_at ?? null, + }, + }; + } + + return { + ok: true, + data: row.data ?? defaultMindmapData, + meta: { + workspace_id: row.workspace_id, + document_id: row.document_id, + mindmap_id: row.mindmap_id, + exists: true, + deleted_at: row.deleted_at, + created_at: row.created_at, + updated_at: row.updated_at, + }, + }; + }, +}); + export const put = mutation({ args: { - userId: v.string(), docId: v.string(), mindmapId: v.string(), data: v.any(), createOnly: v.optional(v.boolean()), }, handler: async (ctx, args) => { - const doc = await requireOwnedDocument(ctx, args.userId, args.docId); + const userId = await requireUserId(ctx); + + const doc = await requireOwnedDocument(ctx, userId, args.docId); const mindmapId = normalizeMindmapId(args.docId, args.mindmapId); const existing = await ctx.db @@ -102,13 +155,13 @@ export const put = mutation({ deleted_at: null, deleted_by: null, }); - await enqueueIngestMindmapJob(ctx, { userId: args.userId, docId: args.docId, mindmapId, debounceMs: 1500 }); + await enqueueIngestMindmapJob(ctx, { userId, docId: args.docId, mindmapId, debounceMs: 1500 }); return { ok: true, created: false, skipped: false, updated_at: ts }; } await ctx.db.insert("mindmaps", { id: `${args.docId}:${mindmapId}`, - user_id: args.userId, + user_id: userId, workspace_id: doc.workspace_id, document_id: args.docId, mindmap_id: mindmapId, @@ -119,15 +172,17 @@ export const put = mutation({ deleted_by: null, }); - await enqueueIngestMindmapJob(ctx, { userId: args.userId, docId: args.docId, mindmapId, debounceMs: 1500 }); + await enqueueIngestMindmapJob(ctx, { userId, docId: args.docId, mindmapId, debounceMs: 1500 }); return { ok: true, created: true, skipped: false, updated_at: ts }; }, }); export const softDelete = mutation({ - args: { userId: v.string(), docId: v.string(), mindmapId: v.string() }, + args: { docId: v.string(), mindmapId: v.string() }, handler: async (ctx, args) => { - await requireOwnedDocument(ctx, args.userId, args.docId); + const userId = await requireUserId(ctx); + + await requireOwnedDocument(ctx, userId, args.docId); const mindmapId = normalizeMindmapId(args.docId, args.mindmapId); const existing = await ctx.db @@ -141,15 +196,17 @@ export const softDelete = mutation({ } const ts = nowIso(); - await ctx.db.patch(existing._id, { deleted_at: ts, deleted_by: args.userId, updated_at: ts }); + await ctx.db.patch(existing._id, { deleted_at: ts, deleted_by: userId, updated_at: ts }); return { ok: true, moved: 1, deleted_at: ts }; }, }); export const restore = mutation({ - args: { userId: v.string(), docId: v.string(), mindmapId: v.string() }, + args: { docId: v.string(), mindmapId: v.string() }, handler: async (ctx, args) => { - await requireOwnedDocument(ctx, args.userId, args.docId); + const userId = await requireUserId(ctx); + + await requireOwnedDocument(ctx, userId, args.docId); const mindmapId = normalizeMindmapId(args.docId, args.mindmapId); const existing = await ctx.db @@ -168,9 +225,11 @@ export const restore = mutation({ }); export const purge = mutation({ - args: { userId: v.string(), docId: v.string(), mindmapId: v.string() }, + args: { docId: v.string(), mindmapId: v.string() }, handler: async (ctx, args) => { - await requireOwnedDocument(ctx, args.userId, args.docId); + const userId = await requireUserId(ctx); + + await requireOwnedDocument(ctx, userId, args.docId); const mindmapId = normalizeMindmapId(args.docId, args.mindmapId); const existing = await ctx.db @@ -188,12 +247,14 @@ export const purge = mutation({ }); export const listByWorkspace = query({ - args: { userId: v.string(), workspaceId: v.string(), includeDeleted: v.optional(v.boolean()) }, + args: { workspaceId: v.string(), includeDeleted: v.optional(v.boolean()) }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + // 说明:阶段 6 先按 membership 存在即可,避免引入复杂权限模型。 const membership = await ctx.db .query("workspace_members") - .withIndex("by_workspace_user", (q) => q.eq("workspace_id", args.workspaceId).eq("user_id", args.userId)) + .withIndex("by_workspace_user", (q) => q.eq("workspace_id", args.workspaceId).eq("user_id", userId)) .first(); if (!membership) { throw new Error("无权操作该工作空间"); @@ -207,7 +268,7 @@ export const listByWorkspace = query({ const includeDeleted = Boolean(args.includeDeleted); return rows - .filter((r) => r.user_id === args.userId) + .filter((r) => r.user_id === userId) .filter((r) => (includeDeleted ? true : r.deleted_at == null)) .sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? "")) .map((r) => ({ @@ -225,10 +286,12 @@ export const listByWorkspace = query({ }); export const copyByDocument = mutation({ - args: { userId: v.string(), sourceDocId: v.string(), targetDocId: v.string() }, + args: { sourceDocId: v.string(), targetDocId: v.string() }, handler: async (ctx, args) => { - const sourceDoc = await requireOwnedDocument(ctx, args.userId, args.sourceDocId); - const targetDoc = await requireOwnedDocument(ctx, args.userId, args.targetDocId); + const userId = await requireUserId(ctx); + + const sourceDoc = await requireOwnedDocument(ctx, userId, args.sourceDocId); + const targetDoc = await requireOwnedDocument(ctx, userId, args.targetDocId); // 说明:同一工作空间复制最常见;若跨工作空间复制,这里仍允许,但 mindmaps 会落到目标页面的 workspace_id。 const sourceRows = await ctx.db @@ -237,7 +300,7 @@ export const copyByDocument = mutation({ .collect(); const sourceMindmaps = sourceRows - .filter((r) => r.user_id === args.userId) + .filter((r) => r.user_id === userId) .filter((r) => r.document_id === args.sourceDocId) .filter((r) => r.deleted_at == null); @@ -253,7 +316,7 @@ export const copyByDocument = mutation({ await ctx.db.insert("mindmaps", { id: `${args.targetDocId}:${r.mindmap_id}`, - user_id: args.userId, + user_id: userId, workspace_id: targetDoc.workspace_id, document_id: args.targetDocId, mindmap_id: r.mindmap_id, @@ -271,11 +334,13 @@ export const copyByDocument = mutation({ }); export const emptyTrashByWorkspace = mutation({ - args: { userId: v.string(), workspaceId: v.string() }, + args: { workspaceId: v.string() }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const membership = await ctx.db .query("workspace_members") - .withIndex("by_workspace_user", (q) => q.eq("workspace_id", args.workspaceId).eq("user_id", args.userId)) + .withIndex("by_workspace_user", (q) => q.eq("workspace_id", args.workspaceId).eq("user_id", userId)) .first(); if (!membership) { throw new Error("无权操作该工作空间"); @@ -286,7 +351,7 @@ export const emptyTrashByWorkspace = mutation({ .withIndex("by_workspace", (q) => q.eq("workspace_id", args.workspaceId)) .collect(); - const toDelete = rows.filter((r) => r.user_id === args.userId && r.deleted_at != null); + const toDelete = rows.filter((r) => r.user_id === userId && r.deleted_at != null); for (const r of toDelete) { await ctx.db.delete(r._id); } diff --git a/wolai-frontend/convex/workspaces.ts b/wolai-frontend/convex/workspaces.ts index c5de5de2..1a33f729 100644 --- a/wolai-frontend/convex/workspaces.ts +++ b/wolai-frontend/convex/workspaces.ts @@ -1,5 +1,6 @@ import { mutation, query } from "./_generated/server"; import { v } from "convex/values"; +import { getAuthUserId } from "@convex-dev/auth/server"; import { nowIso } from "./_utils/time"; import type { MutationCtx, QueryCtx } from "./_generated/server"; @@ -12,6 +13,14 @@ type WorkspaceSummary = { isDefault: boolean; }; +async function requireUserId(ctx: any): Promise { + const userId = await getAuthUserId(ctx); + if (userId === null) { + throw new Error("未登录"); + } + return userId; +} + async function findWorkspaceById(ctx: QueryCtx | MutationCtx, workspaceId: string) { return await ctx.db .query("workspaces") @@ -29,14 +38,15 @@ async function countMembers(ctx: QueryCtx | MutationCtx, workspaceId: string): P export const ensureDefaultWorkspace = mutation({ args: { - userId: v.string(), fallbackName: v.optional(v.string()), workspaceIdIfCreate: v.string(), }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const memberships = await ctx.db .query("workspace_members") - .withIndex("by_user_id", (q) => q.eq("user_id", args.userId)) + .withIndex("by_user_id", (q) => q.eq("user_id", userId)) .collect(); if (memberships.length === 0) { @@ -50,13 +60,13 @@ export const ensureDefaultWorkspace = mutation({ name: workspaceName, type: "personal", icon_url: null, - created_by: args.userId, + created_by: userId, created_at: ts, }); await ctx.db.insert("workspace_members", { workspace_id: args.workspaceIdIfCreate, - user_id: args.userId, + user_id: userId, role: "owner", is_default: true, created_at: ts, @@ -103,12 +113,14 @@ export const ensureDefaultWorkspace = mutation({ }); export const fetchWorkspaceSummaries = query({ - args: { userId: v.string() }, + args: {}, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + // 说明:为了复用 ensureDefaultWorkspace 的返回结构,这里直接走同样的聚合逻辑。 const memberships = await ctx.db .query("workspace_members") - .withIndex("by_user_id", (q) => q.eq("user_id", args.userId)) + .withIndex("by_user_id", (q) => q.eq("user_id", userId)) .collect(); const workspaceIds = Array.from(new Set(memberships.map((m) => m.workspace_id))); @@ -135,12 +147,14 @@ export const fetchWorkspaceSummaries = query({ }); export const switchDefaultWorkspace = mutation({ - args: { userId: v.string(), workspaceId: v.string() }, + args: { workspaceId: v.string() }, handler: async (ctx, args) => { + const userId = await requireUserId(ctx); + const target = await ctx.db .query("workspace_members") .withIndex("by_workspace_user", (q) => - q.eq("workspace_id", args.workspaceId).eq("user_id", args.userId), + q.eq("workspace_id", args.workspaceId).eq("user_id", userId), ) .first(); @@ -150,7 +164,7 @@ export const switchDefaultWorkspace = mutation({ const memberships = await ctx.db .query("workspace_members") - .withIndex("by_user_id", (q) => q.eq("user_id", args.userId)) + .withIndex("by_user_id", (q) => q.eq("user_id", userId)) .collect(); // 说明:Convex 暂无批量 update,这里逐条 patch。 diff --git a/wolai-frontend/src/app/(app)/documents/[id]/page.tsx b/wolai-frontend/src/app/(app)/documents/[id]/page.tsx index 4befa4e6..2a8c8ae6 100644 --- a/wolai-frontend/src/app/(app)/documents/[id]/page.tsx +++ b/wolai-frontend/src/app/(app)/documents/[id]/page.tsx @@ -1,10 +1,8 @@ import { notFound, redirect } from "next/navigation"; -import { createSupabaseServerClient } from "@/lib/supabase/server"; import { DocumentShell } from "@/components/editor/document-shell"; import type { PageOptionsState, DocumentStats } from "@/types/page-options"; import { isConvexEnabled } from "@/lib/convex/enabled"; -import { requireAuthContext } from "@/lib/auth/authContext"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; interface DocumentPageProps { @@ -20,9 +18,8 @@ export default async function DocumentPage({ params, searchParams }: DocumentPag const openTableId = typeof openTableIdRaw === "string" ? openTableIdRaw : null; if (isConvexEnabled()) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); - const doc = await client.query(api.documents.getMeta, { userId: auth.userId, id }); + const { client } = await getAuthedConvexClient(); + const doc = await client.query(api.documents.getMeta, { id }); if (!doc) { notFound(); } @@ -61,58 +58,5 @@ export default async function DocumentPage({ params, searchParams }: DocumentPag ); } - const supabase = await createSupabaseServerClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - redirect("/login"); - } - - const { data: document } = await supabase - .from("documents") - .select( - "id,title,updated_at,workspace_id,wide_layout,use_small_text,show_heading_numbers,show_toc,show_structure,protect_editing,show_word_count,word_count,character_count,block_count", - ) - .eq("user_id", session.user.id) - .eq("id", id) - .single(); - - if (!document) { - notFound(); - } - - const initialOptions: PageOptionsState = { - wideLayout: document.wide_layout ?? false, - smallText: document.use_small_text ?? false, - showHeadingNumbers: document.show_heading_numbers ?? true, - showToc: document.show_toc ?? false, - showStructure: document.show_structure ?? false, - protectEditing: document.protect_editing ?? false, - showWordCount: document.show_word_count ?? true, - }; - - const initialStats: DocumentStats = { - wordCount: document.word_count ?? 0, - characterCount: document.character_count ?? 0, - blockCount: document.block_count ?? 0, - }; - - return ( -
-
- -
-
- ); + redirect("/auth"); } diff --git a/wolai-frontend/src/app/(app)/layout.tsx b/wolai-frontend/src/app/(app)/layout.tsx index 760699f0..78d929e6 100644 --- a/wolai-frontend/src/app/(app)/layout.tsx +++ b/wolai-frontend/src/app/(app)/layout.tsx @@ -5,14 +5,10 @@ import { Breadcrumb } from "@/components/breadcrumb"; import { BottomToolbar } from "@/components/bottom-toolbar"; import { MobileSidebarTrigger } from "@/components/mobile-sidebar-trigger"; import type { DocumentRecord } from "@/lib/documents"; -import { createSupabaseServerClient } from "@/lib/supabase/server"; -import { ensureDefaultWorkspace, fetchWorkspaceSummaries } from "@/lib/workspaces"; -import { fetchSidebarDataset } from "@/lib/sidebar-tree"; import type { SidebarInitialData } from "@/components/sidebar/types"; import { SearchPalette } from "@/components/search/search-palette"; import { isConvexEnabled } from "@/lib/convex/enabled"; -import { requireAuthContext } from "@/lib/auth/authContext"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; import type { MediaAsset } from "@/types/media"; @@ -66,11 +62,9 @@ function makeId(): string { export default async function AppLayout({ children }: { children: ReactNode }) { if (isConvexEnabled()) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); + const { auth, client } = await getAuthedConvexClient(); const ensured = await client.mutation(api.workspaces.ensureDefaultWorkspace, { - userId: auth.userId, fallbackName: auth.name ?? auth.email ?? "我的空间", workspaceIdIfCreate: makeId(), }); @@ -84,11 +78,9 @@ export default async function AppLayout({ children }: { children: ReactNode }) { if (activeWorkspaceId) { const [docRows, trashedDocs] = await Promise.all([ client.query(api.documents.listByWorkspace, { - userId: auth.userId, workspaceId: activeWorkspaceId, }), client.query(api.documents.listTrashedByWorkspace, { - userId: auth.userId, workspaceId: activeWorkspaceId, }), ]); @@ -96,7 +88,6 @@ export default async function AppLayout({ children }: { children: ReactNode }) { documents = docRows as unknown as DocumentRecord[]; const mindmapRows = await client.query(api.mindmaps.listByWorkspace, { - userId: auth.userId, workspaceId: activeWorkspaceId, includeDeleted: true, }); @@ -178,10 +169,10 @@ export default async function AppLayout({ children }: { children: ReactNode }) { } return ( -
+
{sidebarInitialData && } -
-
+
+
@@ -193,58 +184,5 @@ export default async function AppLayout({ children }: { children: ReactNode }) { ); } - const supabase = await createSupabaseServerClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - redirect("/login"); - } - - await ensureDefaultWorkspace(supabase, session.user.id, session.user.email ?? "我的空间"); - const { workspaces, activeWorkspaceId } = await fetchWorkspaceSummaries(supabase, session.user.id); - - let documents: DocumentRecord[] = []; - let sidebarInitialData: SidebarInitialData | null = null; - - if (activeWorkspaceId) { - const dataset = await fetchSidebarDataset(supabase, activeWorkspaceId); - documents = dataset.documents; - const localMindmaps = await detectLocalMindmapDocs( - dataset.documents.map((d) => d.id), - ); - const mindmapDocs = Array.from( - new Set([...(dataset.mindmapDocs ?? []), ...localMindmaps]), - ); - const trashedMindmapAssets = await detectLocalTrashedMindmapAssets( - activeWorkspaceId, - dataset.documents.map((d) => d.id), - ); - sidebarInitialData = { - activeWorkspaceId, - workspaces, - documents: dataset.documents, - trashedDocuments: dataset.trashedDocuments, - trashedMediaAssets: dataset.trashedMediaAssets ?? [], - trashedMindmapAssets, - mediaAssets: dataset.mediaAssets, - mindmapDocs, - }; - } - - return ( -
- {sidebarInitialData && } -
-
- - -
-
{children}
- - -
-
- ); + redirect("/login"); } diff --git a/wolai-frontend/src/app/(auth)/login/page.tsx b/wolai-frontend/src/app/(auth)/login/page.tsx index e8b2a073..696a8eba 100644 --- a/wolai-frontend/src/app/(auth)/login/page.tsx +++ b/wolai-frontend/src/app/(auth)/login/page.tsx @@ -1,116 +1,26 @@ "use client"; -import { useCallback, useState } from "react"; +import { useEffect } from "react"; import { useRouter } from "next/navigation"; -import { getSupabaseBrowserClient } from "@/lib/supabase/client"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Separator } from "@/components/ui/separator"; export default function LoginPage() { const router = useRouter(); - const [email, setEmail] = useState("test@163.com"); - const [password, setPassword] = useState("123456"); - const [loading, setLoading] = useState(false); - const [message, setMessage] = useState(""); - const [sessionInfo, setSessionInfo] = useState(""); - const handleLogin = useCallback(async () => { - setLoading(true); - setMessage(""); - const supabaseBrowser = getSupabaseBrowserClient(); - let data: unknown = null; - let error: { message?: string } | null = null; - try { - const result = await supabaseBrowser.auth.signInWithPassword({ - email, - password, - }); - data = result.data; - error = result.error as unknown as { message?: string } | null; - } catch (err) { - const runtimeCfg = (() => { - try { - return (window as unknown as { __MNOTE_RUNTIME_CONFIG__?: { supabaseUrl?: string } }).__MNOTE_RUNTIME_CONFIG__; - } catch { - return undefined; - } - })(); - const supabaseUrlHint = runtimeCfg?.supabaseUrl ? `(Supabase:${runtimeCfg.supabaseUrl})` : ""; - setLoading(false); - setMessage(`登录失败:网络或证书异常 ${supabaseUrlHint}\n${String(err)}`); - return; - } - setLoading(false); - if (error) { - setMessage(`登录失败:${error.message}`); - return; - } - setMessage("登录成功,准备跳转..."); - const session = (data as { session?: unknown } | null)?.session ?? null; - setSessionInfo(JSON.stringify(session, null, 2)); - // 说明:曾出现“需要二次登录”的现象(服务端仍读到旧 cookie/session)。 - // 这里用整页跳转强制刷新,确保 App Router 的服务端组件读取到最新会话。 - if (typeof window !== "undefined") { - window.location.href = "/"; - return; - } - router.replace("/"); - }, [email, password, router]); - - const handleGetSession = useCallback(async () => { - setLoading(true); - const supabaseBrowser = getSupabaseBrowserClient(); - const { - data: { session }, - error, - } = await supabaseBrowser.auth.getSession(); - setLoading(false); - if (error) { - setMessage(`获取会话失败:${error.message}`); - return; - } - if (!session) { - setMessage("当前未登录"); - setSessionInfo(""); - return; - } - setMessage("发现有效 Session"); - setSessionInfo(JSON.stringify(session, null, 2)); - }, []); + useEffect(() => { + // 已移除 Supabase 登录,统一走 Convex Auth 页面。 + router.replace("/auth"); + }, [router]); return (
- Supabase 登录 -

测试账号:test@163.com / 123456

+ 正在跳转 +

该页面已迁移到 Convex Auth(/auth)。

-
- - setEmail(e.target.value)} /> -
-
- - setPassword(e.target.value)} /> -
-
- - -
- {message &&

{message}

} - - {sessionInfo && ( -
-              {sessionInfo}
-            
- )} +

如果没有自动跳转,请手动打开 /auth。

diff --git a/wolai-frontend/src/app/api/ai-agent/client-tool-result/route.ts b/wolai-frontend/src/app/api/ai-agent/client-tool-result/route.ts index 45d7c73b..6dfc54c4 100644 --- a/wolai-frontend/src/app/api/ai-agent/client-tool-result/route.ts +++ b/wolai-frontend/src/app/api/ai-agent/client-tool-result/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { requireAuthContext } from "@/lib/auth/authContext"; import { @@ -36,12 +35,7 @@ export async function POST(request: Request) { const resolvedUserId = async () => { if (userId) return userId; - const supabase = await createSupabaseRouteClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - if (!session) return null; - return session.user.id; + return null; }; const finalUserId = await resolvedUserId(); diff --git a/wolai-frontend/src/app/api/ai-agent/run/route.ts b/wolai-frontend/src/app/api/ai-agent/run/route.ts index 2fe581e9..8e5f8b8f 100644 --- a/wolai-frontend/src/app/api/ai-agent/run/route.ts +++ b/wolai-frontend/src/app/api/ai-agent/run/route.ts @@ -4,7 +4,6 @@ import { loadLocalAiConfig } from "@/lib/ai/localAiConfig"; import { createToolRegistry, resolveAllowedToolIds } from "@/lib/ai-agent/tools/registry"; import { builtinTools, builtinToolSets } from "@/lib/ai-agent/tools/builtins/registryBuiltins"; import { runAiAgent } from "@/lib/ai-agent/runtime/runAgent"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { searchSearxng } from "@/lib/ai-agent/tools/builtins/searchWeb"; import { createMindmapServerTools, type MindmapSupabaseClient } from "@/lib/ai-agent/tools/builtins/mindmap/mindmapServerTools"; @@ -80,12 +79,7 @@ export async function POST(request: Request) { const { auth, client } = await getAuthedConvexClient(); return { userId: auth.userId, supabase: null as any, convexClient: client }; } - const supabase = await createSupabaseRouteClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - if (!session) return { userId: "", supabase: null as any, convexClient: null as any }; - return { userId: session.user.id, supabase, convexClient: null as any }; + return { userId: "", supabase: null as any, convexClient: null as any }; })(); if (!userId) { return NextResponse.json({ error: "未登录" }, { status: 401 }); diff --git a/wolai-frontend/src/app/api/auth/callback/route.ts b/wolai-frontend/src/app/api/auth/callback/route.ts index 1df0e9d6..b79ddf50 100644 --- a/wolai-frontend/src/app/api/auth/callback/route.ts +++ b/wolai-frontend/src/app/api/auth/callback/route.ts @@ -1,18 +1,8 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; export async function POST(request: NextRequest) { - const supabase = await createSupabaseRouteClient(); - const { event, session } = await request.json(); - - if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") { - await supabase.auth.setSession(session); - } - - if (event === "SIGNED_OUT") { - await supabase.auth.signOut(); - } - + // 说明:历史兼容接口(Supabase Auth callback)。当前项目迁移到 Convex 后,这里保持空实现。 + await request.json().catch(() => null); return NextResponse.json({ success: true }); } diff --git a/wolai-frontend/src/app/api/auth/signin/route.ts b/wolai-frontend/src/app/api/auth/signin/route.ts index 8925b718..b3bf7a3d 100644 --- a/wolai-frontend/src/app/api/auth/signin/route.ts +++ b/wolai-frontend/src/app/api/auth/signin/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; +import { isDevAuthEnabled } from "@/lib/auth/devUser"; interface SignInRequest { email: string; @@ -19,15 +20,14 @@ export async function POST(request: Request) { // Convex 模式 if (isConvexEnabled()) { try { - const body = await request.json() as SignInRequest; - const { email, password, name, flow } = body; + await request.json().catch(() => null); - // TODO: 调用 Convex Auth mutation 处理登录/注册 - // 当前暂时返回未实现错误 - return NextResponse.json( - { error: "Convex Auth API 路由暂未实现,请稍后实现" }, - { status: 501 } - ); + // 说明:当前项目默认使用开发用户鉴权(USE_CONVEX=1),无需走登录/注册。 + if (isDevAuthEnabled()) { + return NextResponse.json({ ok: true, mode: "dev_user" }); + } + + return NextResponse.json({ error: "Convex Auth 登录/注册尚未接入" }, { status: 501 }); } catch (error) { console.error("Convex Auth error:", error); return NextResponse.json( @@ -37,74 +37,5 @@ export async function POST(request: Request) { } } - // Supabase 模式(保留兼容) - try { - const { createSupabaseRouteClient } = await import("@/lib/supabase/server"); - const supabase = await createSupabaseRouteClient(); - - const body = await request.json() as SignInRequest; - const { email, password, name, flow } = body; - - if (flow === "signUp") { - // 注册 - const { data, error } = await supabase.auth.signUp({ - email, - password, - options: { - data: { - name: name ?? "", - }, - }, - }); - - if (error) { - return NextResponse.json( - { error: error.message }, - { status: 400 } - ); - } - - // 注册成功后自动登录 - const { error: signInError } = await supabase.auth.signInWithPassword({ - email, - password, - }); - - if (signInError) { - return NextResponse.json( - { error: signInError.message }, - { status: 401 } - ); - } - - return NextResponse.json( - { message: "注册并登录成功" }, - { status: 200 } - ); - } else { - // 登录 - const { data, error } = await supabase.auth.signInWithPassword({ - email, - password, - }); - - if (error) { - return NextResponse.json( - { error: error.message }, - { status: 401 } - ); - } - - return NextResponse.json( - { message: "登录成功" }, - { status: 200 } - ); - } - } catch (error) { - console.error("Supabase Auth error:", error); - return NextResponse.json( - { error: "服务器错误" }, - { status: 500 } - ); - } + return NextResponse.json({ error: "仅支持 Convex 模式" }, { status: 501 }); } diff --git a/wolai-frontend/src/app/api/blocks/embed/route.ts b/wolai-frontend/src/app/api/blocks/embed/route.ts index 0ec1971a..b7a241e8 100644 --- a/wolai-frontend/src/app/api/blocks/embed/route.ts +++ b/wolai-frontend/src/app/api/blocks/embed/route.ts @@ -1,9 +1,7 @@ import { randomUUID } from "crypto"; 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; import { getBlocksFromDocumentContent, findBlockInTree, withBlocksWrittenBack } from "@/lib/blocks"; @@ -26,16 +24,15 @@ export async function POST(request: Request) { } if (isConvexEnabled()) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); + const { client } = await getAuthedConvexClient(); - const source = await client.query(api.documents.getContent, { userId: auth.userId, id: sourceDocumentId }); + const source = await client.query(api.documents.getContent, { id: sourceDocumentId }); if (!source) return NextResponse.json({ error: "源页面不存在或无权限" }, { status: 404 }); const sourceBlocks = getBlocksFromDocumentContent(source.content); const hit = findBlockInTree(sourceBlocks, blockId); if (!hit) return NextResponse.json({ error: "源块不存在或无权限" }, { status: 404 }); - const target = await client.query(api.documents.getContent, { userId: auth.userId, id: targetDocumentId }); + const target = await client.query(api.documents.getContent, { id: targetDocumentId }); if (!target) return NextResponse.json({ error: "目标页面不存在或无权限" }, { status: 404 }); const targetBlocks = getBlocksFromDocumentContent(target.content); @@ -53,11 +50,14 @@ export async function POST(request: Request) { const nextBlocks = [...targetBlocks, referenceBlock]; const payload = withBlocksWrittenBack(target.content, nextBlocks); - await client.mutation(api.documents.updateContent, { userId: auth.userId, id: targetDocumentId, content: payload }); + await client.mutation(api.documents.updateContent, { id: targetDocumentId, content: payload }); return NextResponse.json({ ok: true }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -112,4 +112,5 @@ export async function POST(request: Request) { if (error) return NextResponse.json({ error: error.message }, { status: 500 }); return NextResponse.json({ ok: true }); + */ } diff --git a/wolai-frontend/src/app/api/blocks/get/route.ts b/wolai-frontend/src/app/api/blocks/get/route.ts index def1e065..f181e6ad 100644 --- a/wolai-frontend/src/app/api/blocks/get/route.ts +++ b/wolai-frontend/src/app/api/blocks/get/route.ts @@ -1,8 +1,6 @@ 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; import { getBlocksFromDocumentContent, findBlockInTree } from "@/lib/blocks"; @@ -16,9 +14,8 @@ export async function GET(request: Request) { } if (isConvexEnabled()) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); - const doc = await client.query(api.documents.getContent, { userId: auth.userId, id: sourceDocumentId }); + const { client } = await getAuthedConvexClient(); + const doc = await client.query(api.documents.getContent, { id: sourceDocumentId }); if (!doc) return NextResponse.json({ error: "页面不存在或无权限" }, { status: 404 }); const blocks = getBlocksFromDocumentContent(doc.content); const hit = findBlockInTree(blocks, blockId); @@ -26,6 +23,9 @@ export async function GET(request: Request) { return NextResponse.json({ ok: true, block: hit.block }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -45,4 +45,5 @@ export async function GET(request: Request) { if (!hit) return NextResponse.json({ error: "块不存在或无权限" }, { status: 404 }); return NextResponse.json({ ok: true, block: hit.block }); + */ } diff --git a/wolai-frontend/src/app/api/blocks/move/route.ts b/wolai-frontend/src/app/api/blocks/move/route.ts index 95d58159..52f95db6 100644 --- a/wolai-frontend/src/app/api/blocks/move/route.ts +++ b/wolai-frontend/src/app/api/blocks/move/route.ts @@ -1,8 +1,6 @@ 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; import { getBlocksFromDocumentContent, removeBlockSubtree, withBlocksWrittenBack } from "@/lib/blocks"; @@ -26,28 +24,30 @@ export async function POST(request: Request) { } if (isConvexEnabled()) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); + const { client } = await getAuthedConvexClient(); - const source = await client.query(api.documents.getContent, { userId: auth.userId, id: sourceDocumentId }); + const source = await client.query(api.documents.getContent, { id: sourceDocumentId }); if (!source) return NextResponse.json({ error: "源页面不存在或无权限" }, { status: 404 }); const sourceBlocks = getBlocksFromDocumentContent(source.content); const removedRes = removeBlockSubtree(sourceBlocks, blockId); if (!removedRes.removed) return NextResponse.json({ error: "源块不存在或无权限" }, { status: 404 }); - const target = await client.query(api.documents.getContent, { userId: auth.userId, id: targetDocumentId }); + const target = await client.query(api.documents.getContent, { id: targetDocumentId }); if (!target) return NextResponse.json({ error: "目标页面不存在或无权限" }, { status: 404 }); const targetBlocks = getBlocksFromDocumentContent(target.content); const nextSourcePayload = withBlocksWrittenBack(source.content, removedRes.nextBlocks); const nextTargetPayload = withBlocksWrittenBack(target.content, [...targetBlocks, removedRes.removed]); - await client.mutation(api.documents.updateContent, { userId: auth.userId, id: sourceDocumentId, content: nextSourcePayload }); - await client.mutation(api.documents.updateContent, { userId: auth.userId, id: targetDocumentId, content: nextTargetPayload }); + await client.mutation(api.documents.updateContent, { id: sourceDocumentId, content: nextSourcePayload }); + await client.mutation(api.documents.updateContent, { id: targetDocumentId, content: nextTargetPayload }); return NextResponse.json({ ok: true }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -96,4 +96,5 @@ export async function POST(request: Request) { if (tgtErr) return NextResponse.json({ error: tgtErr.message }, { status: 500 }); return NextResponse.json({ ok: true }); + */ } diff --git a/wolai-frontend/src/app/api/blocks/patch/route.ts b/wolai-frontend/src/app/api/blocks/patch/route.ts index b4828169..4c510a07 100644 --- a/wolai-frontend/src/app/api/blocks/patch/route.ts +++ b/wolai-frontend/src/app/api/blocks/patch/route.ts @@ -1,8 +1,6 @@ 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; import { getBlocksFromDocumentContent, replaceBlockInTree, withBlocksWrittenBack } from "@/lib/blocks"; @@ -20,9 +18,8 @@ export async function POST(request: Request) { } if (isConvexEnabled()) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); - const doc = await client.query(api.documents.getContent, { userId: auth.userId, id: sourceDocumentId }); + const { client } = await getAuthedConvexClient(); + const doc = await client.query(api.documents.getContent, { id: sourceDocumentId }); if (!doc) return NextResponse.json({ error: "页面不存在或无权限" }, { status: 404 }); const blocks = getBlocksFromDocumentContent(doc.content); @@ -30,10 +27,13 @@ export async function POST(request: Request) { if (!replaced.ok) return NextResponse.json({ error: "块不存在或无权限" }, { status: 404 }); const payload = withBlocksWrittenBack(doc.content, replaced.nextBlocks); - await client.mutation(api.documents.updateContent, { userId: auth.userId, id: sourceDocumentId, content: payload }); + await client.mutation(api.documents.updateContent, { id: sourceDocumentId, content: payload }); return NextResponse.json({ ok: true }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -61,4 +61,5 @@ export async function POST(request: Request) { if (error) return NextResponse.json({ error: error.message }, { status: 500 }); return NextResponse.json({ ok: true }); + */ } diff --git a/wolai-frontend/src/app/api/dev/ingest/enqueue/route.ts b/wolai-frontend/src/app/api/dev/ingest/enqueue/route.ts index 4adda016..61842db8 100644 --- a/wolai-frontend/src/app/api/dev/ingest/enqueue/route.ts +++ b/wolai-frontend/src/app/api/dev/ingest/enqueue/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { requireAuthContext } from "@/lib/auth/authContext"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getConvexAuthedHttpClient } from "@/lib/convex/server"; import { api } from "@/lib/convex/api"; export const dynamic = "force-dynamic"; @@ -12,7 +12,7 @@ type Payload = export async function POST(request: Request) { const auth = await requireAuthContext(); - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); const body = (await request.json().catch(() => null)) as Payload | null; if (!body) { diff --git a/wolai-frontend/src/app/api/dev/jobs/demo/route.ts b/wolai-frontend/src/app/api/dev/jobs/demo/route.ts index 00dc962c..2b3ea0eb 100644 --- a/wolai-frontend/src/app/api/dev/jobs/demo/route.ts +++ b/wolai-frontend/src/app/api/dev/jobs/demo/route.ts @@ -1,14 +1,14 @@ import { NextResponse } from "next/server"; import { randomUUID } from "crypto"; import { requireAuthContext } from "@/lib/auth/authContext"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getConvexAuthedHttpClient } from "@/lib/convex/server"; import { api } from "@/lib/convex/api"; export const dynamic = "force-dynamic"; export async function POST(request: Request) { const auth = await requireAuthContext(); - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); const { ms }: { ms?: number } = await request.json().catch(() => ({})); const id = randomUUID(); @@ -24,7 +24,7 @@ export async function POST(request: Request) { export async function GET(request: Request) { const auth = await requireAuthContext(); - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); const url = new URL(request.url); const id = url.searchParams.get("id") ?? ""; diff --git a/wolai-frontend/src/app/api/documents/content/route.ts b/wolai-frontend/src/app/api/documents/content/route.ts index e08ae025..05aead57 100644 --- a/wolai-frontend/src/app/api/documents/content/route.ts +++ b/wolai-frontend/src/app/api/documents/content/route.ts @@ -1,15 +1,12 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getAuthedConvexClient } from "@/lib/convex/route"; 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 = await requireAuthContext(); const url = new URL(request.url); const documentId = url.searchParams.get("documentId") ?? ""; @@ -17,9 +14,8 @@ export async function GET(request: Request) { return NextResponse.json({ error: "缺少 documentId" }, { status: 400 }); } - const client = getConvexHttpClient(); + const { client } = await getAuthedConvexClient(); const result = await client.query(api.documents.getContent, { - userId: auth.userId, id: documentId, }); @@ -30,36 +26,5 @@ export async function GET(request: Request) { return NextResponse.json({ content: result.content ?? null }); } - const supabase = await createSupabaseRouteClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - return NextResponse.json({ error: "未登录" }, { status: 401 }); - } - - const url = new URL(request.url); - const documentId = url.searchParams.get("documentId") ?? ""; - - if (!documentId) { - return NextResponse.json({ error: "缺少 documentId" }, { status: 400 }); - } - - const { data: document, error } = await supabase - .from("documents") - .select("id,content") - .eq("id", documentId) - .eq("user_id", session.user.id) - .single(); - - if (error) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - - if (!document) { - return NextResponse.json({ error: "页面不存在" }, { status: 404 }); - } - - return NextResponse.json({ content: document.content ?? null }); + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); } diff --git a/wolai-frontend/src/app/api/documents/copy-tree/route.ts b/wolai-frontend/src/app/api/documents/copy-tree/route.ts index a6d18982..7c841c6c 100644 --- a/wolai-frontend/src/app/api/documents/copy-tree/route.ts +++ b/wolai-frontend/src/app/api/documents/copy-tree/route.ts @@ -1,8 +1,6 @@ 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; import path from "path"; import { promises as fs } from "fs"; @@ -163,8 +161,7 @@ function replaceAssetRefsInContent(content: Json | null, assetMap: Map it.documentId))); - const firstMeta = await client.query(api.documents.getMeta, { userId: auth.userId, id: sourceIds[0] }); + const firstMeta = await client.query(api.documents.getMeta, { id: sourceIds[0] }); if (!firstMeta) { return NextResponse.json({ error: "源页面不存在或无权限" }, { status: 404 }); } @@ -204,7 +201,6 @@ export async function POST(request: Request) { const wid = workspaceId; const allDocs = await client.query(api.documents.listAllForCopy, { - userId: auth.userId, workspaceId: wid, }); @@ -275,7 +271,6 @@ export async function POST(request: Request) { titleSet.add(newTitle); await client.mutation(api.documents.create, { - userId: auth.userId, id: newId, workspaceId: wid, parentId, @@ -286,7 +281,6 @@ export async function POST(request: Request) { await ensureDocumentScaffold(newId, newTitle); await client.mutation(api.mindmaps.copyByDocument, { - userId: auth.userId, sourceDocId: item.old.id, targetDocId: newId, }); @@ -298,6 +292,9 @@ export async function POST(request: Request) { }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -543,4 +540,5 @@ export async function POST(request: Request) { return NextResponse.json({ items: insertedDocs.map((d) => ({ oldId: d.oldId, newId: d.newId })), }); + */ } diff --git a/wolai-frontend/src/app/api/documents/create-child/route.ts b/wolai-frontend/src/app/api/documents/create-child/route.ts index 6eb937e8..b8dc2bfc 100644 --- a/wolai-frontend/src/app/api/documents/create-child/route.ts +++ b/wolai-frontend/src/app/api/documents/create-child/route.ts @@ -1,9 +1,6 @@ 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; function makeId(): string { @@ -22,8 +19,7 @@ export const dynamic = "force-dynamic"; export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); + const { auth, client } = await getAuthedConvexClient(); const { parentId, title, blocks }: CreateChildPayload = await request.json(); @@ -35,7 +31,7 @@ export async function POST(request: Request) { let accessScope: "private" | "shared" | "public" = "private"; if (parentId) { - const parentDoc = await client.query(api.documents.getMeta, { userId: auth.userId, id: parentId }); + const parentDoc = await client.query(api.documents.getMeta, { id: parentId }); if (!parentDoc) { return NextResponse.json({ error: "父页面不存在" }, { status: 404 }); } @@ -43,7 +39,6 @@ export async function POST(request: Request) { 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(), }); @@ -58,7 +53,6 @@ export async function POST(request: Request) { const pageId = makeId(); const created = await client.mutation(api.documents.create, { - userId: auth.userId, id: pageId, workspaceId, parentId: parentId ?? null, @@ -73,6 +67,9 @@ export async function POST(request: Request) { }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -159,4 +156,5 @@ export async function POST(request: Request) { pageId: data.id, title: data.title ?? resolvedTitle, }); + */ } diff --git a/wolai-frontend/src/app/api/documents/create/route.ts b/wolai-frontend/src/app/api/documents/create/route.ts index 98f4204d..85ae6025 100644 --- a/wolai-frontend/src/app/api/documents/create/route.ts +++ b/wolai-frontend/src/app/api/documents/create/route.ts @@ -1,16 +1,13 @@ import { randomUUID } from "crypto"; import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; -import { ensureDefaultWorkspace, resolveActiveWorkspaceId } from "@/lib/workspaces"; import type { Json } from "@/types/supabase"; import { composeContentWithBlocks, extractBlocksFromContent } from "@/lib/document-content"; 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"; +import { getAuthedConvexClient } from "@/lib/convex/route"; const documentsBaseDir = getDocumentsBaseDir(); @@ -41,13 +38,11 @@ export async function POST(request: Request) { } async function handleCreateRequestConvex(request: Request) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); + const { auth, client } = await getAuthedConvexClient(); 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(), }); @@ -58,7 +53,6 @@ async function handleCreateRequestConvex(request: Request) { if (parentId) { const parentDoc = await client.query(api.documents.getMeta, { - userId: auth.userId, id: parentId, }); @@ -68,7 +62,7 @@ async function handleCreateRequestConvex(request: Request) { 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 }); + const parentContentRes = await client.query(api.documents.getContent, { id: parentId }); parentContent = (parentContentRes?.content as Json | null) ?? null; } else { workspaceId = workspaceBootstrap.activeWorkspaceId || null; @@ -80,7 +74,6 @@ async function handleCreateRequestConvex(request: Request) { const id = randomUUID(); const data = await client.mutation(api.documents.create, { - userId: auth.userId, id, workspaceId, parentId: parentId ?? null, @@ -108,7 +101,6 @@ async function handleCreateRequestConvex(request: Request) { const payload = composeContentWithBlocks(parentContent, nextBlocks); await client.mutation(api.documents.updateContent, { - userId: auth.userId, id: parentId, content: payload, }); @@ -118,6 +110,9 @@ async function handleCreateRequestConvex(request: Request) { } async function handleCreateRequest(request: Request) { + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -228,4 +223,5 @@ async function handleCreateRequest(request: Request) { } return NextResponse.json(data); + */ } diff --git a/wolai-frontend/src/app/api/documents/delete/route.ts b/wolai-frontend/src/app/api/documents/delete/route.ts index 35721268..4d420e90 100644 --- a/wolai-frontend/src/app/api/documents/delete/route.ts +++ b/wolai-frontend/src/app/api/documents/delete/route.ts @@ -1,24 +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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; export const dynamic = "force-dynamic"; export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await 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 }); + const { client } = await getAuthedConvexClient(); + await client.mutation(api.documents.softDelete, { id: documentId }); return NextResponse.json({ success: true }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -48,4 +48,5 @@ export async function POST(request: Request) { } return NextResponse.json({ success: true }); + */ } diff --git a/wolai-frontend/src/app/api/documents/duplicate/route.ts b/wolai-frontend/src/app/api/documents/duplicate/route.ts index e7f2182d..29ca79b9 100644 --- a/wolai-frontend/src/app/api/documents/duplicate/route.ts +++ b/wolai-frontend/src/app/api/documents/duplicate/route.ts @@ -1,8 +1,6 @@ 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; import path from "path"; import { promises as fs } from "fs"; @@ -48,15 +46,14 @@ async function copyMindmapIfExists(sourceId: string, targetId: string) { export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); + const { client } = await getAuthedConvexClient(); 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 }); + const sourceDoc = await client.query(api.documents.getMeta, { id: documentId }); if (!sourceDoc) { return NextResponse.json({ error: "页面不存在或无权访问" }, { status: 404 }); } @@ -70,7 +67,6 @@ export async function POST(request: Request) { : `${Date.now()}_${Math.random().toString(16).slice(2)}`; const duplicated = await client.mutation(api.documents.duplicate, { - userId: auth.userId, sourceId: documentId, newId, title: duplicatedTitle, @@ -78,7 +74,6 @@ export async function POST(request: Request) { await ensureDocumentScaffold(duplicated.id, duplicated.title ?? duplicatedTitle); await client.mutation(api.mindmaps.copyByDocument, { - userId: auth.userId, sourceDocId: documentId, targetDocId: duplicated.id, }); @@ -91,6 +86,9 @@ export async function POST(request: Request) { }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -164,4 +162,5 @@ export async function POST(request: Request) { await copyMindmapIfExists(sourceDoc.id, duplicated.id); return NextResponse.json(duplicated); + */ } diff --git a/wolai-frontend/src/app/api/documents/embed/route.ts b/wolai-frontend/src/app/api/documents/embed/route.ts index a0cced42..113bd40c 100644 --- a/wolai-frontend/src/app/api/documents/embed/route.ts +++ b/wolai-frontend/src/app/api/documents/embed/route.ts @@ -1,6 +1,5 @@ import { randomUUID } from "crypto"; 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"; @@ -22,12 +21,12 @@ export async function POST(request: Request) { return NextResponse.json({ error: "缺少参数" }, { status: 400 }); } - const sourceDoc = await client.query(api.documents.getMeta, { userId: auth.userId, id: sourceId }); + const sourceDoc = await client.query(api.documents.getMeta, { id: sourceId }); if (!sourceDoc) { return NextResponse.json({ error: "原始页面不存在或无权限" }, { status: 404 }); } - const targetContent = await client.query(api.documents.getContent, { userId: auth.userId, id: targetId }); + const targetContent = await client.query(api.documents.getContent, { id: targetId }); if (!targetContent) { return NextResponse.json({ error: "目标页面不存在或无权限" }, { status: 404 }); } @@ -48,7 +47,6 @@ export async function POST(request: Request) { const payload: Json = composeContentWithBlocks(targetContent.content, nextBlocks); await client.mutation(api.documents.updateContent, { - userId: auth.userId, id: targetId, content: payload, }); @@ -56,6 +54,9 @@ export async function POST(request: Request) { return NextResponse.json({ ok: true }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -119,4 +120,5 @@ export async function POST(request: Request) { } return NextResponse.json({ ok: true }); + */ } diff --git a/wolai-frontend/src/app/api/documents/empty-trash/route.ts b/wolai-frontend/src/app/api/documents/empty-trash/route.ts index 745def66..76a89255 100644 --- a/wolai-frontend/src/app/api/documents/empty-trash/route.ts +++ b/wolai-frontend/src/app/api/documents/empty-trash/route.ts @@ -1,8 +1,6 @@ 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; export const dynamic = "force-dynamic"; @@ -13,16 +11,18 @@ interface EmptyTrashPayload { export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await 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 }); + const { client } = await getAuthedConvexClient(); + await client.mutation(api.documents.emptyTrashByWorkspace, { workspaceId }); return NextResponse.json({ success: true }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -65,4 +65,5 @@ export async function POST(request: Request) { } return NextResponse.json({ success: true }); + */ } diff --git a/wolai-frontend/src/app/api/documents/move/route.ts b/wolai-frontend/src/app/api/documents/move/route.ts index 73482c79..faa469dd 100644 --- a/wolai-frontend/src/app/api/documents/move/route.ts +++ b/wolai-frontend/src/app/api/documents/move/route.ts @@ -1,8 +1,6 @@ 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; interface MovePayload { @@ -13,12 +11,10 @@ interface MovePayload { export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await requireAuthContext(); const { documentId, parentId = null, position }: MovePayload = await request.json(); const sortOrder = Number.isFinite(position) ? Math.floor(position) : 0; - const client = getConvexHttpClient(); + const { client } = await getAuthedConvexClient(); await client.mutation(api.documents.move, { - userId: auth.userId, id: documentId, parentId, sortOrder, @@ -26,6 +22,9 @@ export async function POST(request: Request) { return NextResponse.json({ ok: true }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -52,4 +51,5 @@ export async function POST(request: Request) { } return NextResponse.json({ ok: true }); + */ } diff --git a/wolai-frontend/src/app/api/documents/options/route.ts b/wolai-frontend/src/app/api/documents/options/route.ts index f17c6df8..b78af7b4 100644 --- a/wolai-frontend/src/app/api/documents/options/route.ts +++ b/wolai-frontend/src/app/api/documents/options/route.ts @@ -1,38 +1,23 @@ 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; import type { PageOptionsState } from "@/types/page-options"; -import type { Database } from "@/types/supabase"; type OptionsPayload = { documentId: string; options: Partial; }; -const COLUMN_MAP: Record = { - wideLayout: "wide_layout", - smallText: "use_small_text", - showHeadingNumbers: "show_heading_numbers", - showToc: "show_toc", - showStructure: "show_structure", - protectEditing: "protect_editing", - showWordCount: "show_word_count", -}; - export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await requireAuthContext(); const { documentId, options }: OptionsPayload = await request.json(); if (!documentId || !options) { return NextResponse.json({ error: "缺少参数" }, { status: 400 }); } - const client = getConvexHttpClient(); + const { client } = await getAuthedConvexClient(); await client.mutation(api.documents.updateOptions, { - userId: auth.userId, id: documentId, options: { wideLayout: options.wideLayout, @@ -48,43 +33,5 @@ export async function POST(request: Request) { return NextResponse.json({ ok: true }); } - const supabase = await createSupabaseRouteClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - return NextResponse.json({ error: "未登录" }, { status: 401 }); - } - - const { documentId, options }: OptionsPayload = await request.json(); - if (!documentId || !options) { - return NextResponse.json({ error: "缺少参数" }, { status: 400 }); - } - - const updatePayload: Record = {}; - (Object.keys(options) as (keyof PageOptionsState)[]).forEach((key) => { - const column = COLUMN_MAP[key]; - if (!column) return; - const value = options[key]; - if (typeof value === "boolean") { - updatePayload[column as string] = value; - } - }); - - if (Object.keys(updatePayload).length === 0) { - return NextResponse.json({ error: "缺少可更新的选项" }, { status: 400 }); - } - - const { error } = await supabase - .from("documents") - .update(updatePayload) - .eq("id", documentId) - .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 }); } diff --git a/wolai-frontend/src/app/api/documents/purge/route.ts b/wolai-frontend/src/app/api/documents/purge/route.ts index 3253b02d..c55f3f38 100644 --- a/wolai-frontend/src/app/api/documents/purge/route.ts +++ b/wolai-frontend/src/app/api/documents/purge/route.ts @@ -1,24 +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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; export const dynamic = "force-dynamic"; export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await 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 }); + const { client } = await getAuthedConvexClient(); + await client.mutation(api.documents.purge, { id: documentId }); return NextResponse.json({ success: true }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -45,4 +45,5 @@ export async function POST(request: Request) { } return NextResponse.json({ success: true }); + */ } diff --git a/wolai-frontend/src/app/api/documents/restore/route.ts b/wolai-frontend/src/app/api/documents/restore/route.ts index 21cf319d..97775b42 100644 --- a/wolai-frontend/src/app/api/documents/restore/route.ts +++ b/wolai-frontend/src/app/api/documents/restore/route.ts @@ -1,24 +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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; export const dynamic = "force-dynamic"; export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await 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 }); + const { client } = await getAuthedConvexClient(); + await client.mutation(api.documents.restore, { id: documentId }); return NextResponse.json({ success: true }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -50,4 +50,5 @@ export async function POST(request: Request) { } return NextResponse.json({ success: true }); + */ } diff --git a/wolai-frontend/src/app/api/documents/save/route.ts b/wolai-frontend/src/app/api/documents/save/route.ts index 4376d3a4..b0019621 100644 --- a/wolai-frontend/src/app/api/documents/save/route.ts +++ b/wolai-frontend/src/app/api/documents/save/route.ts @@ -1,9 +1,7 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; -import { requireAuthContext } from "@/lib/auth/authContext"; interface SavePayload { documentId: string; @@ -12,37 +10,14 @@ interface SavePayload { export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await requireAuthContext(); const { documentId, content }: SavePayload = await request.json(); - const client = getConvexHttpClient(); + const { client } = await getAuthedConvexClient(); await client.mutation(api.documents.updateContent, { - userId: auth.userId, id: documentId, content, }); return NextResponse.json({ ok: true }); } - const supabase = await createSupabaseRouteClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - return NextResponse.json({ error: "未登录" }, { status: 401 }); - } - - const { documentId, content }: SavePayload = await request.json(); - - const { error } = await supabase - .from("documents") - .update({ content }) - .eq("id", documentId) - .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 }); } diff --git a/wolai-frontend/src/app/api/documents/stats/route.ts b/wolai-frontend/src/app/api/documents/stats/route.ts index b0795fd1..e80c2b92 100644 --- a/wolai-frontend/src/app/api/documents/stats/route.ts +++ b/wolai-frontend/src/app/api/documents/stats/route.ts @@ -1,8 +1,6 @@ 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; import type { DocumentStats } from "@/types/page-options"; @@ -13,15 +11,13 @@ interface StatsPayload { export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await requireAuthContext(); const { documentId, stats }: StatsPayload = await request.json(); if (!documentId || !stats) { return NextResponse.json({ error: "缺少参数" }, { status: 400 }); } - const client = getConvexHttpClient(); + const { client } = await getAuthedConvexClient(); await client.mutation(api.documents.updateStats, { - userId: auth.userId, id: documentId, wordCount: stats.wordCount, characterCount: stats.characterCount, @@ -31,33 +27,5 @@ export async function POST(request: Request) { return NextResponse.json({ ok: true }); } - const supabase = await createSupabaseRouteClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - return NextResponse.json({ error: "未登录" }, { status: 401 }); - } - - const { documentId, stats }: StatsPayload = await request.json(); - if (!documentId || !stats) { - return NextResponse.json({ error: "缺少参数" }, { status: 400 }); - } - - const { error } = await supabase - .from("documents") - .update({ - word_count: stats.wordCount, - character_count: stats.characterCount, - block_count: stats.blockCount, - }) - .eq("id", documentId) - .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 }); } diff --git a/wolai-frontend/src/app/api/documents/title/route.ts b/wolai-frontend/src/app/api/documents/title/route.ts index ce371a8d..b5602086 100644 --- a/wolai-frontend/src/app/api/documents/title/route.ts +++ b/wolai-frontend/src/app/api/documents/title/route.ts @@ -1,9 +1,7 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; -import { requireAuthContext } from "@/lib/auth/authContext"; interface RenamePayload { documentId: string; @@ -12,37 +10,14 @@ interface RenamePayload { export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await requireAuthContext(); const { documentId, title }: RenamePayload = await request.json(); - const client = getConvexHttpClient(); + const { client } = await getAuthedConvexClient(); await client.mutation(api.documents.updateTitle, { - userId: auth.userId, id: documentId, title, }); return NextResponse.json({ ok: true }); } - const supabase = await createSupabaseRouteClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - return NextResponse.json({ error: "未登录" }, { status: 401 }); - } - - const { documentId, title }: RenamePayload = await request.json(); - - const { error } = await supabase - .from("documents") - .update({ title }) - .eq("id", documentId) - .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 }); } diff --git a/wolai-frontend/src/app/api/luckysheet/get-workerbook/route.ts b/wolai-frontend/src/app/api/luckysheet/get-workerbook/route.ts index 3274137e..080c465b 100644 --- a/wolai-frontend/src/app/api/luckysheet/get-workerbook/route.ts +++ b/wolai-frontend/src/app/api/luckysheet/get-workerbook/route.ts @@ -1,9 +1,8 @@ "use server"; import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getConvexAuthedHttpClient } from "@/lib/convex/server"; import { api } from "@/lib/convex/api"; export async function GET(request: Request) { @@ -17,7 +16,7 @@ export async function GET(request: Request) { // Convex 模式 if (isConvexEnabled()) { try { - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); const result = await client.query(api.tables.getByGridKey, { gridKey }); if (!result) { @@ -36,6 +35,9 @@ export async function GET(request: Request) { } // Supabase 模式 + return NextResponse.json({ code: 501, msg: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data, error } = await supabase .from("document_tables") @@ -56,4 +58,5 @@ export async function GET(request: Request) { gridKey: data.grid_key, }, }); + */ } diff --git a/wolai-frontend/src/app/api/luckysheet/load/route.ts b/wolai-frontend/src/app/api/luckysheet/load/route.ts index 9d9e5e44..5b04db3c 100644 --- a/wolai-frontend/src/app/api/luckysheet/load/route.ts +++ b/wolai-frontend/src/app/api/luckysheet/load/route.ts @@ -2,10 +2,9 @@ import { NextResponse } from "next/server"; import { createDefaultTableSnapshot } from "@/lib/online-table"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import type { DocumentTableSnapshot, TableSchema } from "@/types/online-table"; import { isConvexEnabled } from "@/lib/convex/enabled"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getConvexAuthedHttpClient } from "@/lib/convex/server"; import { api } from "@/lib/convex/api"; const fallbackSchema: TableSchema = { @@ -25,7 +24,7 @@ export async function POST(request: Request) { // Convex 模式 if (isConvexEnabled()) { try { - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); const tableData = await client.query(api.tables.getByGridKeyFull, { gridKey }); if (!tableData) { @@ -54,6 +53,9 @@ export async function POST(request: Request) { } // Supabase 模式 + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data, error } = await supabase .from("document_tables") @@ -80,4 +82,5 @@ export async function POST(request: Request) { return new NextResponse(body, { headers: { "Content-Type": "text/plain; charset=utf-8" }, }); + */ } diff --git a/wolai-frontend/src/app/api/luckysheet/upload-image/route.ts b/wolai-frontend/src/app/api/luckysheet/upload-image/route.ts index 4aaaa009..fc5ef265 100644 --- a/wolai-frontend/src/app/api/luckysheet/upload-image/route.ts +++ b/wolai-frontend/src/app/api/luckysheet/upload-image/route.ts @@ -3,12 +3,14 @@ import { Buffer } from "node:buffer"; import { randomUUID } from "node:crypto"; import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; const BUCKET = "media"; const DIRECTORY = "luckysheet"; export async function POST(request: Request) { + return NextResponse.json({ code: 501, msg: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const formData = await request.formData(); const file = formData.get("image"); @@ -39,4 +41,5 @@ export async function POST(request: Request) { msg: "ok", url: publicUrl, }); + */ } diff --git a/wolai-frontend/src/app/api/media/assets/route.ts b/wolai-frontend/src/app/api/media/assets/route.ts index 31bec96f..6cdfeb8c 100644 --- a/wolai-frontend/src/app/api/media/assets/route.ts +++ b/wolai-frontend/src/app/api/media/assets/route.ts @@ -1,10 +1,9 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import type { MediaAsset } from "@/types/media"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { HttpError, requireAuthContext } from "@/lib/auth/authContext"; import { api } from "@/lib/convex/api"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getConvexAuthedHttpClient } from "@/lib/convex/server"; export const dynamic = "force-dynamic"; @@ -29,7 +28,7 @@ export async function GET(request: Request) { return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); } - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); const items = await client.query(api.mediaAssets.listByWorkspace, { userId: auth.userId, workspaceId, @@ -41,7 +40,9 @@ export async function GET(request: Request) { return NextResponse.json({ items: (items ?? []) as MediaAsset[] }); } - const supabase = await createSupabaseRouteClient(); + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + const supabase = null as any; const { data: { user }, error: authError, @@ -130,7 +131,7 @@ export async function POST(request: Request) { updated_at: now, }; - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); await client.mutation(api.mediaAssets.create, { userId: auth.userId, asset: { @@ -151,7 +152,9 @@ export async function POST(request: Request) { return NextResponse.json({ asset }); } - const supabase = await createSupabaseRouteClient(); + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + const supabase = null as any; const { data: { user }, error: authError, diff --git a/wolai-frontend/src/app/api/media/batch/route.ts b/wolai-frontend/src/app/api/media/batch/route.ts index d44e9f99..b516cb89 100644 --- a/wolai-frontend/src/app/api/media/batch/route.ts +++ b/wolai-frontend/src/app/api/media/batch/route.ts @@ -1,12 +1,11 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { makeUniqueFileName } from "@/lib/file-tree/naming"; import { rewriteToPublicOrigin } from "@/lib/url/rewritePublicOrigin"; import { getMnoteRuntimeConfig } from "@/lib/runtime-config"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { HttpError, requireAuthContext } from "@/lib/auth/authContext"; import { api } from "@/lib/convex/api"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getConvexAuthedHttpClient } from "@/lib/convex/server"; export const dynamic = "force-dynamic"; @@ -84,7 +83,7 @@ export async function POST(request: Request) { return NextResponse.json({ error: "缺少参数" }, { status: 400 }); } - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); const assets = (await client.query(api.mediaAssets.listByIds, { userId: auth.userId, ids: payload.assetIds, @@ -142,7 +141,6 @@ export async function POST(request: Request) { } const targetDoc = await client.query(api.documents.getMeta, { - userId: auth.userId, id: payload.targetDocumentId, }); @@ -213,6 +211,9 @@ export async function POST(request: Request) { } } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -410,4 +411,5 @@ export async function POST(request: Request) { const message = err instanceof Error ? err.message : "操作失败"; return NextResponse.json({ error: message }, { status: 500 }); } + */ } diff --git a/wolai-frontend/src/app/api/media/empty-trash/route.ts b/wolai-frontend/src/app/api/media/empty-trash/route.ts index fbd2b3e3..6cb22e31 100644 --- a/wolai-frontend/src/app/api/media/empty-trash/route.ts +++ b/wolai-frontend/src/app/api/media/empty-trash/route.ts @@ -1,9 +1,8 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { HttpError, requireAuthContext } from "@/lib/auth/authContext"; import { api } from "@/lib/convex/api"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getConvexAuthedHttpClient } from "@/lib/convex/server"; export const dynamic = "force-dynamic"; @@ -45,7 +44,7 @@ export async function POST(request: Request) { return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); } - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); const res = await client.mutation(api.mediaAssets.emptyTrashByWorkspace, { userId: auth.userId, workspaceId, @@ -55,6 +54,9 @@ export async function POST(request: Request) { return NextResponse.json({ success: true, ...(res as any) }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -114,4 +116,5 @@ export async function POST(request: Request) { } return NextResponse.json({ success: true, updated: assetIds.length }); + */ } diff --git a/wolai-frontend/src/app/api/media/ocr/route.ts b/wolai-frontend/src/app/api/media/ocr/route.ts index d8b44b43..febdc9d8 100644 --- a/wolai-frontend/src/app/api/media/ocr/route.ts +++ b/wolai-frontend/src/app/api/media/ocr/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { getMnoteRuntimeConfig } from "@/lib/runtime-config"; import { isConvexEnabled } from "@/lib/convex/enabled"; @@ -9,6 +8,9 @@ export async function POST(request: Request) { return NextResponse.json({ error: "Convex 模式暂不支持 OCR" }, { status: 501 }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -51,4 +53,5 @@ export async function POST(request: Request) { } return NextResponse.json({ ok: true }); + */ } diff --git a/wolai-frontend/src/app/api/media/purge/route.ts b/wolai-frontend/src/app/api/media/purge/route.ts index 6eaa40f1..2cd6aef5 100644 --- a/wolai-frontend/src/app/api/media/purge/route.ts +++ b/wolai-frontend/src/app/api/media/purge/route.ts @@ -1,9 +1,8 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { HttpError, requireAuthContext } from "@/lib/auth/authContext"; import { api } from "@/lib/convex/api"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getConvexAuthedHttpClient } from "@/lib/convex/server"; export const dynamic = "force-dynamic"; @@ -45,7 +44,7 @@ export async function POST(request: Request) { return NextResponse.json({ error: "缺少 assetId" }, { status: 400 }); } - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); const res = await client.mutation(api.mediaAssets.purgeById, { userId: auth.userId, id: assetId, @@ -55,6 +54,9 @@ export async function POST(request: Request) { return NextResponse.json({ success: true, ...(res as any) }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -115,4 +117,5 @@ export async function POST(request: Request) { } return NextResponse.json({ success: true }); + */ } diff --git a/wolai-frontend/src/app/api/media/sign/route.ts b/wolai-frontend/src/app/api/media/sign/route.ts index 67dc754b..0457ae76 100644 --- a/wolai-frontend/src/app/api/media/sign/route.ts +++ b/wolai-frontend/src/app/api/media/sign/route.ts @@ -1,74 +1,19 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; -import { rewriteToPublicOrigin } from "@/lib/url/rewritePublicOrigin"; -import { getMnoteRuntimeConfig } from "@/lib/runtime-config"; -import supabaseAdmin from "@/lib/supabase/admin"; import { isConvexEnabled } from "@/lib/convex/enabled"; -import { HttpError, requireAuthContext } from "@/lib/auth/authContext"; import { api } from "@/lib/convex/api"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { HttpError } from "@/lib/auth/authContext"; +import { getAuthedConvexClient } from "@/lib/convex/route"; export const dynamic = "force-dynamic"; -const isUuid = (value: string) => - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); - -const resolveAssetObjectLocation = async (params: { - bucket?: string | null; - storagePath?: string | null; - workspaceId?: string | null; - fileName?: string | null; -}) => { - const { bucket, storagePath, workspaceId, fileName } = params; - const ws = (workspaceId ?? "").trim(); - const fn = (fileName ?? "").trim(); - - if (!ws || !isUuid(ws) || !fn) return null; - - try { - const storage = supabaseAdmin.schema("storage"); - - // 1) 若已给出 bucket/path,先校验是否存在 - const b = (bucket ?? "").trim(); - const p = (storagePath ?? "").trim(); - if (b && p) { - const { data: exact, error: exactError } = await storage - .from("objects") - .select("bucket_id,name") - .eq("bucket_id", b) - .eq("name", p) - .limit(1); - if (!exactError && exact && exact.length > 0) { - return { bucket: b, path: p }; - } - } - - // 2) 兼容历史数据:按 workspace_id/%/file_name 查找 - const pattern = `${ws}/%/${fn}`; - const { data: candidates, error: candError } = await storage - .from("objects") - .select("bucket_id,name,created_at") - .like("name", pattern) - .order("created_at", { ascending: false }) - .limit(20); - - if (candError || !candidates || candidates.length === 0) return null; - - const exactCandidate = - candidates.find((o) => String(o?.name || "").endsWith(`/${fn}`)) ?? - candidates[0]; - if (!exactCandidate?.bucket_id || !exactCandidate?.name) return null; - return { bucket: String(exactCandidate.bucket_id), path: String(exactCandidate.name) }; - } catch { - return null; - } -}; - export async function GET(request: Request) { if (isConvexEnabled()) { let auth; + let client; try { - auth = await requireAuthContext(); + const authed = await getAuthedConvexClient(); + auth = authed.auth; + client = authed.client; } catch (err) { if (err instanceof HttpError) { return NextResponse.json({ error: "未登录" }, { status: err.status }); @@ -83,7 +28,6 @@ export async function GET(request: Request) { return NextResponse.json({ error: "缺少 assetId" }, { status: 400 }); } - const client = getConvexHttpClient(); const asset = await client.query(api.mediaAssets.getById, { userId: auth.userId, id: assetId }); if (!asset) { return NextResponse.json({ error: "资源不存在" }, { status: 404 }); @@ -106,101 +50,5 @@ export async function GET(request: Request) { }); } - const supabase = await createSupabaseRouteClient(); - const { - data: { user }, - error: authError, - } = await supabase.auth.getUser(); - - if (authError || !user) { - return NextResponse.json({ error: "未登录" }, { status: 401 }); - } - - const { searchParams } = new URL(request.url); - const assetId = searchParams.get("assetId"); - - if (!assetId) { - return NextResponse.json({ error: "缺少 assetId" }, { status: 400 }); - } - - // 获取资源信息 - const { data: asset, error: assetError } = await supabase - .from("media_assets") - .select("*") - .eq("id", assetId) - .single(); - - if (assetError || !asset) { - return NextResponse.json({ error: "资源不存在" }, { status: 404 }); - } - - // 兼容:早期/异常写入的 media_assets 可能缺少 bucket/storage_path, - // 导致生成的签名链接指向不存在的对象(ONLYOFFICE 会报“下载失败”)。 - let bucket = (asset.bucket as string | null) ?? null; - let storagePath = (asset.storage_path as string | null) ?? null; - if (!bucket || !storagePath) { - const resolved = await resolveAssetObjectLocation({ - bucket, - storagePath, - workspaceId: asset.workspace_id ?? null, - fileName: asset.file_name ?? null, - }); - if (!resolved) { - return NextResponse.json({ error: "未找到存储对象,无法生成签名链接" }, { status: 404 }); - } - bucket = resolved.bucket; - storagePath = resolved.path; - - // 尽量回填,避免后续重复修复 - await supabase - .from("media_assets") - .update({ bucket, storage_path: storagePath }) - .eq("id", asset.id); - } - - // 对于图片,添加高质量参数以获得更好的显示效果 - // Supabase Storage 支持通过 URL 参数控制图片质量和尺寸 - const isImage = asset.mime_type?.startsWith("image/"); - let signedUrl: string; - - if (isImage) { - // 使用 createSignedUrl 并添加高质量参数 - // 注意:transform 参数需要在签名时指定 - const { data: signedUrlData, error: signError } = await supabase.storage - .from(bucket) - .createSignedUrl(storagePath, 60 * 60, { - // 添加转换参数以获得高质量图片 - transform: { - quality: 95, // 高质量 - format: "origin", // 保持原始格式 - }, - }); - - if (signError || !signedUrlData) { - return NextResponse.json({ error: "生成签名 URL 失败" }, { status: 500 }); - } - signedUrl = signedUrlData.signedUrl; - } else { - // 非图片文件,直接生成签名 URL - const { data: signedUrlData, error: signError } = await supabase.storage - .from(bucket) - .createSignedUrl(storagePath, 60 * 60); - - if (signError || !signedUrlData) { - return NextResponse.json({ error: "生成签名 URL 失败" }, { status: 500 }); - } - signedUrl = signedUrlData.signedUrl; - } - - signedUrl = rewriteToPublicOrigin(signedUrl, getMnoteRuntimeConfig().supabaseUrl); - - return NextResponse.json({ - signedUrl, - asset: { - id: asset.id, - file_name: asset.file_name, - mime_type: asset.mime_type, - file_size: asset.file_size, - }, - }); + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); } diff --git a/wolai-frontend/src/app/api/media/signed-url/route.ts b/wolai-frontend/src/app/api/media/signed-url/route.ts index 140d2d7e..bcd588fa 100644 --- a/wolai-frontend/src/app/api/media/signed-url/route.ts +++ b/wolai-frontend/src/app/api/media/signed-url/route.ts @@ -1,102 +1,11 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; -import { rewriteToPublicOrigin } from "@/lib/url/rewritePublicOrigin"; -import { getMnoteRuntimeConfig } from "@/lib/runtime-config"; -import supabaseAdmin from "@/lib/supabase/admin"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { HttpError, requireAuthContext } from "@/lib/auth/authContext"; export const dynamic = "force-dynamic"; -const base64Url = (input: string) => - Buffer.from(input) - .toString("base64") - .replace(/=/g, "") - .replace(/\+/g, "-") - .replace(/\//g, "_"); - -const parseStoragePath = (fileUrl: string) => { - try { - const url = new URL(fileUrl); - const segments = url.pathname.split("/").filter(Boolean); - const objectIdx = segments.findIndex((seg) => seg === "object"); - if (objectIdx === -1 || objectIdx + 2 >= segments.length) return null; - // pattern 1: /storage/v1/object/public// - if (segments[objectIdx + 1] === "public") { - const bucket = segments[objectIdx + 2]; - const path = segments.slice(objectIdx + 3).join("/"); - return { bucket, path }; - } - // pattern 2: /storage/v1/object/sign// (token in query) - if (segments[objectIdx + 1] === "sign") { - const bucket = segments[objectIdx + 2]; - const path = segments.slice(objectIdx + 3).join("/"); - return { bucket, path }; - } - return null; - } catch { - return null; - } -}; - -const isUuid = (value: string) => - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); - -const tryResolveExistingObjectPath = async (params: { - bucket: string; - path: string; - fileName?: string; -}) => { - const { bucket, path, fileName } = params; - if (!bucket || !path) return null; - try { - const storage = supabaseAdmin.schema("storage"); - - // 1) 先尝试精确匹配(最快且最准确) - const { data: exact, error: exactError } = await storage - .from("objects") - .select("name") - .eq("bucket_id", bucket) - .eq("name", path) - .limit(1); - - if (!exactError && exact && exact.length > 0 && exact[0]?.name) { - return String(exact[0].name); - } - - // 2) 兼容历史数据:老链接中间 UUID 可能用的是 document_id, - // 但实际对象通常是 workspace_id/<其他uuid>/。 - const segments = path.split("/").filter(Boolean); - const workspaceId = segments[0] ?? ""; - const wantedName = (fileName ?? segments[segments.length - 1] ?? "").trim(); - if (!workspaceId || !isUuid(workspaceId) || !wantedName) return null; - - const pattern = `${workspaceId}/%/${wantedName}`; - const { data: candidates, error: candError } = await storage - .from("objects") - .select("name,created_at") - .eq("bucket_id", bucket) - .like("name", pattern) - .order("created_at", { ascending: false }) - .limit(20); - - if (candError || !candidates || candidates.length === 0) return null; - - // 说明:LIKE 会把 `_` 当作通配符,这里用 endsWith 再做一次精确过滤。 - const exactCandidate = - candidates.find((o) => String(o?.name || "").endsWith(`/${wantedName}`)) ?? - candidates[0]; - if (!exactCandidate?.name) return null; - return String(exactCandidate.name); - } catch { - return null; - } -}; - export async function GET(request: Request) { if (isConvexEnabled()) { - // 说明:Convex + MinIO 模式下,这个接口目前主要用于“外链文件”走 ONLYOFFICE 的场景。 - // 由于外链本身已经是可访问的 URL,这里只做最小透传。 try { await requireAuthContext(); } catch (err) { @@ -111,105 +20,12 @@ export async function GET(request: Request) { if (!fileUrl) { return NextResponse.json({ error: "缺少 fileUrl" }, { status: 400 }); } + + // 说明:Convex + MinIO 模式下,这个接口目前主要用于“外链文件”走 ONLYOFFICE 的场景。 + // 由于外链本身已经是可访问的 URL,这里只做最小透传。 return NextResponse.json({ signedUrl: fileUrl }); } - // 说明:在 Cloudflare Tunnel 场景下,后端收到的 Host 可能是 localhost, - // 但 ONLYOFFICE 文档服务器拉取 document.url 时必须使用公网可达的域名。 - // 因此这里优先使用运行时配置(public/mnote-env.json / env)里的公网 Origin, - // 再回退到 request.url 解析出的 origin。 - const runtimeCfg = getMnoteRuntimeConfig(); - - const supabase = await createSupabaseRouteClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - return NextResponse.json({ error: "未登录" }, { status: 401 }); - } - - const { searchParams } = new URL(request.url); - const fileUrl = searchParams.get("fileUrl"); - const fileName = searchParams.get("fileName") ?? undefined; - const forOnlyOffice = searchParams.get("for") === "onlyoffice"; - if (searchParams.get("debug") === "1") { - return NextResponse.json({ - keyLen: (process.env.SUPABASE_SERVICE_ROLE_KEY || "").length, - url: process.env.SUPABASE_URL, - }); - } - - if (!fileUrl) { - return NextResponse.json({ error: "缺少 fileUrl" }, { status: 400 }); - } - - // 说明: - // - 非 ONLYOFFICE:必须能解析为 Supabase Storage 路径,便于生成带 download 的临时签名 URL。 - // - ONLYOFFICE:允许传入任意可访问 URL(包括外链或已签名 URL),并尽量“刷新一次签名”,刷新失败则回退到原 URL。 - const parsed = parseStoragePath(fileUrl); - - if (!forOnlyOffice) { - if (!parsed) { - return NextResponse.json({ error: "无法解析 Supabase 存储路径" }, { status: 400 }); - } - - const { bucket, path } = parsed; - const resolvedPath = - (await tryResolveExistingObjectPath({ bucket, path, fileName })) ?? null; - - if (!resolvedPath) { - return NextResponse.json( - { error: "存储对象不存在,无法生成签名链接" }, - { status: 404 }, - ); - } - - const { data, error } = await supabase.storage.from(bucket).createSignedUrl( - resolvedPath, - 60 * 60, - { download: fileName }, - ); - - if (error || !data?.signedUrl) { - return NextResponse.json({ error: error?.message ?? "生成签名 URL 失败" }, { status: 500 }); - } - - // 返回给浏览器的 URL 必须是公网可达的(不能是 127.0.0.1:18000) - const signedUrl = rewriteToPublicOrigin(data.signedUrl, runtimeCfg.supabaseUrl); - return NextResponse.json({ signedUrl }); - } - - // ONLYOFFICE 需要“可被文档服务器拉取”的 URL:不要强制 download(Content-Disposition: attachment)。 - // 这里优先尝试重新生成 1 小时签名 URL;如果失败(例如策略限制/路径无法解析),则回退到原 URL。 - let upstreamUrl = rewriteToPublicOrigin(fileUrl, runtimeCfg.supabaseUrl); - if (parsed) { - const { bucket, path } = parsed; - const resolvedPath = - (await tryResolveExistingObjectPath({ bucket, path, fileName })) ?? null; - if (!resolvedPath) { - return NextResponse.json( - { error: "存储对象不存在,无法生成签名链接" }, - { status: 404 }, - ); - } - const { data, error } = await supabase.storage.from(bucket).createSignedUrl( - resolvedPath, - 60 * 60, - ); - if (!error && data?.signedUrl) { - upstreamUrl = rewriteToPublicOrigin(data.signedUrl, runtimeCfg.supabaseUrl); - } - } - - // 关键:Supabase Storage 的 signedUrl 自带 `?token=...`,ONLYOFFICE 会把 URL 上的 - // `token` 参数当作自己的 JWT token 去校验,导致报 “文档安全令牌的格式不正确/invalid signature”。 - // - // 解决:对 ONLYOFFICE 返回一个“代理 URL”,把真正的 URL 编码到 `u=...` 中,避免 - // `token` 参数出现在 document.url 上。 - const defaultOrigin = new URL(request.url).origin; - const proxyBase = (runtimeCfg.onlyofficeProxyOrigin || runtimeCfg.cloudflareAppOrigin || defaultOrigin).replace(/\/+$/, ""); - const proxyUrl = new URL("/api/onlyoffice/proxy", proxyBase); - proxyUrl.searchParams.set("u", base64Url(upstreamUrl)); - return NextResponse.json({ signedUrl: proxyUrl.toString() }); + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); } + diff --git a/wolai-frontend/src/app/api/media/upload/route.ts b/wolai-frontend/src/app/api/media/upload/route.ts index 9e6055db..674ec13a 100644 --- a/wolai-frontend/src/app/api/media/upload/route.ts +++ b/wolai-frontend/src/app/api/media/upload/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import type { MediaAsset } from "@/types/media"; import { extname } from "path"; import { rewriteToPublicOrigin } from "@/lib/url/rewritePublicOrigin"; @@ -7,7 +6,7 @@ import { getMnoteRuntimeConfig } from "@/lib/runtime-config"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { HttpError, requireAuthContext } from "@/lib/auth/authContext"; import { api } from "@/lib/convex/api"; -import { getConvexHttpClient } from "@/lib/convex/server"; +import { getConvexAuthedHttpClient } from "@/lib/convex/server"; export const dynamic = "force-dynamic"; @@ -50,7 +49,7 @@ export async function POST(request: Request) { ? crypto.randomUUID() : `${Date.now()}_${Math.random().toString(16).slice(2)}`; const assetType = resolveAssetType(file.type || ""); - const client = getConvexHttpClient(); + const client = await getConvexAuthedHttpClient(); // 1) 获取 Convex 的上传 URL(短时有效) const uploadUrl = await client.mutation(api.mediaAssets.generateUploadUrl, { userId: auth.userId }); @@ -101,6 +100,9 @@ export async function POST(request: Request) { } } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -190,4 +192,5 @@ export async function POST(request: Request) { console.error(error); return NextResponse.json({ error: "上传失败" }, { status: 500 }); } + */ } diff --git a/wolai-frontend/src/app/api/mindmap-ai/agent/route.ts b/wolai-frontend/src/app/api/mindmap-ai/agent/route.ts index 19391fb5..3143e927 100644 --- a/wolai-frontend/src/app/api/mindmap-ai/agent/route.ts +++ b/wolai-frontend/src/app/api/mindmap-ai/agent/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { loadOnlineAiConfig } from "@/lib/ai/onlineAiConfig"; import { loadLocalAiConfig } from "@/lib/ai/localAiConfig"; import { openAiCompatibleChat, tryExtractJsonObject } from "@/lib/ai/openaiCompatibleChat"; @@ -313,6 +312,9 @@ export async function POST(request: Request) { return NextResponse.json({ error: "缺少 documentId/mindmapId/messages" }, { status: 400 }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -833,4 +835,5 @@ export async function POST(request: Request) { }, { status: 200 }, ); + */ } diff --git a/wolai-frontend/src/app/api/mindmap-ai/assets/route.ts b/wolai-frontend/src/app/api/mindmap-ai/assets/route.ts index 1ee33608..e985291e 100644 --- a/wolai-frontend/src/app/api/mindmap-ai/assets/route.ts +++ b/wolai-frontend/src/app/api/mindmap-ai/assets/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { detectLocalMindmapFiles } from "@/lib/server/mindmap-files"; import type { MediaAsset } from "@/types/media"; import path from "path"; @@ -40,6 +39,9 @@ async function listTestPdfs(): Promise { } export async function GET(request: Request) { + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -115,4 +117,5 @@ export async function GET(request: Request) { documentId, items, }); + */ } diff --git a/wolai-frontend/src/app/api/mindmap-ai/expand-node/route.ts b/wolai-frontend/src/app/api/mindmap-ai/expand-node/route.ts index b6b76b0c..7f073326 100644 --- a/wolai-frontend/src/app/api/mindmap-ai/expand-node/route.ts +++ b/wolai-frontend/src/app/api/mindmap-ai/expand-node/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { loadOnlineAiConfig } from "@/lib/ai/onlineAiConfig"; import { openAiCompatibleChat, tryExtractJsonObject } from "@/lib/ai/openaiCompatibleChat"; import { applyMindmapOps, type MindmapOp, type MindmapTreeNode, type NodeRef } from "@/lib/mindmap/mindmapOps"; @@ -93,6 +92,9 @@ export async function POST(request: Request) { return NextResponse.json({ error: "缺少 documentId/mindmapId/targetUid" }, { status: 400 }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -308,4 +310,5 @@ export async function POST(request: Request) { searxCount: searxResults.length, }, }); + */ } diff --git a/wolai-frontend/src/app/api/mindmap-trash/empty/route.ts b/wolai-frontend/src/app/api/mindmap-trash/empty/route.ts index 4faf04de..24fa2277 100644 --- a/wolai-frontend/src/app/api/mindmap-trash/empty/route.ts +++ b/wolai-frontend/src/app/api/mindmap-trash/empty/route.ts @@ -1,101 +1,30 @@ 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"; export const dynamic = "force-dynamic"; -const preferredBaseDir = getDocumentsBaseDir(); -const legacyBaseDir = getLegacyMindmapsBaseDir(); - interface EmptyTrashPayload { workspaceId?: string; } -async function purgeTrashFolder(folder: string): Promise { - const trashDir = path.join(folder, ".trash"); - try { - const entries = await fs.readdir(trashDir); - let removed = 0; - for (const name of entries) { - await fs.rm(path.join(trashDir, name), { force: true, recursive: true }); - removed += 1; - } - return removed; - } catch { - return 0; - } -} - export async function POST(request: Request) { if (isConvexEnabled()) { - const { auth, client } = await getAuthedConvexClient(); + const { client } = await getAuthedConvexClient(); const { workspaceId }: EmptyTrashPayload = await request.json().catch(() => ({})); if (!workspaceId) { return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); } try { - const result = await client.mutation(api.mindmaps.emptyTrashByWorkspace, { - userId: auth.userId, - workspaceId, - }); + const result = await client.mutation(api.mindmaps.emptyTrashByWorkspace, { workspaceId }); return NextResponse.json({ ok: true, removed: result?.deletedCount ?? 0 }); } 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 { workspaceId }: EmptyTrashPayload = await request.json().catch(() => ({})); - if (!workspaceId) { - return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); - } - - const { data: membership, error: membershipError } = await supabase - .from("workspace_members") - .select("workspace_id") - .eq("workspace_id", workspaceId) - .eq("user_id", session.user.id) - .limit(1); - - if (membershipError) { - return NextResponse.json({ error: membershipError.message }, { status: 500 }); - } - - if (!membership || membership.length === 0) { - return NextResponse.json({ error: "无权操作该工作空间" }, { status: 403 }); - } - - const { data: documents, error: docError } = await supabase - .from("documents") - .select("id") - .eq("workspace_id", workspaceId) - .eq("user_id", session.user.id) - .limit(5000); - - if (docError) { - return NextResponse.json({ error: docError.message }, { status: 500 }); - } - - const docIds = (documents ?? []).map((row) => row.id).filter(Boolean); - let removed = 0; - for (const docId of docIds) { - removed += await purgeTrashFolder(path.join(preferredBaseDir, docId)); - removed += await purgeTrashFolder(path.join(legacyBaseDir, docId)); - } - - return NextResponse.json({ ok: true, removed }); + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); } + diff --git a/wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/ops/route.ts b/wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/ops/route.ts index 717242c3..ced4b50b 100644 --- a/wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/ops/route.ts +++ b/wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/ops/route.ts @@ -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 }); } + diff --git a/wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/route.ts b/wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/route.ts index 3d05143a..8dc322ac 100644 --- a/wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/route.ts +++ b/wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/route.ts @@ -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) { - 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> { - 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 }); } + diff --git a/wolai-frontend/src/app/api/mindmap/[docId]/route.ts b/wolai-frontend/src/app/api/mindmap/[docId]/route.ts index 907ced43..68a27569 100644 --- a/wolai-frontend/src/app/api/mindmap/[docId]/route.ts +++ b/wolai-frontend/src/app/api/mindmap/[docId]/route.ts @@ -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//mindmap.json -// 旧版遗留:public/mindmaps//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 }); } + diff --git a/wolai-frontend/src/app/api/onlyoffice/callback/route.ts b/wolai-frontend/src/app/api/onlyoffice/callback/route.ts index 995d2245..10778df3 100644 --- a/wolai-frontend/src/app/api/onlyoffice/callback/route.ts +++ b/wolai-frontend/src/app/api/onlyoffice/callback/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import supabaseAdmin from "@/lib/supabase/admin"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { getConvexHttpClient } from "@/lib/convex/server"; import { api } from "@/lib/convex/api"; @@ -133,34 +132,5 @@ export async function POST(request: Request) { return NextResponse.json({ error: 1 }); } } - - const { data: asset, error: assetError } = await supabaseAdmin - .from("media_assets") - .select("id,bucket,storage_path,mime_type") - .eq("id", assetId) - .single(); - - if (assetError || !asset) { - return NextResponse.json({ error: 1 }); - } - - const downloadUrl = tryRewriteOnlyOfficeDownloadUrl(body.url); - const upstream = await fetch(downloadUrl, { method: "GET", redirect: "follow" }); - if (!upstream.ok) { - return NextResponse.json({ error: 1 }); - } - - const buf = Buffer.from(await upstream.arrayBuffer()); - const { error: uploadError } = await supabaseAdmin.storage - .from(asset.bucket) - .upload(asset.storage_path, buf, { - upsert: true, - contentType: asset.mime_type || "application/octet-stream", - }); - - if (uploadError) { - return NextResponse.json({ error: 1 }); - } - - return NextResponse.json({ error: 0 }); + return NextResponse.json({ error: 1 }); } diff --git a/wolai-frontend/src/app/api/references/backlinks/route.ts b/wolai-frontend/src/app/api/references/backlinks/route.ts index 7ab4ac20..bb18a849 100644 --- a/wolai-frontend/src/app/api/references/backlinks/route.ts +++ b/wolai-frontend/src/app/api/references/backlinks/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; @@ -28,6 +27,9 @@ export async function GET(request: Request) { return NextResponse.json({ backlinks }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -73,4 +75,5 @@ export async function GET(request: Request) { } return NextResponse.json({ backlinks: data ?? [] }); + */ } diff --git a/wolai-frontend/src/app/api/references/record/route.ts b/wolai-frontend/src/app/api/references/record/route.ts index 6a79f46f..f58900db 100644 --- a/wolai-frontend/src/app/api/references/record/route.ts +++ b/wolai-frontend/src/app/api/references/record/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; @@ -43,6 +42,9 @@ export async function POST(request: Request) { return NextResponse.json({ reference }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -92,4 +94,5 @@ export async function POST(request: Request) { } return NextResponse.json({ reference: data }); + */ } diff --git a/wolai-frontend/src/app/api/search/documents/route.ts b/wolai-frontend/src/app/api/search/documents/route.ts index 90d7072f..69b056fd 100644 --- a/wolai-frontend/src/app/api/search/documents/route.ts +++ b/wolai-frontend/src/app/api/search/documents/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; @@ -34,211 +33,12 @@ const TIME_FIELD_COLUMN = { created: "created_at", } as const; -const escapeLike = (value: string): string => value.replace(/[%_\\]/g, (match) => `\\${match}`); -interface DocumentRow { - id: string; - title: string | null; - raw_text: string | null; - updated_at: string | null; - created_at: string | null; -} - -const buildIsoBoundary = (value: string, isEnd = false): string | null => { - if (!value) { - return null; - } - const normalized = value.trim(); - if (!normalized) { - return null; - } - const suffix = isEnd ? "T23:59:59.999Z" : "T00:00:00.000Z"; - const date = new Date(`${normalized}${suffix}`); - if (Number.isNaN(date.getTime())) { - return null; - } - return date.toISOString(); -}; - -const mapDocumentToResult = ( - row: DocumentRow, - keyword: string | null, - forcedMatch?: DocumentSearchResult["matchField"], -): DocumentSearchResult => { - const normalizedKeyword = keyword?.trim() ?? ""; - const normalizedTitle = row.title ?? "无标题"; - let matchField: DocumentSearchResult["matchField"] = "recent"; - if (forcedMatch) { - matchField = forcedMatch; - } else if (normalizedKeyword) { - const matchesTitle = normalizedTitle.toLowerCase().includes(normalizedKeyword.toLowerCase()); - matchField = matchesTitle ? "title" : "content"; - } - - return { - id: row.id, - title: normalizedTitle, - snippet: buildSnippet(row.raw_text, normalizedKeyword || null), - updatedAt: row.updated_at, - createdAt: row.created_at, - matchField, - hasOcr: Boolean(row.raw_text), - publicPath: `/documents/${row.id}`, - score: matchField === "title" ? 2 : 1, - }; -}; - -type RouteSupabaseClient = Awaited>; - -const fetchRecentResults = async ( - supabase: RouteSupabaseClient, - userId: string, - workspaceId: string, -): Promise => { - const { data: recentRows, error: recentError } = await supabase - .from("user_recent_pages") - .select("document_id,last_accessed_at") - .eq("user_id", userId) - .eq("workspace_id", workspaceId) - .order("last_accessed_at", { ascending: false }) - .limit(10); - - if (recentError || !recentRows || recentRows.length === 0) { - return []; - } - - const documentIds = recentRows.map((row) => row.document_id); - - const { data: docRows, error: docsError } = await supabase - .from("documents") - .select("id,title,updated_at,created_at,raw_text") - .in("id", documentIds) - .is("deleted_at", null); - - if (docsError || !docRows) { - return []; - } - - const docMap = new Map(docRows.map((row) => [row.id, row])); - return recentRows - .map((row) => docMap.get(row.document_id)) - .filter((row): row is DocumentRow => Boolean(row)) - .map((row) => mapDocumentToResult(row, null, "recent")); -}; - -const fetchOcrMatches = async ( - supabase: RouteSupabaseClient, - workspaceId: string, - likePattern: string, - limit: number, -) => { - const { data, error } = await supabase - .from("media_assets") - .select("document_id,ocr_text") - .eq("workspace_id", workspaceId) - .is("deleted_at", null) - .not("ocr_text", "is", null) - .ilike("ocr_text", likePattern) - .limit(limit); - - if (error || !data) { - return []; - } - return data; -}; - export async function POST(request: Request) { - if (isConvexEnabled()) { - const { auth, client } = await getAuthedConvexClient(); - const payload = (await request.json()) as DocumentSearchRequest; - const workspaceId = payload.workspaceId; - - if (!workspaceId) { - return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); - } - - const filters: DocumentSearchFilters = { - ...DEFAULT_FILTERS, - ...payload.filters, - }; - - const limit = Math.min(payload.limit ?? 30, MAX_LIMIT); - const normalizedQuery = payload.query?.trim() ?? ""; - const normalizedLower = normalizedQuery.toLowerCase(); - - const docs = await client.query(api.documents.listByWorkspace, { - userId: auth.userId, - workspaceId, - }); - - const sortedByRecent = [...docs].sort((a, b) => { - const ta = a.updated_at ?? a.created_at ?? ""; - const tb = b.updated_at ?? b.created_at ?? ""; - return tb.localeCompare(ta); - }); - - const recentRows = await client.query(api.recents.listByWorkspace, { - userId: auth.userId, - workspaceId, - limit: 10, - }); - const docMap = new Map(docs.map((d) => [d.id, d])); - const recent: DocumentSearchResult[] = recentRows - .map((r) => docMap.get(r.document_id)) - .filter((row): row is (typeof docs)[number] => Boolean(row)) - .map((row) => ({ - id: row.id, - title: row.title ?? "无标题", - snippet: "", - updatedAt: row.updated_at ?? null, - createdAt: row.created_at ?? null, - matchField: "recent", - hasOcr: false, - publicPath: `/documents/${row.id}`, - score: 0, - })); - - if (!normalizedQuery) { - const response: DocumentSearchResponse = { results: [], recent }; - return NextResponse.json(response); - } - - const narrowed = sortedByRecent.filter((row) => { - if (filters.onlyCurrentPage && payload.documentId) { - if (row.id !== payload.documentId) return false; - } - const title = (row.title ?? "无标题").toLowerCase(); - return title.includes(normalizedLower); - }); - - const results: DocumentSearchResult[] = narrowed.slice(0, limit).map((row) => ({ - id: row.id, - title: row.title ?? "无标题", - snippet: buildSnippet(row.title ?? "", normalizedQuery), - updatedAt: row.updated_at ?? null, - createdAt: row.created_at ?? null, - matchField: "title", - hasOcr: false, - publicPath: `/documents/${row.id}`, - score: 2, - })); - - const response: DocumentSearchResponse = { - results, - recent, - }; - - return NextResponse.json(response); - } - - const supabase = await createSupabaseRouteClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - return NextResponse.json({ error: "未登录" }, { status: 401 }); + if (!isConvexEnabled()) { + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); } + const { auth, client } = await getAuthedConvexClient(); const payload = (await request.json()) as DocumentSearchRequest; const workspaceId = payload.workspaceId; @@ -251,140 +51,82 @@ export async function POST(request: Request) { ...payload.filters, }; - const { data: membership, error: membershipError } = await supabase - .from("workspace_members") - .select("workspace_id") - .eq("workspace_id", workspaceId) - .eq("user_id", session.user.id) - .limit(1); - - if (membershipError) { - return NextResponse.json({ error: membershipError.message }, { status: 500 }); - } - - if (!membership || membership.length === 0) { - return NextResponse.json({ error: "无权访问该工作空间" }, { status: 403 }); - } - - if (filters.onlyCurrentPage && !payload.documentId) { - filters.onlyCurrentPage = false; - } - const limit = Math.min(payload.limit ?? 30, MAX_LIMIT); const normalizedQuery = payload.query?.trim() ?? ""; - const likePattern = filters.exact ? normalizedQuery : `%${escapeLike(normalizedQuery)}%`; - const timeColumn = TIME_FIELD_COLUMN[filters.timeField ?? "updated"]; + const normalizedLower = normalizedQuery.toLowerCase(); - let builder = supabase - .from("documents") - .select("id,title,updated_at,created_at,raw_text,is_starred,access_scope") - .eq("workspace_id", workspaceId) - .is("deleted_at", null) - .order(timeColumn, { ascending: false }) - .limit(limit); + const docs = await client.query(api.documents.listByWorkspace, { workspaceId }); + const docMap = new Map(docs.map((d) => [d.id, d])); - if (filters.onlyCurrentPage && payload.documentId) { - builder = builder.eq("id", payload.documentId); + const recentRows = await client.query(api.recents.listByWorkspace, { + userId: auth.userId, + workspaceId, + limit: 10, + }); + const recent: DocumentSearchResult[] = recentRows + .map((r) => docMap.get(r.document_id)) + .filter((row): row is (typeof docs)[number] => Boolean(row)) + .map((row) => ({ + id: row.id, + title: row.title ?? "无标题", + snippet: "", + updatedAt: row.updated_at ?? null, + createdAt: row.created_at ?? null, + matchField: "recent", + hasOcr: false, + publicPath: `/documents/${row.id}`, + score: 0, + })); + + if (!normalizedQuery) { + const response: DocumentSearchResponse = { results: [], recent }; + return NextResponse.json(response); } - const customFrom = filters.customRange?.from ? buildIsoBoundary(filters.customRange.from, false) : null; - const customTo = filters.customRange?.to ? buildIsoBoundary(filters.customRange.to, true) : null; + const timeRangeMs = TIME_RANGE_TO_MS[filters.timeRange]; + const timeField = TIME_FIELD_COLUMN[filters.timeField]; + const boundaryIso = + typeof timeRangeMs === "number" + ? new Date(Date.now() - timeRangeMs).toISOString() + : null; - if (customFrom) { - builder = builder.gte(timeColumn, customFrom); - } - if (customTo) { - builder = builder.lte(timeColumn, customTo); - } - - if (!customFrom && !customTo) { - const now = Date.now(); - const offset = TIME_RANGE_TO_MS[filters.timeRange]; - if (offset) { - const from = new Date(now - offset).toISOString(); - builder = builder.gte(timeColumn, from); - } - } - - if (normalizedQuery) { - if (filters.titleOnly) { - builder = filters.exact ? builder.eq("title", normalizedQuery) : builder.ilike("title", likePattern); - } else { - const clauses = [ - `title.${filters.exact ? `eq.${normalizedQuery}` : `ilike.${likePattern}`}`, - ]; - if (filters.includeOcr) { - clauses.push(`raw_text.ilike.${likePattern}`); + const narrowed = [...docs] + .sort((a, b) => { + const ta = a.updated_at ?? a.created_at ?? ""; + const tb = b.updated_at ?? b.created_at ?? ""; + return tb.localeCompare(ta); + }) + .filter((row) => { + if (filters.onlyCurrentPage && payload.documentId) { + if (row.id !== payload.documentId) return false; } - builder = builder.or(clauses.join(",")); - } - } - const shouldSearchOcr = Boolean(filters.includeOcr && normalizedQuery); - const [{ data, error }, recentResults, ocrMatches] = await Promise.all([ - builder, - fetchRecentResults(supabase, session.user.id, workspaceId), - shouldSearchOcr ? fetchOcrMatches(supabase, workspaceId, likePattern, limit) : Promise.resolve([]), - ]); - - if (error) { - return NextResponse.json({ error: error.message }, { status: 500 }); - } - - let results: DocumentSearchResult[] = (data ?? []).map((row) => - mapDocumentToResult(row, normalizedQuery || null), - ); - - if (ocrMatches.length > 0 && normalizedQuery) { - const snippetMap = new Map(); - ocrMatches.forEach((row: { document_id: string; ocr_text: string | null }) => { - if (!row.ocr_text) return; - if (!snippetMap.has(row.document_id)) { - snippetMap.set(row.document_id, row.ocr_text); + if (boundaryIso) { + const ts = (row as any)?.[timeField] ?? null; + if (!ts || typeof ts !== "string") return false; + if (ts < boundaryIso) return false; } + + const title = (row.title ?? "无标题").toLowerCase(); + if (filters.exact) { + return title === normalizedLower; + } + return title.includes(normalizedLower); }); - if (snippetMap.size > 0) { - const resultMap = new Map(results.map((item) => [item.id, item])); - const missingDocIds: string[] = []; - snippetMap.forEach((text, docId) => { - const snippet = buildSnippet(text, normalizedQuery || null); - if (resultMap.has(docId)) { - const existing = resultMap.get(docId)!; - resultMap.set(docId, { - ...existing, - snippet: snippet || existing.snippet, - hasOcr: true, - matchField: "content", - }); - } else { - missingDocIds.push(docId); - } - }); - let extraResults: DocumentSearchResult[] = []; - if (missingDocIds.length > 0) { - const { data: extraDocs } = await supabase - .from("documents") - .select("id,title,updated_at,created_at,raw_text") - .in("id", missingDocIds) - .is("deleted_at", null); - extraResults = - extraDocs?.map((row) => { - const snippetText = snippetMap.get(row.id) ?? row.raw_text ?? ""; - return { - ...mapDocumentToResult(row, normalizedQuery || null, "content"), - snippet: buildSnippet(snippetText, normalizedQuery || null), - hasOcr: true, - }; - }) ?? []; - } - results = [...resultMap.values(), ...extraResults]; - } - } - const response: DocumentSearchResponse = { - results, - recent: recentResults, - }; + const results: DocumentSearchResult[] = narrowed.slice(0, limit).map((row) => ({ + id: row.id, + title: row.title ?? "无标题", + snippet: buildSnippet(row.title ?? "", normalizedQuery), + updatedAt: row.updated_at ?? null, + createdAt: row.created_at ?? null, + matchField: "title", + hasOcr: false, + publicPath: `/documents/${row.id}`, + score: 2, + })); + const response: DocumentSearchResponse = { results, recent }; return NextResponse.json(response); } + diff --git a/wolai-frontend/src/app/api/search/recent/route.ts b/wolai-frontend/src/app/api/search/recent/route.ts index e87734fd..a345db1e 100644 --- a/wolai-frontend/src/app/api/search/recent/route.ts +++ b/wolai-frontend/src/app/api/search/recent/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; @@ -28,6 +27,9 @@ export async function POST(request: Request) { return NextResponse.json({ ok: true }); } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -72,4 +74,5 @@ export async function POST(request: Request) { } return NextResponse.json({ ok: true }); + */ } diff --git a/wolai-frontend/src/app/api/sidebar/route.ts b/wolai-frontend/src/app/api/sidebar/route.ts index 72360823..71ca68a3 100644 --- a/wolai-frontend/src/app/api/sidebar/route.ts +++ b/wolai-frontend/src/app/api/sidebar/route.ts @@ -1,19 +1,9 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; -import { ensureDefaultWorkspace, fetchWorkspaceSummaries } from "@/lib/workspaces"; -import { fetchSidebarDataset } from "@/lib/sidebar-tree"; import type { SidebarInitialData } from "@/components/sidebar/types"; import { isConvexEnabled } from "@/lib/convex/enabled"; -import { getConvexHttpClient } from "@/lib/convex/server"; import { api } from "@/lib/convex/api"; -import { requireAuthContext } from "@/lib/auth/authContext"; +import { getAuthedConvexClient } from "@/lib/convex/route"; import { randomUUID } from "crypto"; -import { - detectLocalMindmapFiles, - detectLocalMindmapDocs, - detectLocalMindmapImageAssetIdsByMindmapId, - detectLocalTrashedMindmapAssets, -} from "@/lib/server/mindmap-files"; import type { MediaAsset } from "@/types/media"; export const dynamic = "force-dynamic"; @@ -72,20 +62,17 @@ function extractMindmapImageAssetIdsFromData(input: unknown): string[] { export async function GET(request: Request) { if (isConvexEnabled()) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); + const { auth, client } = await getAuthedConvexClient(); const url = new URL(request.url); const workspaceIdParam = url.searchParams.get("workspaceId"); const bootstrap = await client.mutation(api.workspaces.ensureDefaultWorkspace, { - userId: auth.userId, fallbackName: auth.email ?? auth.name ?? "我的空间", workspaceIdIfCreate: randomUUID(), }); const summaries = await client.query(api.workspaces.fetchWorkspaceSummaries, { - userId: auth.userId, }); const workspaces = summaries.workspaces.length > 0 ? summaries.workspaces : bootstrap.workspaces; @@ -98,17 +85,14 @@ export async function GET(request: Request) { try { const documents = await client.query(api.documents.listByWorkspace, { - userId: auth.userId, workspaceId: targetWorkspaceId, }); const trashedDocuments = await client.query(api.documents.listTrashedByWorkspace, { - userId: auth.userId, workspaceId: targetWorkspaceId, }); const mindmapRows = await client.query(api.mindmaps.listByWorkspace, { - userId: auth.userId, workspaceId: targetWorkspaceId, includeDeleted: true, }); @@ -200,6 +184,9 @@ export async function GET(request: Request) { } } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { session }, @@ -301,4 +288,5 @@ export async function GET(request: Request) { { status: 500 }, ); } + */ } diff --git a/wolai-frontend/src/app/api/tables/[tableId]/route.ts b/wolai-frontend/src/app/api/tables/[tableId]/route.ts index 96a21e48..dd7726f3 100644 --- a/wolai-frontend/src/app/api/tables/[tableId]/route.ts +++ b/wolai-frontend/src/app/api/tables/[tableId]/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import type { DocumentTableSnapshot, TableRowData } from "@/types/online-table"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { getAuthedConvexClient } from "@/lib/convex/route"; @@ -25,25 +24,12 @@ const extractTableId = async (context: RouteContext) => { return (await context.params).tableId; }; -const getAuthUser = async () => { - const supabase = await createSupabaseRouteClient(); - const { data, error } = await supabase.auth.getUser(); - if (error) { - console.error("Auth getUser error:", error); - } - if (!data?.user) { - return { supabase, user: null }; - } - return { supabase, user: data.user }; -}; - -export async function GET(request: Request, context: RouteContext) { +export async function GET(_request: Request, context: RouteContext) { const tableId = await extractTableId(context); if (!tableId) { return NextResponse.json({ error: "Missing tableId" }, { status: 400 }); } - // Convex 模式 if (isConvexEnabled()) { const { auth, client } = await getAuthedConvexClient(); try { @@ -63,36 +49,7 @@ export async function GET(request: Request, context: RouteContext) { } } - // Supabase 模式 - const { supabase, user } = await getAuthUser(); - - if (!user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - try { - // 假设 RLS 确保了用户只能访问其有权限的表格 - const { data: tableData, error } = await supabase - .from("document_tables") - .select("*") - .eq("id", tableId) - .single(); - - if (error) { - console.error("Error fetching table:", error); - return NextResponse.json({ error: "Failed to fetch table data" }, { status: 500 }); - } - - if (!tableData) { - return NextResponse.json({ error: "Table not found" }, { status: 404 }); - } - - return NextResponse.json(tableData, { status: 200 }); - - } catch (error) { - console.error("API error:", error); - return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); - } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); } export async function PATCH(request: Request, context: RouteContext) { @@ -101,13 +58,18 @@ export async function PATCH(request: Request, context: RouteContext) { return NextResponse.json({ error: "Missing tableId" }, { status: 400 }); } - // Convex 模式 if (isConvexEnabled()) { const { auth, client } = await getAuthedConvexClient(); + let body: UpdateTableRequest; try { - const body = await request.json() as UpdateTableRequest; + body = (await request.json()) as UpdateTableRequest; + } catch (error) { + console.error("Invalid payload:", error); + return NextResponse.json({ error: "Invalid payload" }, { status: 400 }); + } - const result = await client.mutation(api.tables.update, { + try { + await client.mutation(api.tables.update, { userId: auth.userId, tableId, title: body.title, @@ -117,7 +79,6 @@ export async function PATCH(request: Request, context: RouteContext) { rows: body.rows, }); - // 重新获取更新后的表格数据 const updated = await client.query(api.tables.get, { userId: auth.userId, tableId, @@ -130,125 +91,15 @@ export async function PATCH(request: Request, context: RouteContext) { } } - // Supabase 模式 - const { supabase, user } = await getAuthUser(); - - if (!user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - let body: UpdateTableRequest | null = null; - try { - body = await request.json() as UpdateTableRequest; - } catch (error) { - console.error("Invalid payload:", error); - return NextResponse.json({ error: "Invalid payload" }, { status: 400 }); - } - - try { - const { data: tableMeta, error: metaError } = await supabase - .from("document_tables") - .select("id, workspace_id, document_id, grid_key") - .eq("id", tableId) - .single(); - - if (metaError || !tableMeta) { - console.error("Table not found or load error:", metaError); - return NextResponse.json({ error: "Table not found" }, { status: 404 }); - } - - const updatePayload: Record = { - updated_by: user.id, - last_synced_at: new Date().toISOString(), - }; - - if (body?.title !== undefined) { - updatePayload.title = body.title; - } - if (body?.schema) { - updatePayload.schema = body.schema; - } - if (body?.snapshot !== undefined) { - updatePayload.snapshot = body.snapshot ?? {}; - } - if (body?.viewPreferences) { - updatePayload.view_preferences = body.viewPreferences; - } - - const { error: updateError } = await supabase - .from("document_tables") - .update(updatePayload) - .eq("id", tableId); - - if (updateError) { - console.error("Failed to update table:", updateError); - return NextResponse.json({ error: "Failed to update table", details: updateError.message }, { status: 500 }); - } - - const nextRows = body?.rows ?? body?.snapshot?.rows ?? []; - if (Array.isArray(nextRows)) { - const { error: deleteRowsError } = await supabase - .from("document_table_rows") - .delete() - .eq("table_id", tableId); - - if (deleteRowsError) { - console.error("Failed to clear old rows:", deleteRowsError); - return NextResponse.json({ error: "Failed to update rows", details: deleteRowsError.message }, { status: 500 }); - } - - const sanitizedRows = nextRows - .filter((row) => row && typeof row === "object" && Object.keys(row).length > 0); - - if (sanitizedRows.length > 0) { - const rowsToInsert = sanitizedRows.map((row, index) => ({ - table_id: tableId, - workspace_id: tableMeta.workspace_id, - document_id: tableMeta.document_id, - row_index: index, - row_data: row, - row_hash: null, - is_deleted: false, - updated_by: user.id, - })); - - const { error: insertError } = await supabase - .from("document_table_rows") - .insert(rowsToInsert); - - if (insertError) { - console.error("Failed to insert rows:", insertError); - return NextResponse.json({ error: "Failed to insert rows", details: insertError.message }, { status: 500 }); - } - } - } - - const { data: latest, error: fetchError } = await supabase - .from("document_tables") - .select("*") - .eq("id", tableId) - .single(); - - if (fetchError || !latest) { - console.error("Failed to fetch updated table:", fetchError); - return NextResponse.json({ error: "Failed to load updated table", details: fetchError?.message }, { status: 500 }); - } - - return NextResponse.json(latest, { status: 200 }); - } catch (error) { - console.error("API error:", error); - const message = error instanceof Error ? error.message : "Internal Server Error"; - return NextResponse.json({ error: message }, { status: 500 }); - } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); } -export async function DELETE(request: Request, context: RouteContext) { +export async function DELETE(_request: Request, context: RouteContext) { const tableId = await extractTableId(context); if (!tableId) { return NextResponse.json({ error: "Missing tableId" }, { status: 400 }); } - // Convex 模式 if (isConvexEnabled()) { const { auth, client } = await getAuthedConvexClient(); try { @@ -264,37 +115,6 @@ export async function DELETE(request: Request, context: RouteContext) { } } - // Supabase 模式 - const { supabase, user } = await getAuthUser(); - - if (!user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - try { - const { error: deleteRowsError } = await supabase - .from("document_table_rows") - .delete() - .eq("table_id", tableId); - - if (deleteRowsError) { - console.error("Failed to delete table rows:", deleteRowsError); - return NextResponse.json({ error: "Failed to delete table rows" }, { status: 500 }); - } - - const { error: deleteTableError } = await supabase - .from("document_tables") - .delete() - .eq("id", tableId); - - if (deleteTableError) { - console.error("Failed to delete table:", deleteTableError); - return NextResponse.json({ error: "Failed to delete table" }, { status: 500 }); - } - - return NextResponse.json({ success: true }, { status: 200 }); - } catch (error) { - console.error("API error:", error); - return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); - } + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); } + diff --git a/wolai-frontend/src/app/api/tables/create/route.ts b/wolai-frontend/src/app/api/tables/create/route.ts index 823589c8..800fbbc3 100644 --- a/wolai-frontend/src/app/api/tables/create/route.ts +++ b/wolai-frontend/src/app/api/tables/create/route.ts @@ -1,5 +1,4 @@ import { NextResponse } from "next/server"; -import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { DocumentTableSnapshot, TableSchema } from "@/types/online-table"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { getAuthedConvexClient } from "@/lib/convex/route"; @@ -27,7 +26,6 @@ export async function POST(request: Request) { // 获取 document 所在的 workspace_id const document = await client.query(api.documents.getMeta, { - userId: auth.userId, id: documentId, }); @@ -54,6 +52,9 @@ export async function POST(request: Request) { } // Supabase 模式 + return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); + + /* const supabase = await createSupabaseRouteClient(); const { data: { user } } = await supabase.auth.getUser(); @@ -112,4 +113,5 @@ export async function POST(request: Request) { console.error("API error:", error); return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } + */ } diff --git a/wolai-frontend/src/app/api/workspaces/switch/route.ts b/wolai-frontend/src/app/api/workspaces/switch/route.ts index 5b8637f1..f5189896 100644 --- a/wolai-frontend/src/app/api/workspaces/switch/route.ts +++ b/wolai-frontend/src/app/api/workspaces/switch/route.ts @@ -1,75 +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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; export const dynamic = "force-dynamic"; export async function POST(request: Request) { if (isConvexEnabled()) { - const auth = await requireAuthContext(); + const { client } = await getAuthedConvexClient(); const payload = await request.json().catch(() => ({})); const workspaceId = payload.workspaceId as string | undefined; if (!workspaceId) { return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); } - const client = getConvexHttpClient(); await client.mutation(api.workspaces.switchDefaultWorkspace, { - userId: auth.userId, workspaceId, }); return NextResponse.json({ success: true }); } - - const supabase = await createSupabaseRouteClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - return NextResponse.json({ error: "未登录" }, { status: 401 }); - } - - const payload = await request.json().catch(() => ({})); - const workspaceId = payload.workspaceId as string | undefined; - - if (!workspaceId) { - return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); - } - - const { data: membership, error: membershipError } = await supabase - .from("workspace_members") - .select("id") - .eq("workspace_id", workspaceId) - .eq("user_id", session.user.id) - .single(); - - if (membershipError || !membership) { - return NextResponse.json({ error: "无权切换至该工作空间" }, { status: 403 }); - } - - const { error: resetError } = await supabase - .from("workspace_members") - .update({ is_default: false }) - .eq("user_id", session.user.id); - - if (resetError) { - return NextResponse.json({ error: resetError.message }, { status: 500 }); - } - - const { error: switchError } = await supabase - .from("workspace_members") - .update({ is_default: true }) - .eq("user_id", session.user.id) - .eq("workspace_id", workspaceId); - - if (switchError) { - return NextResponse.json({ error: switchError.message }, { status: 500 }); - } - - return NextResponse.json({ success: true }); + return NextResponse.json({ error: "仅支持 Convex 模式" }, { status: 501 }); } diff --git a/wolai-frontend/src/app/globals.css b/wolai-frontend/src/app/globals.css index 9b0aca28..bd1884c7 100644 --- a/wolai-frontend/src/app/globals.css +++ b/wolai-frontend/src/app/globals.css @@ -43,6 +43,17 @@ --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); + + /* Wolai 设计系统颜色变量 */ + --color-wolai-bg: #FFFFFF; + --color-wolai-bg-sidebar: #F7F7F5; + --color-wolai-bg-hover: #EFEFEF; + --color-wolai-bg-active: #E8E8E7; + --color-wolai-text-primary: #37352F; + --color-wolai-text-secondary: #9B9A97; + --color-wolai-border: #E9E9E8; + --color-wolai-brand: #22A559; + --font-wolai-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "PingFang SC", Helvetica, Arial, sans-serif; } :root { @@ -87,6 +98,45 @@ body, min-height: 100%; } +/* Wolai 字体栈 */ +body { + font-family: -apple-system, "Microsoft YaHei", sans-serif; + color: #37352F; + background-color: #FFFFFF; +} + +/* 滚动条美化 - 悬停时显示 */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 4px; +} +:hover::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.1); +} + +/* BlockNote 变量覆盖 */ +:root { + --bn-colors-editor-background: #FFFFFF; + --bn-colors-editor-text: #37352F; + --bn-font-family: -apple-system, "Microsoft YaHei", sans-serif; + --bn-border-radius: 4px; +} + +/* 调整编辑器内容宽度 */ +.bn-editor { + padding-left: 5rem; + padding-right: 5rem; + max-width: 900px; + margin: 0 auto; +} + .dark { --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); @@ -510,4 +560,109 @@ body, #luckysheet-rich-text-editor { z-index: 100 !important; } + + /* Wolai 编辑器样式 */ + .wolai-editor .bn-block-content { + color: #37352F; + line-height: 1.6; + } + + /* 标题样式 */ + .wolai-editor h1 { + font-size: 30px; + font-weight: 700; + margin-top: 1.5em; + margin-bottom: 0.5em; + color: #37352F; + line-height: 1.3; + } + + .wolai-editor h2 { + font-size: 24px; + font-weight: 600; + margin-top: 1.4em; + margin-bottom: 0.4em; + color: #37352F; + line-height: 1.4; + } + + .wolai-editor h3 { + font-size: 20px; + font-weight: 600; + margin-top: 1.3em; + margin-bottom: 0.4em; + color: #37352F; + line-height: 1.4; + } + + /* 列表样式 */ + .wolai-editor ul, + .wolai-editor ol { + padding-left: 1.5em; + } + + .wolai-editor li { + margin: 2px 0; + } + + /* 引用块样式 */ + .wolai-editor blockquote { + border-left: 3px solid #E9E9E8; + padding-left: 1em; + color: #9B9A97; + font-style: italic; + } + + /* 代码块样式 */ + .wolai-editor pre { + background-color: #F7F7F5; + border-radius: 4px; + padding: 1em; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; + font-size: 14px; + } + + /* 分割线样式 */ + .wolai-editor hr { + border-color: #E9E9E8; + margin: 1.5em 0; + } + + /* 链接样式 */ + .wolai-editor a { + color: #2563eb; + text-decoration: underline; + } + + .wolai-editor a:hover { + color: #1d4ed8; + } + + /* Slash 菜单样式 */ + .bn-suggestion-menu { + background-color: #FFFFFF; + border: 1px solid #E9E9E8; + border-radius: 4px; + box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px; + } + + .bn-suggestion-menu-item:hover, + .bn-suggestion-menu-item.selected { + background-color: #EFEFEF; + } + + .bn-suggestion-menu-item-title { + color: #37352F; + font-weight: 500; + } + + .bn-suggestion-menu-item-subtext { + color: #9B9A97; + } + + .bn-suggestion-menu-item-group-title { + color: #9B9A97; + font-size: 12px; + font-weight: 600; + } } diff --git a/wolai-frontend/src/app/layout.tsx b/wolai-frontend/src/app/layout.tsx index ebb167e2..fadc4b65 100644 --- a/wolai-frontend/src/app/layout.tsx +++ b/wolai-frontend/src/app/layout.tsx @@ -1,10 +1,8 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; -import { SupabaseProvider } from "@/components/providers/supabase-provider"; import { QueryProvider } from "@/components/providers/query-provider"; import { ConvexClientProvider } from "@/components/providers/convex-provider"; -import { createSupabaseServerClient } from "@/lib/supabase/server"; import { getMnoteRuntimeConfig } from "@/lib/runtime-config"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { ConvexAuthNextjsServerProvider } from "@convex-dev/auth/nextjs/server"; @@ -16,7 +14,7 @@ const inter = Inter({ export const metadata: Metadata = { title: "Wolai Clone", - description: "Stage0 + Stage1 implementation powered by Supabase", + description: "Convex 自部署模式", }; export default async function RootLayout({ @@ -27,16 +25,6 @@ export default async function RootLayout({ const isDesktop = process.env.MNOTE_DESKTOP === "1"; const useConvex = isConvexEnabled(); - let session = null; - if (!useConvex) { - try { - session = (await (await createSupabaseServerClient()).auth.getSession()).data.session; - } catch { - // 说明:不阻塞页面渲染,交由客户端登录页处理(例如网络暂不可达)。 - session = null; - } - } - const runtimeConfig = { ...getMnoteRuntimeConfig(), isDesktop, useConvex }; const runtimeConfigJson = JSON.stringify(runtimeConfig).replace(/ - {useConvex ? ( - - - {children} - - - ) : ( - + + {children} - - )} + + ); diff --git a/wolai-frontend/src/app/page.tsx b/wolai-frontend/src/app/page.tsx index 3e336438..d54e2ffc 100644 --- a/wolai-frontend/src/app/page.tsx +++ b/wolai-frontend/src/app/page.tsx @@ -1,9 +1,6 @@ import { redirect } from "next/navigation"; -import { createSupabaseServerClient } 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 { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; function makeId(): string { @@ -13,80 +10,34 @@ function makeId(): string { } export default async function Home() { - if (isConvexEnabled()) { - const auth = await requireAuthContext(); - const client = getConvexHttpClient(); - - const { activeWorkspaceId } = await client.mutation(api.workspaces.ensureDefaultWorkspace, { - userId: auth.userId, - fallbackName: auth.name ?? auth.email ?? "我的空间", - workspaceIdIfCreate: makeId(), - }); - - const docs = await client.query(api.documents.listByWorkspace, { - userId: auth.userId, - workspaceId: activeWorkspaceId, - }); - - const firstDoc = [...docs].sort((a, b) => (a.created_at ?? "").localeCompare(b.created_at ?? ""))[0]; - if (firstDoc?.id) { - redirect(`/documents/${firstDoc.id}`); - } - - const docId = makeId(); - await client.mutation(api.documents.create, { - userId: auth.userId, - id: docId, - workspaceId: activeWorkspaceId, - parentId: null, - title: "新页面", - accessScope: "private", - content: [], - }); - redirect(`/documents/${docId}`); + if (!isConvexEnabled()) { + redirect("/auth"); } - const supabase = await createSupabaseServerClient(); - const session = (await supabase.auth.getSession()).data.session; + const { auth, client } = await getAuthedConvexClient(); - if (!session) { - redirect("/login"); - } + const { activeWorkspaceId } = await client.mutation(api.workspaces.ensureDefaultWorkspace, { + fallbackName: auth.name ?? auth.email ?? "我的空间", + workspaceIdIfCreate: makeId(), + }); - await ensureDefaultWorkspace(supabase, session.user.id, session.user.email ?? "我的空间"); - const workspaceId = await resolveActiveWorkspaceId(supabase, session.user.id); - - if (!workspaceId) { - redirect("/login"); - } - - const { data: firstDoc } = await supabase - .from("documents") - .select("id") - .eq("user_id", session.user.id) - .eq("workspace_id", workspaceId) - .order("created_at", { ascending: true }) - .limit(1) - .maybeSingle(); + const docs = await client.query(api.documents.listByWorkspace, { + workspaceId: activeWorkspaceId, + }); + const firstDoc = [...docs].sort((a, b) => (a.created_at ?? "").localeCompare(b.created_at ?? ""))[0]; if (firstDoc?.id) { redirect(`/documents/${firstDoc.id}`); } - const { data: createdDoc, error } = await supabase - .from("documents") - .insert({ - user_id: session.user.id, - workspace_id: workspaceId, - title: "新页面", - content: {}, - }) - .select("id") - .single(); - - if (error || !createdDoc) { - redirect("/login"); - } - - redirect(`/documents/${createdDoc.id}`); + const docId = makeId(); + await client.mutation(api.documents.create, { + id: docId, + workspaceId: activeWorkspaceId, + parentId: null, + title: "新页面", + accessScope: "private", + content: [], + }); + redirect(`/documents/${docId}`); } diff --git a/wolai-frontend/src/components/breadcrumb.tsx b/wolai-frontend/src/components/breadcrumb.tsx index de055afc..b266192b 100644 --- a/wolai-frontend/src/components/breadcrumb.tsx +++ b/wolai-frontend/src/components/breadcrumb.tsx @@ -31,16 +31,19 @@ export function Breadcrumb({ documents }: BreadcrumbProps) { return (
-