20260513 mindmap优化01
This commit is contained in:
@@ -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) => ({
|
||||
|
||||
Reference in New Issue
Block a user