Skip to content

[feat] 내가 팔로잉하는 유저들 중, 최근 공개 피드 작성한 유저 정보 조회 api 개발#199

Merged
seongjunnoh merged 10 commits into
developfrom
feat/#196-users-show-recent-feed-writers-of-my-followings
Aug 13, 2025
Merged

[feat] 내가 팔로잉하는 유저들 중, 최근 공개 피드 작성한 유저 정보 조회 api 개발#199
seongjunnoh merged 10 commits into
developfrom
feat/#196-users-show-recent-feed-writers-of-my-followings

Conversation

@seongjunnoh
Copy link
Copy Markdown
Collaborator

@seongjunnoh seongjunnoh commented Aug 12, 2025

#️⃣ 연관된 이슈

closes #196

📝 작업 내용

이슈 참고해주시면 됩니다.

  • 요구사항 정리

    • 내가 팔로잉 하는 유저들 중, 최근에 공개 피드를 작성한 유저들이 누구인지를 알기 위한 api 입니다
    • [userId, nickname, profileImageUrl] 의 정보가 필요합니다
    • 내가 팔로잉 하는 유저들이 작성한 여러 공개 피드 중, 가장 최근에 작성한 공개피드의 createdAt 을 기준으로 정렬하여야 합니다
    • 최대 10개를 반환하고, 페이징 처리는 구현하지 않습니다
  • 주요 코드 설명

    • service 는 비즈니스 로직을 수행하지 않습니다 (QueryDSL 에서 바로 요구사항에 맞는 데이터를 뽑아서 던지도록 하였습니다)
    • 요구사항에 맞는 데이터를 QueryDSL + Query Projection 을 통해 dto로 조회합니다 (따라서 지연로딩으로 인한 n+1 문제 발생 X)
    • UserQueryMapper 에서 dto -> response 로의 매핑을 수행하도록 하였습니다
    • 관련 api 테스트 코드를 작성하였습니다

📸 스크린샷

💬 리뷰 요구사항

@heeeeyong 님이 빠른 연동을 원하셔서 리뷰를 빠르게 해주시면 감사하겠습니다!! @hd0rable , @buzz0331

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • 신기능
    • 팔로잉한 사용자 중 최근 공개 피드를 작성한 작성자 목록 조회 API 추가 — 최신 활동 순 정렬, 최대 10명 반환, 각 항목에 사용자 ID·닉네임·프로필 이미지 포함, 비공개 피드는 제외.
  • 테스트
    • 통합 테스트 추가: 정렬 정확성, 비공개 작성자 제외, 반환 수(최대 10명), 최신 피드 기준 정렬 검증.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 12, 2025

Walkthrough

팔로잉한 사용자 중 최근에 공개 피드를 작성한 작성자 목록(최대 10명)을 조회하는 신규 API를 추가했다. 컨트롤러 엔드포인트, 응용 서비스/포트/매퍼, 영속 어댑터/리포지토리(QueryDSL), 응답 DTO, Query DTO 프로젝션, 통합 테스트가 추가·확장되었다.

Changes

Cohort / File(s) Summary
Web API
src/main/java/.../user/adapter/in/web/UserQueryController.java, src/main/java/.../user/adapter/in/web/response/UserFollowingRecentWritersResponse.java
GET /users/my-followings/recent-feeds 엔드포인트 추가 및 응답 DTO(UserFollowingRecentWritersResponse, RecentWriter) 신설.
Application (Use case / Service / Mapper)
src/main/java/.../user/application/port/in/UserShowFollowingRecentWritersUseCase.java, src/main/java/.../user/application/service/UserShowFollowingRecentWritersService.java, src/main/java/.../user/application/mapper/UserQueryMapper.java
입력 포트와 서비스 구현 추가(사이즈 상수 10). UserQueryDto → RecentWriter 매핑 및 래핑 응답 변환 메서드 추가.
Ports (Out)
src/main/java/.../user/application/port/out/UserQueryPort.java
findRecentFeedWritersOfMyFollowings(Long userId, int size) 메서드 추가.
Persistence Adapter & Repository
src/main/java/.../user/adapter/out/persistence/UserQueryPersistenceAdapter.java, src/main/java/.../user/adapter/out/persistence/repository/UserQueryRepository.java, src/main/java/.../user/adapter/out/persistence/repository/UserQueryRepositoryImpl.java
QueryDSL 기반 쿼리 추가: 팔로잉 사용자의 최근 공개 피드 작성자 조회(작성자별 최신 피드 기준 그룹·정렬, size 제한).
Query DTO
src/main/java/.../user/application/port/out/dto/UserQueryDto.java
@QueryProjection 3-인자 생성자 추가(userId, nickname, profileImageUrl).
Tests
src/test/java/.../user/adapter/in/web/UserShowFollowingRecentWritersApiTest.java
통합 테스트 추가: 정렬(최신순), 비공개 피드 제외, 최대 10개 제한, 최신 피드 기준 정렬 검증, 데이터 셋업/정리 포함.

