Skip to content

Conversation

@Gyuhyeok99
Copy link
Contributor

@Gyuhyeok99 Gyuhyeok99 commented Sep 25, 2025

관련 이슈

작업 내용

차단 관련 API를 구현하였습니다.

  • 로그인한 유저의 차단 목록 조회 API
  • 차단 API
  • 차단 취소 API

급하게 구현을 하다보니 커밋을 완전 많이 쪼개진 못했는데 그래도 노력은 했습니다 ㅎㅎ..
그래도 커밋 따라가면 이해하기 편하실겁니다!

커뮤니티 부분은 현재 인성님이 수정해주고 계신 거 같아서 최대한 기존코드 유지하면서 로직 추가했습니다!
(커뮤니티 쪽 코드가 엄청 지저분하네요 🥲 앞으로 개선될 것이라고 생각합니다!)

  1. 우선 차단을 했을 때 게시글 목록에서 차단한 유저의 게시글은 보이지 않도록 했습니다!
  2. 그럼에도 게시글을 조회하려한다면 예외를 던지게 했습니다!
  3. 특정 게시글에 차단한 유저의 댓글이 있다면 보이지 않게 했습니다!

관련 명세서 : https://github.com/solid-connection/api-docs/pull/51

특이 사항

만욱님의 요청으로 여기서 변경할 사항은 아니지만.. 로그인하지 않은 유저도 게시글 목록 볼 수 있게 수정했습니다!
너무 간단한 pr이 될 거 같아 그냥 같이 작업했습니다!

리뷰 요구사항 (선택)

1. 차단 api의 위치

우선 siteUser쪽에 구현했는데 괜찮은가요? 커뮤니티쪽에 구현할까도 생각했는데 추후 채팅 등 다른곳에서도 차단이 생길 수 있다고 생각해 가장 범용적으로 사용될 수 있는 siteUser쪽으로 구현했습니다.

2. 단방향 차단 vs 양방향 차단

우선은 단방향으로 본인이 차단한 유저의 게시글 및 댓글만 보이지 않게 했는데 이래도 문제 없을까요?

3. 댓글 관련 차단

여기가 가장 고민이 되었던 지점인데.. 차단한 유저의 댓글에 차단하지 않은 유저의 대댓글이 있다면 어떻게 해야할까요?

  1. 차단한 유저의 댓글만 숨기고 대댓글은 보이게한다 -> 이때 그럼 그 부모댓글은 어떻게 보여야할까요?
  2. 그냥 차단하지 않은 유저의 대댓글이더라도 부모댓글은 차단한 유저이기에 모두 숨긴다

사실 2번이 간단해보여서 2번으로 구현했습니다 ㅎㅎ.. 그런데 구현하고 나니 2번도 나쁘지 않아보인다는 생각이 드네요

@coderabbitai
Copy link

coderabbitai bot commented Sep 25, 2025

