Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -75,6 +75,7 @@ public final class ExampleKeyConstant {
public static final String POST_GET_BY_ID_404 = "Post__Get__By__Id__404";
public static final String POST_FIND_BY_TITLE_200 = "Post__Find__By__Title__200";
public static final String POST_GRAPH_GET_ALL_STREAMED_200 = "Post__Graph__Get__All__Streamed__200";
public static final String POST_GRAPH_GET_BY_CLUSTER_200 = "Post__Graph__Get__By__Cluster__200";
public static final String POST_GRAPH_SYNC_200 = "Post__Graph__Sync__200";
public static final String POST_GRAPH_SYNC_400 = "Post__Graph__Sync__400";
public static final String POST_GRAPH_CREATE_RELATION_201 = "Post__Graph__Create__Relation__201";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -27,6 +28,12 @@ public interface PostGraphApi {
@ApiErrorExample(include = {"401", "500"})
ResponseEntity<StreamingResponseBody> getAllPost();

@GetMapping(value = "/cluster/{cluster}", produces = MediaType.APPLICATION_NDJSON_VALUE)
@Operation(summary = "특정 클러스터 게시글 조회", description = "해당 클러스터에 해당하는 게시글 및 관련 데이터를 조회합니다.")
@ApiSuccessExample({@ApiSuccessExample.Success(code = "200", exampleKey = POST_GRAPH_GET_BY_CLUSTER_200)})
@ApiErrorExample(include = {"401", "500"})
ResponseEntity<StreamingResponseBody> getPostsByCluster(@PathVariable int cluster);

@GetMapping("/sync")
@Operation(summary = "게시글 그래프 동기화", description = "외부 소스에서 게시글을 동기화하여 그래프를 업데이트합니다.")
@ApiSuccessExample({@ApiSuccessExample.Success(code = "200", exampleKey = POST_GRAPH_SYNC_200)})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public ResponseEntity<StreamingResponseBody> getAllPost() {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_NDJSON).body(streamBody);
}

@Override
public ResponseEntity<StreamingResponseBody> getPostsByCluster(int cluster) {
StreamingResponseBody streamBody = postWebMapper.toStreamingResponseBody(getPostGraphUseCase.getPostsByCluster(cluster));
return ResponseEntity.ok().contentType(MediaType.APPLICATION_NDJSON).body(streamBody);
}

@Override
public ResponseEntity<CommonResponse<Void>> syncPosts() {
syncPostGraphUseCase.syncPosts();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ public Stream<PostGraphView> streamAllWithPromotesAndSimilar() {
return postNeo4JRepository.streamAllWithPromotesAndSimilar().map(postNeo4jMapper::toGraphView);
}

@Override
public Stream<PostGraphView> streamByClusterWithPromotesAndSimilar(int cluster) {
return postNeo4JRepository.streamByClusterWithPromotesAndSimilar(cluster).map(postNeo4jMapper::toGraphView);
}

@Override
public int updatePostIdByContentAndLink(String content, String link, String postId) {
return postNeo4JRepository.updatePostIdByContentAndLink(content, link, postId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import com.vigilante.retriever.v1.post.adapter.out.persistence.neo4j.node.PostNode;
Expand All @@ -23,6 +24,15 @@ RETURN p, collect(DISTINCT pr), collect(DISTINCT c), collect(DISTINCT sr), colle
""")
Stream<PostNode> streamAllWithPromotesAndSimilar();

@Query("""
MATCH (p:Post)
WHERE p.cluster = $cluster
OPTIONAL MATCH (p)-[pr:PROMOTES]->(c:Channel)
OPTIONAL MATCH (p)-[sim:SIMILAR]->(sp:Post)
RETURN p, collect(pr), collect(c), collect(sim), collect(sp)
""")
Stream<PostNode> streamByClusterWithPromotesAndSimilar(@Param("cluster") int cluster);

@Query("""
MATCH (p:Post)
WHERE p.content = $content AND p.link = $link AND p.postId IS NULL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ public Optional<PostGraphView> findByPostId(String postId) {
public Stream<PostGraphView> streamAllWithPromotesAndSimilar() {
return postNeo4jPort.streamAllWithPromotesAndSimilar();
}

public Stream<PostGraphView> streamByClusterWithPromotesAndSimilar(int cluster) {
return postNeo4jPort.streamByClusterWithPromotesAndSimilar(cluster);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ public class GetPostGraphService implements GetPostGraphUseCase {
public Stream<PostGraphView> getAllPost() {
return postNeo4jQuery.streamAllWithPromotesAndSimilar();
}

@Override
public Stream<PostGraphView> getPostsByCluster(int cluster) {
return postNeo4jQuery.streamByClusterWithPromotesAndSimilar(cluster);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,20 @@ public record Promote(
ChannelGraphView channel
) {
}

public static PostGraphView create(PostGraphView postGraphView) {
return PostGraphView.builder()
.postId(postGraphView.postId())
.title(postGraphView.title())
.link(postGraphView.link())
.domain(postGraphView.domain())
.content(postGraphView.content())
.cluster(postGraphView.cluster())
.discoveredAt(postGraphView.discoveredAt())
.updatedAt(postGraphView.updatedAt())
.isDeleted(postGraphView.isDeleted())
.promotesChannels(postGraphView.promotesChannels())
.similarPosts(postGraphView.similarPosts())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
public interface GetPostGraphUseCase {

Stream<PostGraphView> getAllPost();

Stream<PostGraphView> getPostsByCluster(int cluster);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ public interface PostNeo4jPort {

Stream<PostGraphView> streamAllWithPromotesAndSimilar();

Stream<PostGraphView> streamByClusterWithPromotesAndSimilar(int cluster);

int updatePostIdByContentAndLink(String content, String link, String postId);
}
65 changes: 65 additions & 0 deletions src/main/resources/openapi/examples/post/get-by-cluster.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"success": true,
"code": "200",
"message": "조회가 완료되었습니다",
"data": {
"postId": "mock0000000000000000000001",
"title": "[공지] 예시 게시물 제목입니다",
"link": "https://example.com/post/1",
"domain": "example.com",
"content": "이 게시물은 예시(mock) 데이터입니다. 실제 게시물이 아닙니다.",
"cluster": 1,
"discoveredAt": "2025-01-01T00:00:00.000000",
"updatedAt": "2025-01-02T12:00:00.000000",
"isDeleted": false,
"promotesChannels": [
{
"id": 101,
"channel": {
"channelId": 2858,
"title": "예시 채널",
"username": "mock_channel_user",
"status": "active"
}
},
{
"id": 102,
"channel": {
"channelId": 9999,
"title": "테스트 채널",
"username": "test_channel_user",
"status": "inactive"
}
}
],
"similarPosts": [
{
"postId": "mock0000000000000000000002",
"title": "예시 연관 게시물 1",
"link": "https://example.com/post/2",
"domain": "example.com",
"content": "이 항목은 모의(mock) 데이터이며 실제 게시글이 아닙니다.",
"cluster": 1,
"discoveredAt": "2025-01-01T00:00:00.000000",
"updatedAt": "2025-01-02T00:00:00.000000",
"isDeleted": false,
"promotesChannels": [],
"similarPosts": []
},
{
"postId": "mock0000000000000000000003",
"title": "예시 연관 게시물 2",
"link": "https://example.net/post/3",
"domain": "example.net",
"content": "테스트용 더미 콘텐츠입니다.",
"cluster": 1,
"discoveredAt": "2025-01-01T00:00:00.000000",
"updatedAt": "2025-01-02T00:00:00.000000",
"isDeleted": false,
"promotesChannels": [],
"similarPosts": []
}
]
},
"timestamp": "2025-01-01T00:00:00.000000"
}
Loading