Sequence Diagram(s)

sequenceDiagram
  participant C as Client
  participant WC as UserQueryController
  participant S as UserShowFollowingRecentWritersService
  participant P as UserQueryPort
  participant PA as UserQueryPersistenceAdapter
  participant R as UserQueryRepository
  participant DB as Database
  participant M as UserQueryMapper

  C->>WC: GET /users/my-followings/recent-feeds (userId)
  WC->>S: showMyFollowingRecentWriters(userId)
  S->>P: findRecentFeedWritersOfMyFollowings(userId, 10)
  P->>PA: delegate
  PA->>R: findFeedWritersOfMyFollowingsOrderByCreatedAtDesc(userId, 10)
  R->>DB: Query (followings + feeds + alias)
  DB-->>R: List<UserQueryDto>
  R-->>PA: List<UserQueryDto>
  PA-->>P: List<UserQueryDto>
  P-->>S: List<UserQueryDto>
  S->>M: toRecentWriterResponses(dtos)
  M-->>S: UserFollowingRecentWritersResponse
  S-->>WC: Response DTO
  WC-->>C: 200 OK (BaseResponse<UserFollowingRecentWritersResponse>)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
팔로잉 중 최근에 피드를 작성한 사용자 정보를 최대 10개 반환하는 API 개발 (#196)

Assessment against linked issues: Out-of-scope changes

(없음)

Possibly related PRs

  • [feat] 사용자 검색 api 구현 #113: UserQueryMapper 및 UserQueryDto 변경과 직접적으로 연관되어 매핑/DTO 충돌 가능성 있음 — 관련 소스 경로와 책임 영역이 겹침.

Suggested reviewers

  • hd0rable
  • buzz0331

Poem

"깡총깡총, 밭을 건너와요 🥕
팔로잉 트레일 위 최신 글 냄새 맡아
열 명 골라 줄 세우니 가장 뜨끈한 순서대로
귀 기울여 전해요, 여기 누가 썼나!"

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#196-users-show-recent-feed-writers-of-my-followings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Aug 12, 2025

Test Results

378 tests  +4   378 ✅ +4   31s ⏱️ -1s
111 suites +1     0 💤 ±0 
111 files   +1     0 ❌ ±0 

Results for commit bc773a6. ± Comparison against base commit 859479f.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (6)
src/main/java/konkuk/thip/user/application/service/UserShowFollowingRecentWritersService.java (2)

14-14: SIZE 상수를 설정 가능하도록 개선 고려

현재 SIZE가 하드코딩된 상수로 정의되어 있습니다. 향후 요구사항 변경 시 유연성을 위해 application.yml에서 설정 가능하도록 하는 것을 고려해보세요.

-    private static final int SIZE = 10;
+    @Value("${user.following.recent-writers.size:10}")
+    private int size;

그리고 Line 20에서도 변경:

-        return userQueryMapper.toRecentWriterResponses(userQueryPort.findRecentFeedWritersOfMyFollowings(userId, SIZE));
+        return userQueryMapper.toRecentWriterResponses(userQueryPort.findRecentFeedWritersOfMyFollowings(userId, size));

19-21: 트랜잭션 설정 확인 필요

읽기 전용 작업이므로 @Transactional(readOnly = true)를 추가하여 성능을 최적화할 수 있습니다.

+    @Transactional(readOnly = true)
     @Override
     public UserFollowingRecentWritersResponse showMyFollowingRecentWriters(Long userId) {
         return userQueryMapper.toRecentWriterResponses(userQueryPort.findRecentFeedWritersOfMyFollowings(userId, SIZE));
     }

필요한 import 추가:

import org.springframework.transaction.annotation.Transactional;
src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java (1)

12-12: 와일드카드 import 사용 지양

와일드카드 import는 코드의 명확성을 떨어뜨릴 수 있습니다. 명시적인 import를 사용하는 것이 좋습니다.

-import konkuk.thip.user.application.port.in.*;
+import konkuk.thip.user.application.port.in.UserViewAliasChoiceUseCase;
+import konkuk.thip.user.application.port.in.UserGetFollowUsecase;
+import konkuk.thip.user.application.port.in.UserIsFollowingUsecase;
+import konkuk.thip.user.application.port.in.UserVerifyNicknameUseCase;
+import konkuk.thip.user.application.port.in.UserSearchUsecase;
+import konkuk.thip.user.application.port.in.UserMyPageUseCase;
+import konkuk.thip.user.application.port.in.UserShowFollowingRecentWritersUseCase;
src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepositoryImpl.java (2)

178-179: JOIN 조건 개선 가능

현재 join(feed).on(feed.userJpaEntity.eq(writer)) 구문은 동작하지만, QueryDSL의 일반적인 패턴과 다릅니다. 더 명확한 조인 구문을 사용하는 것이 좋습니다.

-                .join(feed).on(feed.userJpaEntity.eq(writer))
+                .join(QFeedJpaEntity.feedJpaEntity, feed).on(feed.userJpaEntity.eq(writer))

187-191: GROUP BY 절 최적화 고려

현재 alias.imageUrl을 GROUP BY에 포함시키고 있는데, 이는 NULL 값이 있을 때 여러 행이 생성될 수 있습니다. writer의 고유 식별자만으로 그룹핑하고 집계 함수를 사용하는 것이 더 효율적일 수 있습니다.

                .groupBy(       // 그룹핑
                        writer.userId,
-                        writer.nickname,
-                        alias.imageUrl
+                        writer.nickname
                )

alias.imageUrl은 이미 LEFT JOIN으로 가져오므로, SELECT 절에서 MAX나 MIN 같은 집계 함수를 사용할 수도 있습니다:

                .select(new QUserQueryDto(
                        writer.userId,
                        writer.nickname,
-                        alias.imageUrl
+                        alias.imageUrl.max()
                ))
src/test/java/konkuk/thip/user/adapter/in/web/UserShowFollowingRecentWritersApiTest.java (1)

125-228: 최대 10개 제한 테스트의 반복 코드를 개선할 수 있습니다.

테스트 로직은 정확하지만 12명의 사용자와 피드를 생성하는 반복 코드가 많습니다.

헬퍼 메서드를 사용하여 코드를 간소화할 수 있습니다:

+private void createUsersAndFeeds(AliasJpaEntity alias, UserJpaEntity me, int count, BookJpaEntity book) {
+    List<UserJpaEntity> users = new ArrayList<>();
+    for (int i = 1; i <= count; i++) {
+        UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(alias, "user" + i));
+        users.add(user);
+        followingJpaRepository.save(TestEntityFactory.createFollowing(me, user));
+        FeedJpaEntity feed = feedJpaRepository.save(TestEntityFactory.createFeed(user, book, true, 50, 10, List.of()));
+        
+        feedJpaRepository.flush();
+        LocalDateTime base = LocalDateTime.now();
+        jdbcTemplate.update(
+            "UPDATE posts SET created_at = ? WHERE post_id = ?",
+            Timestamp.valueOf(base.minusMinutes(65 - i * 5)), feed.getPostId());
+    }
+    return users;
+}