Walkthrough

  1. 차단 도메인 확장: ErrorCode에 차단 관련 3개 코드(BLOCK_USER_NOT_FOUND, ALREADY_BLOCKED_BY_CURRENT_USER, CANNOT_BLOCK_YOURSELF)를 추가했습니다.
  2. 차단 엔터티/레포지토리: UserBlock에 blockerId/blockedId 생성자를 추가하고 UserBlockRepository를 도입해 존재 확인, 단건 조회, 닉네임 포함한 Slice 조회 쿼리를 제공했습니다.
  3. 사이트유저 API 및 서비스: SiteUserController에 차단 리스트 조회(Get), 차단(Post), 차단 해제(Delete) 엔드포인트를 추가하고 SiteUserService에 차단 생성·취소·조회 로직과 검증을 구현했습니다.
  4. 게시글 접근 제어: PostQueryService에 siteUserId 기반 차단 검사와 차단 시 ACCESS_DENIED 처리 로직을 추가하고, 게시글 목록 조회에서 차단 사용자를 제외하도록 PostRepository를 확장했습니다.
  5. 댓글 트리 필터링: CommentRepository의 재귀 CTE 쿼리를 siteUserId로 차단된 사용자의 댓글을 제외하도록 수정하고 CommentService가 새 메서드 시그니처를 사용하도록 변경했습니다.
  6. 컨트롤러 인증 처리 변경: BoardController의 @Authorizeduser를 optional(required = false)로 바꾸고 siteUserId를 Long으로 받아 서비스에 전달하도록 변경했습니다.
  7. 테스트·픽스처 추가: UserBlockFixture 및 UserBlockFixtureBuilder와 관련 단위/통합 테스트를 추가해 차단 관련 동작(목록, 차단, 해제, 접근 제한, 댓글 필터링)을 검증하도록 했습니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • wibaek
  • whqtker
  • lsy1307

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning 익명 사용자가 게시글 목록을 조회할 수 있도록 BoardController와 PostQueryService에 추가된 변경 사항은 연결 이슈 #512의 범위를 벗어난 기능으로 보이며 차단 API 구현과 직접 관련성이 떨어집니다. 익명 사용자 접근 로직을 별도의 PR로 분리하거나 이슈 범위에 포함시키려면 이슈 설명을 업데이트하여 관련성을 명확히 해 주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 “feat: 차단 관련 api 구현”은 차단 기능과 연관된 핵심 변경 사항을 명확히 요약하여 간결하고 구체적이어서 PR의 주된 목적을 잘 반영합니다.
Linked Issues Check ✅ Passed 연결된 이슈 #512의 요구 사항인 사용자 차단 구현, 마이페이지 차단 목록 조회, 차단 취소 기능이 모두 코드 및 테스트로 충실히 반영되어 이슈의 목표를 완벽하게 달성하고 있습니다.
Description Check ✅ Passed 제공된 템플릿의 모든 필수 섹션(관련 이슈, 작업 내용, 특이 사항, 선택적 리뷰 요구사항)을 충실히 채우고 구체적인 설명과 질문을 포함하여 PR 설명이 충분히 완성되었습니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (13)
src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java (2)

195-220: 테스트 안정성: 순서 의존성 제거 제안.
DB/정렬 조건 변화에 덜 민감하게 하려면 containsExactly 대신 containsExactlyInAnyOrder를 고려해 주세요.

적용 예시:

-                () -> assertThat(responses)
-                        .extracting(PostFindCommentResponse::id)
-                        .containsExactly(parentComment1.getId(), childComment1.getId()),
+                () -> assertThat(responses)
+                        .extracting(PostFindCommentResponse::id)
+                        .containsExactlyInAnyOrder(parentComment1.getId(), childComment1.getId()),

199-205: 오탈자(nit): 변수명 정정 권장.
parentCommen2 → parentComment2로 바꾸면 가독성이 좋아집니다.

-            Comment parentCommen2 = commentFixture.부모_댓글("부모 댓글2", post, user2);
-            Comment childComment3 = commentFixture.자식_댓글("자식 댓글1", post, user1, parentCommen2);
-            Comment childComment4 = commentFixture.자식_댓글("자식 댓글1", post, user1, parentCommen2);
+            Comment parentComment2 = commentFixture.부모_댓글("부모 댓글2", post, user2);
+            Comment childComment3 = commentFixture.자식_댓글("자식 댓글1", post, user1, parentComment2);
+            Comment childComment4 = commentFixture.자식_댓글("자식 댓글1", post, user1, parentComment2);
src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java (1)

19-27: NOT IN 서브쿼리 → NOT EXISTS로의 전환을 고려해 주세요.
NOT EXISTS가 널/성능 측면에서 일반적으로 더 안전합니다. (blockedId가 non-null이라면 현 구현도 문제는 없지만, EXISTS가 쿼리 플랜 상 유리한 경우가 많습니다.)

-    @Query("""
-       SELECT p FROM Post p
-       WHERE p.boardCode = :boardCode
-       AND p.siteUserId NOT IN (
-           SELECT ub.blockedId FROM UserBlock ub WHERE ub.blockerId = :siteUserId
-       )
-       """)
+    @Query("""
+       SELECT p FROM Post p
+       WHERE p.boardCode = :boardCode
+       AND NOT EXISTS (
+           SELECT 1 FROM UserBlock ub
+           WHERE ub.blockerId = :siteUserId
+             AND ub.blockedId = p.siteUserId
+       )
+       """)
     List<Post> findByBoardCodeExcludingBlockedUsers(@Param("boardCode") String boardCode, @Param("siteUserId") Long siteUserId);

서비스 계층에서 siteUserId가 null일 때는 본 메서드를 호출하지 않고 findByBoardCode(...) 경로만 타는지 한 번만 확인 부탁드립니다.

src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java (1)

