0.3.1 UI修复

This commit is contained in:
liaibo
2026-01-18 19:01:31 +08:00
parent 90a38b48cf
commit 8a5b915002
86 changed files with 1160 additions and 2867 deletions
+91 -26
View File
@@ -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<string> {
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);
}