그러나 현재 코드도 테스트의 의도가 명확하므로 필수적인 개선은 아닙니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 859479f and 964f08c.

📒 Files selected for processing (11)
  • src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java (3 hunks)
  • src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowingRecentWritersResponse.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/UserQueryPersistenceAdapter.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepository.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepositoryImpl.java (2 hunks)
  • src/main/java/konkuk/thip/user/application/mapper/UserQueryMapper.java (2 hunks)
  • src/main/java/konkuk/thip/user/application/port/in/UserShowFollowingRecentWritersUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/port/out/UserQueryPort.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/port/out/dto/UserQueryDto.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/service/UserShowFollowingRecentWritersService.java (1 hunks)
  • src/test/java/konkuk/thip/user/adapter/in/web/UserShowFollowingRecentWritersApiTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
⏰ 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 (16)
src/main/java/konkuk/thip/user/application/port/out/dto/UserQueryDto.java (2)

27-34: 새로운 3-arg 생성자 구현이 적절합니다.

QueryDSL 프로젝션을 위한 새로운 생성자가 올바르게 구현되었습니다. null 값으로 초기화하여 최근 피드 작성자 정보 조회에 필요한 최소한의 필드만 사용하는 설계가 합리적입니다.


21-24: 검증 로직 주석 처리 안전 확인 완료