87-88: 1) 차단 목록 조회 테스트 정렬의 결정성 보강.
동일 초 단위 createdAt 저장 시 역순 정렬이 비결정적일 수 있어 id를 2차 정렬 키로 추가하는 것을 권합니다.

아래처럼 보조 정렬 키를 추가해 주세요.

-            pageable = PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "createdAt"));
+            pageable = PageRequest.of(
+                0, 20,
+                Sort.by(Sort.Order.desc("createdAt"), Sort.Order.desc("id"))
+            );
src/main/java/com/example/solidconnection/community/board/controller/BoardController.java (1)

34-42: 2) 응답 타입을 구체화해 API 명세를 선명하게.
와일드카드 대신 제네릭 타입을 명시하면 컨트롤러 계약이 더 명확해집니다.

아래처럼 변경을 제안드립니다.

-    public ResponseEntity<?> findPostsByCodeAndCategory(
+    public ResponseEntity<List<PostListResponse>> findPostsByCodeAndCategory(
...
-        List<PostListResponse> postsByCodeAndPostCategory = postQueryService
+        List<PostListResponse> postsByCodeAndPostCategory = postQueryService
                 .findPostsByCodeAndPostCategory(code, category, siteUserId);
-        return ResponseEntity.ok().body(postsByCodeAndPostCategory);
+        return ResponseEntity.ok(postsByCodeAndPostCategory);
src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java (1)

55-61: 2) 게시글 목록의 최신순 정렬 일관성 확보 필요.
todo가 남아 있듯 현재 최신순 보장이 없습니다. 리포지토리 레벨에서 createdAt DESC, id DESC 정렬을 명시해 주세요.

정렬을 JPQL/Query 메서드에 명시하거나 Pageable 기본 정렬을 강제하는 방식으로 개선을 권장드립니다.

src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java (2)

15-39: 1) NOT IN 서브쿼리 대신 NOT EXISTS로 변경해 성능·NULL 안정성 향상.
NOT IN은 NULL 포함 시 예기치 않은 결과를 낳을 수 있습니다. NOT EXISTS와 명시적 NULL 허용 조건으로 대체를 권장합니다.

아래처럼 변경하면 명확하고 빠릅니다.

-    @Query(value = """
-               WITH RECURSIVE CommentTree AS (
-                       SELECT
-                               id, parent_id, post_id, site_user_id, content,
-                               created_at, updated_at, is_deleted,
-                               0 AS level, CAST(id AS CHAR(255)) AS path
-                       FROM comment
-                       WHERE post_id = :postId AND parent_id IS NULL
-                       AND site_user_id NOT IN (
-                           SELECT blocked_id FROM user_block WHERE blocker_id = :siteUserId
-                       )
-                       UNION ALL
-                       SELECT
-                               c.id, c.parent_id, c.post_id, c.site_user_id, c.content,
-                               c.created_at, c.updated_at, c.is_deleted,
-                               ct.level + 1, CONCAT(ct.path, '->', c.id)
-                       FROM comment c
-                       INNER JOIN CommentTree ct ON c.parent_id = ct.id
-                       WHERE c.site_user_id NOT IN (
-                           SELECT blocked_id FROM user_block WHERE blocker_id = :siteUserId
-                       )
-               )
-               SELECT * FROM CommentTree
-               ORDER BY path
-               """, nativeQuery = true)
+    @Query(value = """
+               WITH RECURSIVE CommentTree AS (
+                       SELECT
+                               id, parent_id, post_id, site_user_id, content,
+                               created_at, updated_at, is_deleted,
+                               0 AS level, CAST(id AS CHAR(255)) AS path
+                       FROM comment
+                       WHERE post_id = :postId
+                         AND parent_id IS NULL
+                         AND (
+                             :siteUserId IS NULL OR NOT EXISTS (
+                                 SELECT 1
+                                 FROM user_block ub
+                                 WHERE ub.blocker_id = :siteUserId
+                                   AND ub.blocked_id = comment.site_user_id
+                             )
+                         )
+                       UNION ALL
+                       SELECT
+                               c.id, c.parent_id, c.post_id, c.site_user_id, c.content,
+                               c.created_at, c.updated_at, c.is_deleted,
+                               ct.level + 1, CONCAT(ct.path, '->', c.id)
+                       FROM comment c
+                       INNER JOIN CommentTree ct ON c.parent_id = ct.id
+                       WHERE (
+                             :siteUserId IS NULL OR NOT EXISTS (
+                                 SELECT 1
+                                 FROM user_block ub
+                                 WHERE ub.blocker_id = :siteUserId
+                                   AND ub.blocked_id = c.site_user_id
+                             )
+                       )
+               )
+               SELECT * FROM CommentTree
+               ORDER BY path
+               """, nativeQuery = true)

