mirror of
https://github.com/reconurge/flowsint.git
synced 2026-04-30 03:09:05 -05:00
feat(api): nodes positions
This commit is contained in:
@@ -203,6 +203,9 @@ async def get_sketch_nodes(
|
|||||||
"data": record["data"],
|
"data": record["data"],
|
||||||
"label": record["data"].get("label", "Node"),
|
"label": record["data"].get("label", "Node"),
|
||||||
"idx": idx,
|
"idx": idx,
|
||||||
|
# Extract x and y positions if they exist
|
||||||
|
**({"x": record["data"]["x"]} if "x" in record["data"] else {}),
|
||||||
|
**({"y": record["data"]["y"]} if "y" in record["data"] else {}),
|
||||||
}
|
}
|
||||||
for idx, record in enumerate(nodes_result)
|
for idx, record in enumerate(nodes_result)
|
||||||
]
|
]
|
||||||
@@ -400,6 +403,60 @@ def edit_node(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NodePosition(BaseModel):
|
||||||
|
nodeId: str
|
||||||
|
x: float
|
||||||
|
y: float
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePositionsInput(BaseModel):
|
||||||
|
positions: List[NodePosition]
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{sketch_id}/nodes/positions")
|
||||||
|
@update_sketch_timestamp
|
||||||
|
def update_node_positions(
|
||||||
|
sketch_id: str,
|
||||||
|
data: UpdatePositionsInput,
|
||||||
|
background_tasks: BackgroundTasks,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: Profile = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Update positions (x, y) for multiple nodes in batch.
|
||||||
|
This is used to persist node positions after drag operations in the graph viewer.
|
||||||
|
"""
|
||||||
|
# Verify the sketch exists and user has access
|
||||||
|
sketch = db.query(Sketch).filter(Sketch.id == sketch_id).first()
|
||||||
|
if not sketch:
|
||||||
|
raise HTTPException(status_code=404, detail="Sketch not found")
|
||||||
|
check_investigation_permission(
|
||||||
|
current_user.id, sketch.investigation_id, actions=["update"], db=db
|
||||||
|
)
|
||||||
|
|
||||||
|
if not data.positions:
|
||||||
|
return {"status": "no positions to update", "count": 0}
|
||||||
|
|
||||||
|
# Convert Pydantic models to dicts for GraphRepository
|
||||||
|
positions = [pos.model_dump() for pos in data.positions]
|
||||||
|
|
||||||
|
# Update positions using GraphRepository
|
||||||
|
try:
|
||||||
|
graph_repo = GraphRepository(neo4j_connection)
|
||||||
|
updated_count = graph_repo.update_nodes_positions(
|
||||||
|
positions=positions,
|
||||||
|
sketch_id=sketch_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Position update error: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to update node positions")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "positions updated",
|
||||||
|
"count": updated_count,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{sketch_id}/nodes")
|
@router.delete("/{sketch_id}/nodes")
|
||||||
@update_sketch_timestamp
|
@update_sketch_timestamp
|
||||||
def delete_nodes(
|
def delete_nodes(
|
||||||
|
|||||||
@@ -547,6 +547,40 @@ class GraphRepository:
|
|||||||
|
|
||||||
return self._connection.query(cypher, parameters)
|
return self._connection.query(cypher, parameters)
|
||||||
|
|
||||||
|
def update_nodes_positions(
|
||||||
|
self,
|
||||||
|
positions: List[Dict[str, Any]],
|
||||||
|
sketch_id: str
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
Update positions (x, y) for multiple nodes in batch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
positions: List of dicts with keys 'nodeId', 'x', 'y'
|
||||||
|
sketch_id: Investigation sketch ID (for safety)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Number of nodes updated
|
||||||
|
"""
|
||||||
|
if not self._connection or not positions:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
query = """
|
||||||
|
UNWIND $positions AS pos
|
||||||
|
MATCH (n)
|
||||||
|
WHERE elementId(n) = pos.nodeId AND n.sketch_id = $sketch_id
|
||||||
|
SET n.x = pos.x, n.y = pos.y
|
||||||
|
RETURN count(n) as updated_count
|
||||||
|
"""
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"positions": positions,
|
||||||
|
"sketch_id": sketch_id
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self._connection.query(query, params)
|
||||||
|
return result[0]["updated_count"] if result else 0
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
"""Context manager entry."""
|
"""Context manager entry."""
|
||||||
return self
|
return self
|
||||||
|
|||||||
Reference in New Issue
Block a user