7-arg 생성자를 직접 호출하거나 QueryDSL Projections로 사용하는 곳이 코드베이스에서 발견되지 않아, 해당 검증 로직 주석 처리는 다른 사용처에 영향을 주지 않습니다.

src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowingRecentWritersResponse.java (1)

7-11: API 응답 DTO 설계가 우수합니다.

Swagger 문서화와 명확한 필드명을 통해 API 스펙이 잘 정의되었습니다. 중첩된 RecentWriter 레코드로 구조를 명확하게 분리한 것도 좋습니다.

src/main/java/konkuk/thip/user/application/mapper/UserQueryMapper.java (1)

16-23: 매핑 로직이 효율적으로 구현되었습니다.

MapStruct를 활용한 매핑 메서드들이 잘 구성되었습니다. 특히 default 메서드로 래핑 로직을 제공하여 서비스 레이어에서 간편하게 사용할 수 있도록 한 것이 좋습니다.

src/main/java/konkuk/thip/user/application/port/out/UserQueryPort.java (1)

29-29: 포트 메서드 시그니처가 명확합니다.

메서드명과 파라미터가 기능을 잘 표현하고 있으며, 기존 포트 인터페이스와 일관성을 유지하고 있습니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepository.java (1)

21-21: 리포지토리 메서드 네이밍이 명확합니다.

메서드명이 수행하는 작업을 정확히 표현하고 있으며, 정렬 기준(OrderByCreatedAtDesc)도 명시적으로 나타내고 있습니다. 이전 학습에서 확인된 바와 같이 단순한 LocalDateTime 기반 정렬을 선호하는 패턴과도 일치합니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/UserQueryPersistenceAdapter.java (1)

72-75: 메소드 구현이 깔끔합니다!

QueryDSL을 통해 데이터를 직접 조회하여 N+1 문제를 방지한 점이 좋습니다.

src/main/java/konkuk/thip/user/application/port/in/UserShowFollowingRecentWritersUseCase.java (1)

1-8: 인터페이스 설계가 적절합니다!

단일 책임 원칙에 따라 use case를 분리한 점이 좋습니다.

src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java (1)

160-169: 새로운 엔드포인트 구현이 적절합니다!

API 설계가 RESTful하고, 적절한 설명과 파라미터 처리가 잘 되어 있습니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepositoryImpl.java (1)

164-195: 쿼리 최적화가 잘 되었습니다!

QueryDSL과 Query Projection을 활용하여 N+1 문제를 방지하고, 필요한 필드만 조회하도록 구현한 점이 훌륭합니다. GROUP BY와 MAX 집계 함수를 사용한 정렬 로직도 요구사항에 정확히 부합합니다.

src/test/java/konkuk/thip/user/adapter/in/web/UserShowFollowingRecentWritersApiTest.java (6)

1-31: 임포트와 기본 설정이 적절하게 구성되었습니다.

통합 테스트에 필요한 모든 의존성이 올바르게 임포트되었고, Spring Boot 테스트 환경 설정도 적합합니다.


34-38: 테스트 클래스 설정이 올바릅니다.

@AutoConfigureMockMvc(addFilters = false) 설정으로 인증 필터를 비활성화하여 테스트 환경에서 인증 없이 API를 테스트할 수 있도록 구성했습니다.