15-39: 3) 인덱스 권장: user_block(blocker_id, blocked_id).
차단 필터가 빈번하므로 해당 복합 인덱스를 권장합니다. 성능에 큰 영향을 줍니다.

src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixtureBuilder.java (1)

17-19: 1) 빌더 재사용 패턴 명확화.
userBlock()이 새로운 빌더 인스턴스를 반환하므로 체이닝은 반드시 반환 객체에 이어서 호출해야 합니다. 의도된 사용이라면 주석으로 명시하면 가독성이 좋아집니다.

src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java (2)

50-53: 1) 원시 타입 비교는 == 로 간결하게.
auto-boxing을 피하려면 Objects.equals 대신 'blockerId == blockedId'가 더 적합합니다.

-        if (Objects.equals(blockerId, blockedId)) {
+        if (blockerId == blockedId) {
             throw new CustomException(CANNOT_BLOCK_YOURSELF);
         }

63-70: 2) 차단 해제 시 사용자 존재 여부 노출 최소화.
존재하지 않는 사용자에 대해 USER_NOT_FOUND를 반환하면 사용자 열거 위험이 있습니다. 블록 관계 유무만으로 BLOCK_USER_NOT_FOUND를 반환하는 쪽이 안전합니다.

정책 합의가 된다면 아래처럼 단순화하세요.

-    public void cancelUserBlock(long blockerId, long blockedId) {
-        if (!siteUserRepository.existsById(blockedId)) {
-            throw new CustomException(USER_NOT_FOUND);
-        }
-        UserBlock userBlock = userBlockRepository.findByBlockerIdAndBlockedId(blockerId, blockedId)
-                .orElseThrow(() -> new CustomException(BLOCK_USER_NOT_FOUND));
-        userBlockRepository.delete(userBlock);
-    }
+    public void cancelUserBlock(long blockerId, long blockedId) {
+        UserBlock userBlock = userBlockRepository.findByBlockerIdAndBlockedId(blockerId, blockedId)
+                .orElseThrow(() -> new CustomException(BLOCK_USER_NOT_FOUND));
+        userBlockRepository.delete(userBlock);
+    }
src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java (1)

45-61: 2) REST 경로 일관성 소폭 개선 제안.
POST/DELETE 모두 /users/blocks/{blocked-id}로 통일하면 리소스 모델이 더 선명합니다. 응답 코드는 201(Created)/204(No Content)도 고려할 수 있습니다.

src/main/java/com/example/solidconnection/siteuser/repository/UserBlockRepository.java (1)

18-26: 1) 정렬을 쿼리에 명시해 일관성 확보.
Pageable 정렬 위임도 가능하지만, createdAt DESC, id DESC를 쿼리에 고정하면 테스트/운영에서 결과가 안정적입니다.

     @Query("""
            SELECT new com.example.solidconnection.siteuser.dto.UserBlockResponse(
                ub.id, ub.blockedId, su.nickname, ub.createdAt
            )
            FROM UserBlock ub
            JOIN SiteUser su ON ub.blockedId = su.id
            WHERE ub.blockerId = :blockerId
+           ORDER BY ub.createdAt DESC, ub.id DESC
            """)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10969dd and 159781f.

📒 Files selected for processing (16)
  • src/main/java/com/example/solidconnection/common/exception/ErrorCode.java (2 hunks)
  • src/main/java/com/example/solidconnection/community/board/controller/BoardController.java (1 hunks)
  • src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java (1 hunks)
  • src/main/java/com/example/solidconnection/community/comment/service/CommentService.java (1 hunks)
  • src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java (1 hunks)
  • src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java (4 hunks)
  • src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java (2 hunks)
  • src/main/java/com/example/solidconnection/siteuser/domain/UserBlock.java (1 hunks)
  • src/main/java/com/example/solidconnection/siteuser/dto/UserBlockResponse.java (1 hunks)
  • src/main/java/com/example/solidconnection/siteuser/repository/UserBlockRepository.java (1 hunks)
  • src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java (1 hunks)
  • src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java (3 hunks)
  • src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java (9 hunks)
  • src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixture.java (1 hunks)
  • src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixtureBuilder.java (1 hunks)
  • src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixture.java (1)
