Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,42 @@ public record BodyForReactFlow(
public record NodeDto(
@JsonProperty("id") String nodeKey,
@JsonProperty("type") String nodeType,
Map<String, String> data,
@JsonProperty("position") PositionDto positionDto
@JsonProperty("selected") Boolean selected,
@JsonProperty("dragging") Boolean dragging,
@JsonProperty("position") PositionDto positionDto,
@JsonProperty("measured") MeasurementsDto measurements,
@JsonProperty("data") DataDto data
) {
public record PositionDto(
@JsonProperty("x") double x,
@JsonProperty("y") double y
) {}
) {}

public record MeasurementsDto(
@JsonProperty("width") double width,
@JsonProperty("height") double height
) {}

public record DataDto(
@JsonProperty("content") String content,
@JsonProperty("createdAt") String createAt, // YYYY-MM-DD format
Copy link

Copilot AI Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the parameter name 'createAt'. It should be 'createdAt' to match the JSON property.

Suggested change
@JsonProperty("createdAt") String createAt, // YYYY-MM-DD format
@JsonProperty("createdAt") String createdAt, // YYYY-MM-DD format

Copilot uses AI. Check for mistakes.
@JsonProperty("link") String sourceUrl,
@JsonProperty("title") String title,
@JsonProperty("user") WriterDto writer
) {}

public record WriterDto(
@JsonProperty("name") String name,
@JsonProperty("profileUrl") String profileImageUrl
) {}

}

public record EdgeDto(
@JsonProperty("id") String edgeKey,
@JsonProperty("source") String sourceNodeKey,
@JsonProperty("target") String targetNodeKey,
@JsonProperty("type") String edgeType,
@JsonProperty("animated") boolean isAnimated,
@JsonProperty("style") StyleDto styleDto
@JsonProperty("target") String targetNodeKey
) {
public record StyleDto(
String stroke,
Double strokeWidth
) {}
}