76-87: JDBC를 통한 타임스탬프 조작이 효과적입니다.

JPA 엔티티의 @CreatedDate 어노테이션으로 인해 자동 생성되는 타임스탬프를 JDBC로 직접 수정하는 방식이 테스트 데이터 설정에 적합합니다. 시간 순서를 명확하게 제어할 수 있어 테스트의 예측 가능성을 높입니다.


90-101: 첫 번째 테스트 케이스가 핵심 요구사항을 잘 검증합니다.

팔로잉한 사용자 중 공개 피드를 작성한 사용자만 조회되고, 최근 작성 순으로 정렬되는 것을 검증합니다. 응답 필드(userId, nickname, profileImageUrl)도 올바르게 확인하고 있습니다.


103-123: 비공개 피드 필터링 테스트가 중요한 엣지 케이스를 검증합니다.

비공개 피드를 작성한 사용자가 결과에서 제외되는 것을 확인하는 테스트로, 개인정보 보호 요구사항을 잘 검증합니다.


230-269: 사용자별 최신 피드 기준 정렬 테스트가 핵심 비즈니스 로직을 검증합니다.

user1이 두 개의 피드(f1, f3)를 작성했을 때, 가장 최신 피드(f3)의 시간을 기준으로 정렬되는 것을 확인하는 중요한 테스트 케이스입니다. 이는 PR 목표의 핵심인 "각 유저의 가장 최근 공개피드의 createdAt 기준으로 정렬" 요구사항을 정확히 검증합니다.