src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixtureBuilder.java (1)
  • TestComponent (8-35)
src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixtureBuilder.java (1)
src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixture.java (1)
  • TestComponent (7-20)
src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java (1)
src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java (1)
  • RequiredArgsConstructor (23-71)
src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java (1)
src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java (1)
  • RequiredArgsConstructor (21-62)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (22)
src/main/java/com/example/solidconnection/siteuser/domain/UserBlock.java (1)

38-41: 생성자 추가 LGTM.
테스트/픽스처 용도로 깔끔하고 의도가 분명합니다.

src/main/java/com/example/solidconnection/common/exception/ErrorCode.java (2)

50-50: BLOCK_USER_NOT_FOUND 추가 적절.
404 매핑과 메시지 모두 흐름에 부합합니다.


130-133: 차단 관련 에러코드 2종 추가 LGTM.
자기차단 방지/중복차단 방지에 맞는 400 매핑입니다.

src/main/java/com/example/solidconnection/community/comment/service/CommentService.java (1)

39-45: siteUserId 기반 차단 필터 적용 경로 OK.
Repository 호출부에 siteUserId 전달로 차단 필터가 선반영됩니다.

익명 사용자의 댓글 조회는 비범위로 보였는데, 의도적으로 로그인 사용자만 허용하는지 확인 부탁드립니다.

src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java (1)

60-62: 차단 픽스처 주입 LGTM.
테스트 가독성과 재사용성에 도움이 됩니다.

src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java (5)

60-62: 차단 픽스처 주입 LGTM.
차단 시나리오 셋업이 간결해졌습니다.


107-112: siteUserId null 인자 추가 LGTM.
비로그인 목록 조회 요구사항과 일치합니다.


131-135: 전체 카테고리 + null 사용자 시그니처 적용 확인.
기존 시나리오 보존이 잘 되었습니다.


237-259: 차단 사용자의 게시글 제외 테스트 LGTM.
단방향 차단 정책을 정확히 검증하고 있습니다.


205-208: null 사용자 케이스 유지 검증 LGTM.
기존 동작 파괴 없이 요구사항 확장이 잘 되었습니다.

Also applies to: 223-226

src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixture.java (1)

13-18: 테스트 픽스처 빌더 체인 사용 LGTM.
차단 관계 생성이 간결합니다.

src/main/java/com/example/solidconnection/siteuser/dto/UserBlockResponse.java (1)

5-12: DTO 정의 LGTM.
레코드로 불변성과 직관성을 확보했습니다.

src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java (1)

132-171: 2) 차단/차단해제 서비스 동작 검증 케이스 충실.
중복 차단, 자기 자신 차단, 차단 해제까지 핵심 흐름이 깔끔하게 커버됩니다. 좋은 테스트입니다.

src/main/java/com/example/solidconnection/community/board/controller/BoardController.java (1)

36-41: 1) 비로그인 허용 처리(L:36)와 서비스 파라미터 전달(L:40) 적절.
@Authorizeduser(required = false)와 Long 사용으로 익명 접근을 자연스럽게 지원합니다.

src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java (2)

70-74: 3) 차단한 작성자의 게시글 접근 제한 로직 적합.
요구사항(차단한 사용자의 글 접근 시 예외)에 부합합니다.


49-62: 1. 메소드 호출부 점검 결과
- findPostsByCodeAndPostCategory 호출부 전부에 siteUserId 파라미터가 정상 반영되었습니다.

src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixtureBuilder.java (1)

31-34: 2) 테스트 픽스처 구현 간결하고 적절.
엔티티 생성자와 저장까지 일관되게 캡슐화되어 재사용성이 높습니다.

src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java (2)

43-48: 3) 블록 생성 흐름 적절하며 트랜잭션 경계도 타당.
유효성 검증 → 생성 → 저장 순서가 명료합니다.


27-60: 중복 차단 제약 적용 확인

  1. 이미 선언된 제약
    UserBlock 엔티티에 uk_user_block_blocker_id_blocked_id 유니크 제약이 선언되어 있습니다.
  2. 추가 제약 불필요
    별도 DDL 유니크 제약 추가는 필요 없습니다.
