fix(convex): validate every node in batch mutations
This commit is contained in:
@@ -40,6 +40,36 @@ async function getCanvasIfAuthorized(
|
|||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getValidatedBatchNodesOrThrow(
|
||||||
|
ctx: MutationCtx,
|
||||||
|
userId: string,
|
||||||
|
nodeIds: Id<"nodes">[],
|
||||||
|
): Promise<{ canvasId: Id<"canvases">; nodes: Doc<"nodes">[] }> {
|
||||||
|
if (nodeIds.length === 0) {
|
||||||
|
throw new Error("Batch must contain at least one node id");
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes: Doc<"nodes">[] = [];
|
||||||
|
for (const nodeId of nodeIds) {
|
||||||
|
const node = await ctx.db.get(nodeId);
|
||||||
|
if (!node) {
|
||||||
|
throw new Error("Node not found");
|
||||||
|
}
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvasId = nodes[0].canvasId;
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.canvasId !== canvasId) {
|
||||||
|
throw new Error("All nodes must belong to the same canvas");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await getCanvasOrThrow(ctx, canvasId, userId);
|
||||||
|
|
||||||
|
return { canvasId, nodes };
|
||||||
|
}
|
||||||
|
|
||||||
type NodeCreateMutationName =
|
type NodeCreateMutationName =
|
||||||
| "nodes.create"
|
| "nodes.create"
|
||||||
| "nodes.createWithEdgeSplit"
|
| "nodes.createWithEdgeSplit"
|
||||||
@@ -1252,16 +1282,18 @@ export const batchMove = mutation({
|
|||||||
const user = await requireAuth(ctx);
|
const user = await requireAuth(ctx);
|
||||||
if (moves.length === 0) return;
|
if (moves.length === 0) return;
|
||||||
|
|
||||||
// Canvas-Zugriff über den ersten Node prüfen
|
const nodeIds = moves.map((move) => move.nodeId);
|
||||||
const firstNode = await ctx.db.get(moves[0].nodeId);
|
const { canvasId } = await getValidatedBatchNodesOrThrow(
|
||||||
if (!firstNode) throw new Error("Node not found");
|
ctx,
|
||||||
await getCanvasOrThrow(ctx, firstNode.canvasId, user.userId);
|
user.userId,
|
||||||
|
nodeIds,
|
||||||
|
);
|
||||||
|
|
||||||
for (const { nodeId, positionX, positionY } of moves) {
|
for (const { nodeId, positionX, positionY } of moves) {
|
||||||
await ctx.db.patch(nodeId, { positionX, positionY });
|
await ctx.db.patch(nodeId, { positionX, positionY });
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.db.patch(firstNode.canvasId, { updatedAt: Date.now() });
|
await ctx.db.patch(canvasId, { updatedAt: Date.now() });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1427,23 +1459,19 @@ export const batchRemove = mutation({
|
|||||||
const user = await requireAuth(ctx);
|
const user = await requireAuth(ctx);
|
||||||
if (nodeIds.length === 0) return;
|
if (nodeIds.length === 0) return;
|
||||||
|
|
||||||
// Idempotent: wenn alle Nodes bereits weg sind, no-op.
|
const { canvasId, nodes } = await getValidatedBatchNodesOrThrow(
|
||||||
const firstExistingNode = await (async () => {
|
ctx,
|
||||||
for (const nodeId of nodeIds) {
|
user.userId,
|
||||||
const node = await ctx.db.get(nodeId);
|
nodeIds,
|
||||||
if (node) return node;
|
);
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})();
|
|
||||||
if (!firstExistingNode) return;
|
|
||||||
|
|
||||||
// Canvas-Zugriff über den ersten vorhandenen Node prüfen
|
const uniqueNodes = new Map<Id<"nodes">, Doc<"nodes">>();
|
||||||
const firstNode = firstExistingNode;
|
for (const node of nodes) {
|
||||||
await getCanvasOrThrow(ctx, firstNode.canvasId, user.userId);
|
uniqueNodes.set(node._id, node);
|
||||||
|
}
|
||||||
|
|
||||||
for (const nodeId of nodeIds) {
|
for (const node of uniqueNodes.values()) {
|
||||||
const node = await ctx.db.get(nodeId);
|
const nodeId = node._id;
|
||||||
if (!node) continue;
|
|
||||||
|
|
||||||
// Alle Edges entfernen, die diesen Node als Source oder Target haben
|
// Alle Edges entfernen, die diesen Node als Source oder Target haben
|
||||||
const sourceEdges = await ctx.db
|
const sourceEdges = await ctx.db
|
||||||
@@ -1475,6 +1503,6 @@ export const batchRemove = mutation({
|
|||||||
await ctx.db.delete(nodeId);
|
await ctx.db.delete(nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.db.patch(firstNode.canvasId, { updatedAt: Date.now() });
|
await ctx.db.patch(canvasId, { updatedAt: Date.now() });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user