Comment on lines +48 to +55
@AfterEach
void tearDown() {
feedJpaRepository.deleteAllInBatch();
followingJpaRepository.deleteAllInBatch();
userJpaRepository.deleteAllInBatch();
aliasJpaRepository.deleteAllInBatch();
bookJpaRepository.deleteAllInBatch();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

데이터 정리 순서를 외래키 제약조건에 맞게 조정해야 합니다.

현재 정리 순서가 외래키 제약조건을 위반할 수 있습니다. 참조되는 엔티티를 먼저 삭제하면 제약조건 위반 오류가 발생할 수 있습니다.

다음과 같이 의존성 순서에 맞게 수정하세요:

 @AfterEach
 void tearDown() {
     feedJpaRepository.deleteAllInBatch();
     followingJpaRepository.deleteAllInBatch();
-    userJpaRepository.deleteAllInBatch();
-    aliasJpaRepository.deleteAllInBatch();
     bookJpaRepository.deleteAllInBatch();
+    userJpaRepository.deleteAllInBatch();
+    aliasJpaRepository.deleteAllInBatch();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@AfterEach
void tearDown() {
feedJpaRepository.deleteAllInBatch();
followingJpaRepository.deleteAllInBatch();
userJpaRepository.deleteAllInBatch();
aliasJpaRepository.deleteAllInBatch();
bookJpaRepository.deleteAllInBatch();
}
@AfterEach
void tearDown() {
feedJpaRepository.deleteAllInBatch();
followingJpaRepository.deleteAllInBatch();
bookJpaRepository.deleteAllInBatch();
userJpaRepository.deleteAllInBatch();
aliasJpaRepository.deleteAllInBatch();
}
🤖 Prompt for AI Agents
In
src/test/java/konkuk/thip/user/adapter/in/web/UserShowFollowingRecentWritersApiTest.java
around lines 48-55, the tearDown deletes repositories in an order that can
violate foreign-key constraints; change the deletion order to remove dependent
child records first then parents — e.g., delete feedJpaRepository, then
followingJpaRepository, then aliasJpaRepository, then bookJpaRepository, and
finally userJpaRepository (keep using deleteAllInBatch calls), so all child rows
are removed before their referenced parent rows.

Copy link
Copy Markdown
Member

@hd0rable hd0rable left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!! 간단히 리뷰남겼는데 확인 부탁드립니다!!


import java.util.List;

@Schema(description = "전체 피드 조회 상단에서의 내 띱 목록 응답 DTO")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

와우 친절띠니 LGTM

.from(following)
.join(following.followingUserJpaEntity, writer) // writer : 나에게 팔로잉 당하는 사람들
.join(feed).on(feed.userJpaEntity.eq(writer))
.leftJoin(writer.aliasForUserJpaEntity, alias)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

p2: 저희 모든 유저가 alias와 1대1 매핑관계이고, 모든 유저가 alias를 가지고있으니 inner join으로 하는 것은 어떨까요?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

오 예리하시네요 UserJpaEntity와 연관된 AliasJpaEntity는 nullable = false 이니 inner join이 더 성능적으로 좋을 것 같네요! 수정하겠습니다!


} No newline at end of file
// 단건 매핑: UserQueryDto -> RecentWriter
UserFollowingRecentWritersResponse.RecentWriter toRecentWriter(UserQueryDto dto);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

오 이거는 미리 만들어두신건가여??

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

넵 맞습니다! map struct 가 사용할 매핑 메서드를 미리 인터페이스에 정의해놓았습니다

@RequiredArgsConstructor
public class UserShowFollowingRecentWritersService implements UserShowFollowingRecentWritersUseCase {

private static final int SIZE = 10;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

p2: 페이징 처리 필요하지않으면 사용하지 않으니 빼셔도 좋을듯합니다!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이 부분은 아마 기본 사이즈라도 유지보수하기 쉽게 서비스 로직에서 LIMIT도 넘겨주기로 통일했던 것 같습니다!

private final UserQueryPort userQueryPort;
private final UserQueryMapper userQueryMapper;

@Override
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

p2: 트랜잭션 어노테이션 누락된것같습니다!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

엇 서비스에서 도메인 조회가 없어서 트랜잭션을 추가하진 않았는데 팀 컨벤션 차원으로 추가해보겟습니다!

}

@Test
@DisplayName("유저가 가장 최근에 작성한 피드의 작성 시각(= createdAt)을 기준으로 정렬하여 반환된다.")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

buzz0331
buzz0331 previously approved these changes Aug 13, 2025
Copy link
Copy Markdown
Contributor

@buzz0331 buzz0331 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~ 깔끔하네욥

Comment on lines +181 to +191
.where(
following.userJpaEntity.userId.eq(userId) // 내가 팔로잉 하는 사람
.and(writer.status.eq(StatusType.ACTIVE)) // 그 중 아직 회원인 사람
.and(feed.status.eq(StatusType.ACTIVE)) // 그 중 삭제 X & 공개피드를 작성한 사람
.and(feed.isPublic.isTrue())
)
.groupBy( // 그룹핑
writer.userId,
writer.nickname,
alias.imageUrl
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

굿굿 꼼꼼하네욥 👍🏻

Comment on lines +19 to +23
List<UserFollowingRecentWritersResponse.RecentWriter> toRecentWriterList(List<UserQueryDto> dtos);
// 래핑: List<UserQueryDto> -> UserFollowingRecentWritersResponse
default UserFollowingRecentWritersResponse toRecentWriterResponses(List<UserQueryDto> dtos) {
return new UserFollowingRecentWritersResponse(toRecentWriterList(dtos));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

오호 dto 생성 책임도 mapper가 갖도록 했네요! 좋습니다! 서비스 로직이 가벼워질 것 같아요

Comment on lines +21 to +24
// Assert.notNull(aliasName, "aliasName must not be null");
// Assert.notNull(aliasColor, "aliasColor must not be null");
// Assert.notNull(followerCount, "followerCount must not be null"); // 내 팔로잉 목록 조회에서는 필요 x
Assert.notNull(createdAt, "createdAt must not be null");
// Assert.notNull(createdAt, "createdAt must not be null");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍🏻

@RequiredArgsConstructor
public class UserShowFollowingRecentWritersService implements UserShowFollowingRecentWritersUseCase {

private static final int SIZE = 10;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이 부분은 아마 기본 사이즈라도 유지보수하기 쉽게 서비스 로직에서 LIMIT도 넘겨주기로 통일했던 것 같습니다!

Copy link
Copy Markdown

@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

♻️ Duplicate comments (1)
src/main/java/konkuk/thip/user/application/service/UserShowFollowingRecentWritersService.java (1)

20-21: 조회용 트랜잭션(readOnly = true) 적용이 적절합니다 — 이전 리뷰 피드백 반영 완료.

읽기 전용 쿼리 경로에 @transactional(readOnly = true) 적용으로 불필요한 flush 방지 및 성능에 유리합니다.

🧹 Nitpick comments (2)
src/main/java/konkuk/thip/user/application/service/UserShowFollowingRecentWritersService.java (2)

15-15: SIZE 상수의 의미 명확화 및 관리 전략 개선 제안.

  • 네이밍: 도메인 의미가 드러나도록 LIMIT/…_LIMIT 형태가 가독성에 유리합니다.
  • 관리 전략: 팀 컨벤션(서비스에서 LIMIT 전달)에는 부합하지만, 운영 중 조정 가능성을 고려하면 프로퍼티로 외부화하는 것도 유지보수에 유리합니다.

우선 네이밍 개선만 적용하려면 아래처럼 제안합니다.

-    private static final int SIZE = 10;
+    private static final int RECENT_WRITERS_LIMIT = 10;
-        return userQueryMapper.toRecentWriterResponses(userQueryPort.findRecentFeedWritersOfMyFollowings(userId, SIZE));
+        return userQueryMapper.toRecentWriterResponses(userQueryPort.findRecentFeedWritersOfMyFollowings(userId, RECENT_WRITERS_LIMIT));

프로퍼티 외부화(선택 사항):

  • 장점: 재배포 없이 튜닝 가능, 환경별 상이한 설정 용이.
  • 구현 스케치:
// import 추가
import org.springframework.beans.factory.annotation.Value;

// 필드 추가
@Value("${thip.user.following.recent-writers-limit:10}")
private int recentWritersLimit;

// 사용처
return userQueryMapper.toRecentWriterResponses(
    userQueryPort.findRecentFeedWritersOfMyFollowings(userId, recentWritersLimit)
);

Also applies to: 22-22


21-23: 사전조건 체크 추가 권장: userId null 방지.

서비스 경계에서 명시적으로 방어하면 원인 파악이 쉬워집니다.

     @Override
     @Transactional(readOnly = true)
     public UserFollowingRecentWritersResponse showMyFollowingRecentWriters(Long userId) {
+        java.util.Objects.requireNonNull(userId, "userId must not be null");
         return userQueryMapper.toRecentWriterResponses(userQueryPort.findRecentFeedWritersOfMyFollowings(userId, SIZE));
     }

추가로 상위 Port 인터페이스의 파라미터에도 @NotNull을 선언하면 계약 수준에서 안전성이 강화됩니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 964f08c and bc773a6.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepositoryImpl.java (2 hunks)
  • src/main/java/konkuk/thip/user/application/service/UserShowFollowingRecentWritersService.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepositoryImpl.java
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.258Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
⏰ 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/main/java/konkuk/thip/user/application/service/UserShowFollowingRecentWritersService.java (2)

11-13: 서비스 책임 분리가 명확합니다.

UseCase 구현체로서 포트/매퍼만 주입받아 오케스트레이션에 집중한 구조가 좋습니다.


21-23: 포트 계약 확인 요청: null 대신 빈 컬렉션을 반환하도록 보장되어 있나요?

매퍼가 null을 입력받지 않는다는 전제라면, UserQueryPort.findRecentFeedWritersOfMyFollowings는 빈 컬렉션을 반환하는 계약을 명시하는 것이 안전합니다. 해당 계약이 테스트로도 검증되어 있는지 확인 부탁드립니다. 또한 정렬 순서(최신 공개 피드 createdAt 기준)가 응답까지 그대로 유지되는지(매퍼에서 순서가 깨지지 않는지)도 함께 확인해 주세요.

Copy link
Copy Markdown
Contributor

@buzz0331 buzz0331 left a comment

Choose a reason for hiding this comment

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

굿굿~

@seongjunnoh seongjunnoh merged commit 910f78f into develop Aug 13, 2025
4 checks passed
@seongjunnoh seongjunnoh deleted the feat/#196-users-show-recent-feed-writers-of-my-followings branch August 13, 2025 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-248] [feat] 전체 피드 조회 상단, 내 띱 목록 조회 api 개발

3 participants