src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java (1)

36-43: 1) 차단 목록 조회 API 시그니처와 페이징 기본값 적절.
Slice 기반 응답과 createdAt DESC 기본 정렬이 의도에 부합합니다.

src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java (1)

39-40: 시그니처 변경에 따른 호출부 점검 완료

  1. 호출부 일관성
     - CommentService에서 findCommentTreeByPostId(postId, siteUserId) 호출이 올바르게 적용됨을 확인했습니다.
  2. 누락된 호출부 없음
     - 추가 검색 결과, 모든 호출부가 두 개의 파라미터를 사용하고 있습니다.
src/main/java/com/example/solidconnection/siteuser/repository/UserBlockRepository.java (1)

18-26: ✅ DTO 생성자 시그니처 일치 확인

  1. 시그니처 점검
    • UserBlockResponse(long id, long blockedId, String nickname, ZonedDateTime createdAt)가 쿼리 인자(ub.id, ub.blockedId, su.nickname, ub.createdAt) 순서와 타입 모두 완벽히 일치합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java (2)

180-193: 4) 차단된 사용자의 게시글 접근 시 예외 검증 OK, 메시지 대신 코드 검증을 추천합니다.
메시지는 변경에 취약하니 가능하면 ErrorCode 기반으로 단언하면 테스트 안정성이 올라갑니다. assertThatCode 스타일은 팀 선호에 맞춰 유지하되, 아래처럼 보완해 보세요.

-        assertThatCode(() -> postQueryService.findPostById(user.getId(), post.getId()))
-                .isInstanceOf(CustomException.class)
-                .hasMessage(ACCESS_DENIED.getMessage());
+        assertThatCode(() -> postQueryService.findPostById(user.getId(), post.getId()))
+                .isInstanceOf(CustomException.class)
+                .satisfies(throwable -> {
+                    CustomException ex = (CustomException) throwable;
+                    // CustomException에 errorCode 접근자가 있을 경우 권장
+                    assertThat(ex.getErrorCode()).isEqualTo(ACCESS_DENIED);
+                });

가능 여부: CustomException에 getErrorCode()가 없다면 현 구현 유지 또는 hasFieldOrPropertyWithValue("errorCode", ACCESS_DENIED)로 대체를 고려해 주세요.


236-258: 5) 목록에서 차단 사용자 게시글 제외 테스트가 정확합니다.
양성/음성 케이스가 균형 있게 검증되어 회귀 방지에 유용합니다.

추가 아이디어:

  • 동일 쿼리에 siteUserId=null을 넘겼을 때 차단 필터가 미적용됨을 보이는 보완 테스트를 하나 더 두면 스펙이 더 선명해집니다.

고정된 식별자 파라미터(사용자(1), 사용자(2))가 다른 테스트와 충돌하지 않도록 픽스처가 고유 제약을 안전하게 처리하는지 한 번만 확인해 주세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 159781f and d9c7b63.

📒 Files selected for processing (2)
  • src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java (3 hunks)
  • src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-25T15:53:53.382Z
Learnt from: Gyuhyeok99
PR: solid-connection/solid-connect-server#513
File: src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java:180-193
Timestamp: 2025-09-25T15:53:53.382Z
Learning: Gyuhyeok99 prefers using assertThatCode() for exception testing in AssertJ tests, even when testing that exceptions are thrown. This works functionally and the user has confirmed it works well in their codebase.

Applied to files:

  • src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (3)
src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java (3)

3-5: 1) 차단 시나리오용 import 추가 적절합니다.
의도와 사용처가 명확해 테스트 가독성이 좋아졌습니다.

Also applies to: 8-9, 22-22


60-62: 2) UserBlockFixture 주입 OK, 픽스처 격리만 한 번 확인 부탁.
테스트 간 상태 간섭이 없도록 트랜잭션 롤백/컨테이너 리셋이 보장되는지 점검하면 좋습니다.


110-112: 3) siteUserId 인자(null) 전달 케이스 검증이 명확합니다.
비로그인/필터 미적용 시나리오를 잘 커버하고 있습니다.

익명 사용자의 게시글 목록 조회 허용 변화가 API 스펙과 일치하는지(관련 컨트롤러/문서) 한 번만 교차 확인해 주세요.

Also applies to: 133-135, 205-207, 223-225

@Hexeong
Copy link
Contributor

