20260513 mindmap优化01

This commit is contained in:
lix-2026
2026-05-13 22:43:16 +08:00
parent 17c003976b
commit b4a452a8b7
89 changed files with 11557 additions and 707 deletions
+89 -14
View File
@@ -4,13 +4,21 @@ import { requireUserId } from "./_utils/auth";
import { nowIso } from "./_utils/time";
import { enqueueIngestMindmapJob } from "./_utils/ingestJobs";
import { internal } from "./_generated/api";
import { requireCanonicalOwnedDocument } from "./_utils/documentRecord";
import { getCanonicalDocumentByBusinessId, requireCanonicalOwnedDocument } from "./_utils/documentRecord";
import { buildVisibleDocumentIds } from "./_utils/documentVisibility";
const defaultMindmapData = {
data: { text: "中心主题" },
children: [],
};
const bridgeArtifactArgs = {
streamDeltaHint: v.optional(v.any()),
domainEventHint: v.optional(v.any()),
domainEventPlan: v.optional(v.any()),
domainEventPlans: v.optional(v.any()),
};
function resolveGraceSeconds(): number {
const raw = process.env.DELETE_GRACE_SECONDS ?? process.env.NEXT_PUBLIC_DELETE_GRACE_SECONDS ?? "600";
const parsed = Number(raw);
@@ -31,12 +39,77 @@ async function requireOwnedDocument(ctx: any, userId: string, docId: string) {
return await requireCanonicalOwnedDocument<any>(ctx, docId, userId);
}
async function requireWorkspaceMember(ctx: any, workspaceId: string, userId: string) {
const membership = await ctx.db
.query("workspace_members")
.withIndex("by_workspace_user", (q: any) => q.eq("workspace_id", workspaceId).eq("user_id", userId))
.first();
if (!membership) {
throw new Error("无权操作该工作空间");
}
}
async function readWorkspaceVisibilityContext(ctx: any, workspaceId: string, userId: string) {
const docs = await ctx.db
.query("documents")
.withIndex("by_workspace", (q: any) => q.eq("workspace_id", workspaceId))
.collect();
const directShares = await ctx.db
.query("document_shares")
.withIndex("by_workspace_shared_with", (q: any) =>
q.eq("workspace_id", workspaceId).eq("shared_with_user_id", userId),
)
.collect();
const groupMemberships = await ctx.db
.query("group_members")
.withIndex("by_workspace_user", (q: any) => q.eq("workspace_id", workspaceId).eq("user_id", userId))
.collect();
const groupIds = new Set<string>(groupMemberships.map((item: any) => String(item.group_id)));
const directGroupShares: Array<{ document_id: string; include_descendants: boolean | null }> = [];
for (const groupId of groupIds) {
const rows = await ctx.db
.query("document_group_shares")
.withIndex("by_workspace_group", (q: any) => q.eq("workspace_id", workspaceId).eq("group_id", groupId))
.collect();
for (const row of rows) {
directGroupShares.push({
document_id: row.document_id,
include_descendants: row.include_descendants,
});
}
}
return { docs, directShares, directGroupShares };
}
async function requireReadableDocument(ctx: any, userId: string, docId: string) {
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, docId);
if (!doc) {
throw new Error("页面不存在");
}
await requireWorkspaceMember(ctx, doc.workspace_id, userId);
const visibility = await readWorkspaceVisibilityContext(ctx, doc.workspace_id, userId);
const visibleDocumentIds = buildVisibleDocumentIds({
userId,
docs: visibility.docs,
directShares: visibility.directShares,
directGroupShares: visibility.directGroupShares,
});
if (!visibleDocumentIds.has(doc.id)) {
throw new Error("无权限");
}
return doc;
}
export const get = query({
args: { docId: v.string(), mindmapId: v.string() },
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
const doc = await requireOwnedDocument(ctx, userId, args.docId);
const doc = await requireReadableDocument(ctx, userId, args.docId);
const mindmapId = normalizeMindmapId(args.docId, args.mindmapId);
const row = await ctx.db
@@ -122,6 +195,7 @@ export const put = mutation({
mindmapId: v.string(),
data: v.any(),
createOnly: v.optional(v.boolean()),
...bridgeArtifactArgs,
},
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
@@ -195,7 +269,7 @@ export const put = mutation({
});
export const softDelete = mutation({
args: { docId: v.string(), mindmapId: v.string() },
args: { docId: v.string(), mindmapId: v.string(), ...bridgeArtifactArgs },
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
@@ -299,7 +373,7 @@ export const purgeIfExpired = internalMutation({
});
export const restore = mutation({
args: { docId: v.string(), mindmapId: v.string() },
args: { docId: v.string(), mindmapId: v.string(), ...bridgeArtifactArgs },
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
@@ -328,7 +402,7 @@ export const restore = mutation({
});
export const purge = mutation({
args: { docId: v.string(), mindmapId: v.string() },
args: { docId: v.string(), mindmapId: v.string(), ...bridgeArtifactArgs },
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
@@ -359,14 +433,15 @@ export const listByWorkspace = query({
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", userId))
.first();
if (!membership) {
throw new Error("无权操作该工作空间");
}
await requireWorkspaceMember(ctx, args.workspaceId, userId);
const visibility = await readWorkspaceVisibilityContext(ctx, args.workspaceId, userId);
const visibleDocumentIds = buildVisibleDocumentIds({
userId,
docs: visibility.docs,
directShares: visibility.directShares,
directGroupShares: visibility.directGroupShares,
includeDeletedDocuments: true,
});
const rows = await ctx.db
.query("mindmaps")
@@ -376,7 +451,7 @@ export const listByWorkspace = query({
const includeDeleted = Boolean(args.includeDeleted);
return rows
.filter((r) => r.user_id === userId)
.filter((r) => visibleDocumentIds.has(r.document_id))
.filter((r) => (includeDeleted ? true : r.deleted_at == null))
.sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? ""))
.map((r) => ({