// DTO -> Entity, BodyForReactFlow를 Graph 엔티티로 변환
Expand All @@ -51,10 +66,24 @@ public Graph toEntity() {
Node node = new Node();
node.setNodeKey(dto.nodeKey());
node.setNodeType(NodeType.valueOf(dto.nodeType().toUpperCase()));
node.setData(dto.data());
node.setSelected(dto.selected() != null ? dto.selected() : false);
node.setDragging(dto.dragging() != null ? dto.dragging() : false);
node.setPositonX(dto.positionDto().x());
node.setPositonY(dto.positionDto().y());
node.setGraph(graph); // 연관관계 설정
if (dto.measurements() != null) {
node.setWidth(dto.measurements().width());
node.setHeight(dto.measurements().height());
}
if (dto.data() != null) {
node.setData(Map.of(
"content", dto.data().content(),
"createdAt", dto.data().createAt(),
"sourceUrl", dto.data().sourceUrl(),
"title", dto.data().title(),
"writerName", dto.data().writer() != null ? dto.data().writer().name() : null,
"writerProfileImageUrl", dto.data().writer() != null ? dto.data().writer().profileImageUrl() : null
));
}
return node;
})
.toList();
Expand All @@ -65,12 +94,6 @@ public Graph toEntity() {
edge.setEdgeKey(dto.edgeKey());
edge.setSourceNodeKey(dto.sourceNodeKey());
edge.setTargetNodeKey(dto.targetNodeKey());
edge.setEdgeType(EdgeType.valueOf(dto.edgeType().toUpperCase()));
edge.setAnimated(dto.isAnimated());
if (dto.styleDto() != null) {
edge.setStroke(dto.styleDto().stroke());
edge.setStrokeWidth(dto.styleDto().strokeWidth());
}
edge.setGraph(graph); // 연관관계 설정
return edge;
})
Expand All @@ -88,19 +111,28 @@ public static BodyForReactFlow from(Graph graph) {
.map(n -> new NodeDto(
n.getNodeKey(),
n.getNodeType().name().toUpperCase(),
n.getData(),
new NodeDto.PositionDto(n.getPositonX(), n.getPositonY())
n.isSelected(),
n.isDragging(),
new NodeDto.PositionDto(n.getPositonX(), n.getPositonY()),
new NodeDto.MeasurementsDto(n.getWidth(), n.getHeight()),
new NodeDto.DataDto(
n.getData().get("content"),
n.getData().get("createdAt"),
n.getData().get("sourceUrl"),
n.getData().get("title"),
new NodeDto.WriterDto(
n.getData().get("writerName"),
n.getData().get("writerProfileImageUrl")
)
)
))
.toList();

List<EdgeDto> edgeDtos = graph.getEdges().stream()
.map(e -> new EdgeDto(
e.getEdgeKey(),
e.getSourceNodeKey(),
e.getTargetNodeKey(),
e.getEdgeType().name().toUpperCase(),
e.isAnimated(),
new EdgeDto.StyleDto(e.getStroke(), e.getStrokeWidth())
e.getTargetNodeKey()
))
.toList();

Expand All @@ -113,9 +145,22 @@ public List<Node> toNodeEntities(Graph graph) {
Node node = new Node();
node.setNodeKey(dto.nodeKey());
node.setNodeType(NodeType.valueOf(dto.nodeType().toUpperCase()));
node.setData(dto.data());
node.setPositonX(dto.positionDto().x());
node.setPositonY(dto.positionDto().y());
if (dto.measurements() != null) {
node.setWidth(dto.measurements().width());
node.setHeight(dto.measurements().height());
}
if (dto.data() != null) {
node.setData(Map.of(
"content", dto.data().content(),
"createdAt", dto.data().createAt(),
"sourceUrl", dto.data().sourceUrl(),
"title", dto.data().title(),
"writerName", dto.data().writer() != null ? dto.data().writer().name() : null,
"writerProfileImageUrl", dto.data().writer() != null ? dto.data().writer().profileImageUrl() : null
));
}
node.setGraph(graph); // 연관관계 설정
return node;
})
Expand All @@ -129,12 +174,6 @@ public List<Edge> toEdgeEntities(Graph graph) {
edge.setEdgeKey(dto.edgeKey());
edge.setSourceNodeKey(dto.sourceNodeKey());
edge.setTargetNodeKey(dto.targetNodeKey());
edge.setEdgeType(EdgeType.valueOf(dto.edgeType().toUpperCase()));
edge.setAnimated(dto.isAnimated());
if (dto.styleDto() != null) {
edge.setStroke(dto.styleDto().stroke());
edge.setStrokeWidth(dto.styleDto().strokeWidth());
}
edge.setGraph(graph); // 연관관계 설정
return edge;
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,4 @@ public class Edge extends BaseEntity {

@Column
private String targetNodeKey;

@Column
@Enumerated(EnumType.STRING)
private EdgeType edgeType;

@Column
boolean isAnimated;

@Column
private String stroke;

@Column
private Double strokeWidth;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ public class Node extends BaseEntity {
@Enumerated(EnumType.STRING)
private NodeType nodeType;

@Column
private boolean selected;

@Column
private boolean dragging;

@ElementCollection
@CollectionTable(name = "node_data", joinColumns = @JoinColumn(name = "node_id"))
@MapKeyColumn(name = "data_key")
Expand All @@ -35,4 +41,10 @@ public class Node extends BaseEntity {

@Column
private double positonY;
Copy link

Copilot AI Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the field name 'positonY'. It should be 'positionY'.

Copilot uses AI. Check for mistakes.

@Column
private double width;

@Column
private double height;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.tuna.zoopzoop.backend.domain.dashboard.entity.Edge;
import org.tuna.zoopzoop.backend.domain.dashboard.entity.Graph;
import org.tuna.zoopzoop.backend.domain.dashboard.entity.Node;
import org.tuna.zoopzoop.backend.domain.member.enums.Provider;
import org.tuna.zoopzoop.backend.domain.member.repository.MemberRepository;
import org.tuna.zoopzoop.backend.domain.member.service.MemberService;
Expand All @@ -30,6 +32,7 @@
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
Expand Down Expand Up @@ -183,9 +186,32 @@ void updateGraph_Success() throws Exception {
transactionTemplate.execute(status -> {
Space space = spaceService.findByName(authorizedSpaceName);
Graph updatedGraph = space.getDashboard().getGraph();

// 1. 노드와 엣지의 전체 개수 검증
assertThat(updatedGraph.getNodes()).hasSize(2);
assertThat(updatedGraph.getEdges()).hasSize(1);
assertThat(updatedGraph.getNodes().get(0).getData().get("title")).isEqualTo("노드1");

// 2. 특정 노드(id="1")의 상세 데이터 검증
// DTO의 List 순서대로 엔티티가 생성되므로 get(0)으로 첫 번째 노드를 특정합니다.
Node firstNode = updatedGraph.getNodes().get(0);
assertThat(firstNode.getNodeKey()).isEqualTo("1");
assertThat(firstNode.getPositonX()).isEqualTo(100.0);
assertThat(firstNode.getPositonY()).isEqualTo(200.0);

// Node의 data Map 내부 값들을 상세히 검증
Map<String, String> data = firstNode.getData();
assertThat(data.get("title")).isEqualTo("노드 1");
assertThat(data.get("content")).isEqualTo("첫 번째 노드에 대한 간단한 요약 내용입니다. 이 내용은 노드 내부에 표시됩니다.");
assertThat(data.get("sourceUrl")).isEqualTo("https://example.com/source1");
assertThat(data.get("writerName")).isEqualTo("김Tuna");
assertThat(data.get("writerProfileImageUrl")).isEqualTo("https://example.com/profiles/tuna.jpg");

// 3. 엣지(id="e1-2")의 상세 데이터 검증
Edge edge = updatedGraph.getEdges().get(0);
assertThat(edge.getEdgeKey()).isEqualTo("e1-2");
assertThat(edge.getSourceNodeKey()).isEqualTo("1");
assertThat(edge.getTargetNodeKey()).isEqualTo("2");

return null; // execute 메서드는 반환값이 필요
});
});
Expand Down Expand Up @@ -235,32 +261,65 @@ void updateGraph_Fail_Forbidden() throws Exception {

private String createReactFlowJsonBody() {
return """
{
"nodes": [
{
"id": "1",
"type": "CUSTOM",
"data": { "title": "노드1", "description": "설명1" },
"position": { "x": 100, "y": 200 }
},
{
"id": "2",
"type": "CUSTOM",
"data": { "title": "노드2" },
"position": { "x": 300, "y": 400 }
}
],
"edges": [
{
"id": "e1-2",
"source": "1",
"target": "2",
"type": "SMOOTHSTEP",
"animated": true,
"style": { "stroke": "#999", "strokeWidth": 2.0 }
}
]
}
{
"nodes": [
{
"id": "1",
"type": "CUSTOM",
"selected": false,
"dragging": false,
"position": {
"x": 100,
"y": 200
},
"measured": {
"width": 250,
"height": 150
},
"data": {
"content": "첫 번째 노드에 대한 간단한 요약 내용입니다. 이 내용은 노드 내부에 표시됩니다.",
"createdAt": "2025-10-02",
"link": "https://example.com/source1",
"title": "노드 1",
"user": {
"name": "김Tuna",
"profileUrl": "https://example.com/profiles/tuna.jpg"
}
}
},
{
"id": "2",
"type": "CUSTOM",
"selected": false,
"dragging": false,
"position": {
"x": 500,
"y": 300
},
"measured": {
"width": 250,
"height": 150
},
"data": {
"content": "두 번째 노드에 대한 요약입니다. 원본 소스는 다른 곳을 가리킵니다.",
"createdAt": "2025-10-01",
"link": "https://example.com/source2",
"title": "노드 2",
"user": {
"name": "박Zoop",
"profileUrl": "https://example.com/profiles/zoop.jpg"
}
}
}
],
"edges": [
{
"id": "e1-2",
"source": "1",
"target": "2"
}
]
}
""";
}

Expand Down
Loading