Hexeong commented Sep 26, 2025

대댓글 관련으로 추가 의견 드리자면, 전 댓글을 차단했을 때, 대댓글을 보여야 한다고 생각합니다.

  • 먼저 사용자는 댓글만 차단했지, 대댓글에 대한 행동은 제한하지 않았기 때문에 이는 사용자에게 의도하지 않은 실행으로 여겨질 수 있다고 생각합니다.
  • 또한, 대댓글을 단 사람의 입장에서, 해당 대댓글이 악의적인 행동이 아닐 경우, 그 대댓글이 다른 사람들에게 보여질 기회는 계속 주어져야 된다고 생각해요!

실제로 다른 플랫폼에서는 어떻게 로직이 구현이 되나 봤더니, 동일한 차단 기능이 있는 에브리타임 또한 댓글만 blur처리를 했고, 대댓글은 보이게 해뒀더라구요! 아래는 참고 사진입니다! 의견이 도움이 됐으면 좋겠습니다 :)

image

@Hexeong
Copy link
Contributor

Hexeong commented Sep 26, 2025

단방향 vs 양방향 차단 관련해서 제 생각에는 대댓글과 마찬가지로 단방향 차단이 좋다고 생각합니다 ㅎㅎ

  • 먼저 게시글에 대한 차단 기능의 목적은 사용자가 불편감을 느끼는 컨텐츠에 대해 보이지 않도록 하는 행위라고 생각이 드네요..!
  • 또한, 보통 상대가 나를 차단했음을 직접적으로 알려주는 다른 서비스들을 못봤기도 하기에 저는 단방향이라고 생각합니다 ㅎㅎ

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java (1)

15-39: 차단 사용자 목록 서브쿼리 재사용 제안

차단 목록을 두 번 계산하면서 살짝 중복이 보입니다. 공통 CTE를 두면 읽기도 쉬워지고 차단 로직을 바꿀 때 한 군데만 수정하면 돼서 유지보수성이 올라갑니다. 작은 손질로 쾌적함을 챙겨봅시다.

-               WITH RECURSIVE CommentTree AS (
+               WITH RECURSIVE BlockedUsers AS (
+                       SELECT blocked_id FROM user_block WHERE blocker_id = :siteUserId
+               ),
+               CommentTree AS (
                        SELECT
                                id, parent_id, post_id, site_user_id, content,
                                created_at, updated_at, is_deleted,
                                0 AS level, CAST(id AS CHAR(255)) AS path
                        FROM comment
-                       WHERE post_id = :postId AND parent_id IS NULL
-                       AND site_user_id NOT IN (
-                           SELECT blocked_id FROM user_block WHERE blocker_id = :siteUserId
-                       )
+                       WHERE post_id = :postId AND parent_id IS NULL
+                       AND NOT EXISTS (
+                           SELECT 1 FROM BlockedUsers bu WHERE bu.blocked_id = site_user_id
+                       )
                        UNION ALL
                        SELECT
                                c.id, c.parent_id, c.post_id, c.site_user_id, c.content,
                                c.created_at, c.updated_at, c.is_deleted,
                                ct.level + 1, CONCAT(ct.path, '->', c.id)
                        FROM comment c
                        INNER JOIN CommentTree ct ON c.parent_id = ct.id
-                       WHERE c.site_user_id NOT IN (
-                           SELECT blocked_id FROM user_block WHERE blocker_id = :siteUserId
-                       )
+                       WHERE NOT EXISTS (
+                           SELECT 1 FROM BlockedUsers bu WHERE bu.blocked_id = c.site_user_id
+                       )
                )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9c7b63 and a94ae52.

📒 Files selected for processing (3)
  • src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java (1 hunks)
  • src/main/java/com/example/solidconnection/community/comment/service/CommentService.java (1 hunks)
  • src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixture.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixture.java (1)
src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixtureBuilder.java (1)
  • TestComponent (8-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
src/test/java/com/example/solidconnection/siteuser/fixture/UserBlockFixture.java (1)

13-18: 체이닝 빌더로 깔끔한 픽스처 구성

  1. 체이닝으로 깔끔한 호출 흐름이 만들어져 읽는 재미가 생겼습니다.
  2. 빌더 상태를 캡슐화해 다른 테스트와의 간섭 우려도 줄었다는 점이 든든합니다.
src/main/java/com/example/solidconnection/community/comment/service/CommentService.java (1)

39-57: 차단 필터 호출 변경 잘 반영되었습니다

서비스 층에서 바로 차단 필터링 메서드를 호출해 흐름이 훨씬 또렷해졌습니다. 테스트만 통과하면 그대로 가도 되겠습니다.

Copy link
Member

@whqtker whqtker left a comment

Choose a reason for hiding this comment

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

로직은 문제 없는 거 같아 approve하겠습니다 !!
todo 관련 코멘트도 남겼습니다


public interface PostRepository extends JpaRepository<Post, Long> {

List<Post> findByBoardCode(String boardCode);
Copy link
Member

Choose a reason for hiding this comment

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

todo 관련해서 OrderByCreatedAtDesc 적용하면 될 거 같습니다 !
오래된 순으로 응답이 만들어지는 게 당연한 로직이었네요 ...

Copy link
Contributor Author

@Gyuhyeok99 Gyuhyeok99 Sep 27, 2025

Choose a reason for hiding this comment

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

앗 이건 일부로 적용 안했습니다! 차단기능 바로 상용 나갈건데 이러면 프론트 강제배포가 필요해서요!
서버만 먼저 나가면 오래된순으로 보이는 문제가 발생할 거 같습니다..
왜 아무도 몰랐을까요 🥲

인성님이 작업해주실테니 그때 수정해서 프론트랑 같이 배포나가면 좋을 거 같습니다! @Hexeong

SELECT ub.blockedId FROM UserBlock ub WHERE ub.blockerId = :siteUserId
)
""")
List<Post> findByBoardCodeExcludingBlockedUsers(@Param("boardCode") String boardCode, @Param("siteUserId") Long siteUserId);
Copy link
Member

Choose a reason for hiding this comment

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

여기도 todo 관련해서 order by p.createAt desc 뒤에 붙이면 정렬될 거 같습니다

Copy link
Contributor Author

Choose a reason for hiding this comment

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

위에 말씀드린 것과 같은 이야기입니다!

boolean isBlockedByMe = userBlockRepository.existsByBlockerIdAndBlockedId(siteUserId, post.getSiteUserId());
if (isBlockedByMe) {
throw new CustomException(ACCESS_DENIED);
}
Copy link
Member

Choose a reason for hiding this comment

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

개인적으론 별도의 validate 메서드로 분리하는 것이 더 좋을 거 같습니다 ! 해당 메서드의 핵심은 id 로 게시글 조회라서요.

또한 findPostById 메서드에서 isBlockedByMe 가 재사용되지 않으므로 existsByBlockerIdAndBlockedId 메서드 리턴 값을 변수로 빼는 것 대신 바로 if 문 안에 넣어도 좋을 거 같습니다 !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

좋습니다~ 변수 제거하고 validate 메서드로 수정했습니다! d6331ba

() -> assertThat(responses)
.extracting(PostFindCommentResponse::id)
.doesNotContain(childComment2.getId(), parentCommen2.getId(), childComment3.getId(), childComment4.getId()))
;
Copy link
Member

Choose a reason for hiding this comment

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

엇 세미콜론 올려주세요 !!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

지금 컨벤션에 세미콜론 올리는 게 아니라

); 로 되어있는 거 같아서
); 이거로 수정했습니다!

꼼꼼하게 봐주셔서 감사합니다 ☺️ a4a6e5f

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java (1)

126-130: 2. 메서드 네이밍 조정 제안

현재 이름은 과거형으로 읽혀서 검증 메서드임을 바로 파악하기 어렵습니다. 예를 들어 validateNotBlockedByMe 정도로 바꾸면 의도가 더 분명해질 듯합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a94ae52 and d6331ba.

📒 Files selected for processing (2)
  • src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java (5 hunks)
  • src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java (1)

55-60: 1. 게시글 목록 차단 필터링이 잘 연결됐습니다

로그인 사용자에 대해서만 차단 대상 사용자를 배제하도록 분기 처리한 부분이 명확합니다. 기존 흐름도 그대로 유지되어 안정적으로 보입니다.

@Gyuhyeok99 Gyuhyeok99 merged commit 17aa696 into solid-connection:develop Sep 27, 2025
2 checks passed
@Gyuhyeok99 Gyuhyeok99 deleted the feat/506-user-block-api branch November 9, 2025 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 차단 관련 기능 구현

3 participants