Skip to content

[feat] 모집중인 방 상세보기 api 개발#67

Merged
seongjunnoh merged 11 commits intodevelopfrom
feat/#64-room-recruiting-detail
Jul 13, 2025
Merged

[feat] 모집중인 방 상세보기 api 개발#67
seongjunnoh merged 11 commits intodevelopfrom
feat/#64-room-recruiting-detail

Conversation

@seongjunnoh
Copy link
Collaborator

@seongjunnoh seongjunnoh commented Jul 12, 2025

#️⃣ 연관된 이슈

closes #64

📝 작업 내용

모집중인 방 상세보기 api를 개발하였습니다

  • api 주요 로직

    1. Room, Book 도메인 조회
    2. Room 과 연관된 UserRoom list 조회 -> 일급 컬렉션 생성 후 필요한 로직 수행
    3. 다른 모임방 추천을 위해 QueryDSL 활용
  • 수정된 기획(= 방 대표 이미지 도입 = 카테고리의 대표 이미지) 으로 인해 Category 에 imageUrl 필드가 추가되어야 합니다.

  • 다른 분들과의 충돌을 우려해 아직 수정하지는 않았습니다. 개발중인 브랜치들 모두 머지되면 제가 수정하겠습니다

📸 스크린샷

💬 리뷰 요구사항

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

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

Summary by CodeRabbit

  • 신규 기능

    • 모집 중인 방의 상세 정보를 조회할 수 있는 새로운 API 엔드포인트가 추가되었습니다.
    • 추천 모집 방 목록(카테고리별, 시작일 순 정렬, 최대 5개)이 상세 정보에 함께 제공됩니다.
    • 모집 방 상세 정보에 호스트 여부, 참여 여부, 방 정보, 도서 정보, 멤버/모집 인원 등이 포함됩니다.
    • 방 카테고리에 이미지 URL 속성이 추가되어 관련 UI 표현이 강화되었습니다.
  • 버그 수정

    • 날짜 및 시간 포맷팅 시 불필요한 공백이 제거되었습니다.
  • 테스트

    • 모집 방 상세 조회 API와 추천 방 목록 기능에 대한 통합 테스트 및 도메인 단위 테스트가 추가되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Jul 12, 2025

"""

Walkthrough

모집 중인 방 상세보기 API 및 관련 비즈니스 로직이 새롭게 도입되었습니다. 방 상세 정보, 참가자, 추천 방 목록 조회 기능이 추가되었으며, 이를 위한 도메인, 서비스, 포트, 어댑터, 컨트롤러, DTO, 테스트 코드가 일괄적으로 구현되었습니다. 날짜 유틸리티도 확장되었습니다.

Changes

파일/경로 요약 변경 내용 요약
.../common/util/DateUtil.java 날짜 포매터 추가, formatDate 메서드 추가, formatAfterTime 반환값 트림
.../room/adapter/in/web/RoomQueryController.java 모집중인 방 상세 조회 API 엔드포인트 및 의존성 추가
.../room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java 모집중인 방 상세 및 추천방 응답용 레코드(Record) 클래스 신설
.../room/adapter/out/persistence/RoomQueryPersistenceAdapter.java 카테고리별 추천 모집중 방 조회 메서드 추가
.../room/adapter/out/persistence/RoomQueryRepository.java 추천 모집중 방 조회용 메서드 시그니처 추가
.../room/adapter/out/persistence/RoomQueryRepositoryImpl.java 추천 모집중 방 실제 DB 조회 쿼리 및 매핑 구현
.../room/application/port/in/RoomShowRecruitingDetailViewUseCase.java 모집중인 방 상세 조회 유스케이스 인터페이스 신설
.../room/application/port/out/RoomQueryPort.java 추천 모집중 방 조회용 포트 메서드 추가
.../room/application/service/RoomShowRecruitingDetailViewService.java 모집중인 방 상세 조회 서비스 구현
.../user/domain/RoomParticipants.java 참가자 컬렉션 도메인 클래스 신설, 멤버수/참여여부/호스트여부 판단 메서드 추가
.../room/adapter/out/jpa/CategoryJpaEntity.java 카테고리 엔티티에 imageUrl 필드 추가
.../room/domain/Category.java 카테고리 enum에 imageUrl 필드 및 생성자 추가
.../test/java/konkuk/thip/common/util/TestEntityFactory.java 테스트용 카테고리 엔티티에 imageUrl 속성 추가
.../test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java 테스트용 카테고리 엔티티 생성 시 imageUrl 속성 추가
.../room/adapter/in/web/RoomRecruitingDetailViewApiTest.java 모집중인 방 상세 조회 API 통합 테스트 추가
.../user/domain/RoomParticipantsTest.java RoomParticipants 도메인 단위 테스트 추가

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Controller
    participant Service
    participant RoomPort
    participant BookPort
    participant UserRoomPort
    participant QueryPort

    User->>Controller: GET /rooms/{roomId}/recruiting (userId)
    Controller->>Service: getRecruitingRoomDetailView(userId, roomId)
    Service->>RoomPort: findById(roomId)
    Service->>BookPort: findById(bookId)
    Service->>UserRoomPort: findAllByRoomId(roomId)
    Service->>QueryPort: findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(room, 5)
    Service-->>Controller: RoomRecruitingDetailViewResponse
    Controller-->>User: BaseResponse<RoomRecruitingDetailViewResponse>
Loading

Assessment against linked issues

Objective Addressed Explanation
모집중인 방 상세보기 API 개발 및 데이터 제공 (#64)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes detected.

Possibly related PRs

Suggested labels

🧸 현준

Suggested reviewers

  • buzz0331

Poem

🐰
새로운 방의 문이 열렸네,
모집 상세, 추천방까지 한눈에!
참가자도 호스트도
모두 확인할 수 있죠.
날짜도 예쁘게 포맷해주니,
토끼도 춤추는 오늘의 코드 파티!
🎉
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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.

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 (5)
src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java (1)

5-33: response DTO의 구조 개선을 고려해보세요.

현재 구조는 기능적으로 정확하지만, 24개의 필드를 가진 단일 record는 복잡도가 높습니다. 관련 필드들을 그룹화하여 가독성을 개선할 수 있습니다.

예시 구조 개선안:

 public record RoomRecruitingDetailViewResponse(
         boolean isHost,
         boolean isJoining,
-        Long roomId,
-        String roomName,
-        String roomImageUrl,
-        boolean isPublic,
-        String progressStartDate,
-        String progressEndDate,
-        String recruitEndDate,
-        String category,
-        String roomDescription,
-        int memberCount,
-        int recruitCount,
-        String isbn,
-        String bookImageUrl,
-        String bookTitle,
-        String authorName,
-        String bookDescription,
+        RoomInfo roomInfo,
+        BookInfo bookInfo,
         List<RecommendRoom> recommendRooms
 ) {
+    public record RoomInfo(
+            Long roomId,
+            String roomName,
+            String roomImageUrl,
+            boolean isPublic,
+            String progressStartDate,
+            String progressEndDate,
+            String recruitEndDate,
+            String category,
+            String roomDescription,
+            int memberCount,
+            int recruitCount
+    ) {}
+    
+    public record BookInfo(
+            String isbn,
+            String bookImageUrl,
+            String bookTitle,
+            String authorName,
+            String bookDescription
+    ) {}
src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java (2)

24-24: 상수 정의 위치 개선 제안

RECOMMEND_ROOM_COUNT 상수를 클래스 최상단에 정의하는 것보다는 설정 파일이나 별도의 상수 클래스로 분리하는 것을 고려해보세요. 이렇게 하면 비즈니스 요구사항 변경 시 더 유연하게 대응할 수 있습니다.

-    private final static int RECOMMEND_ROOM_COUNT = 5;

application.yml에 추가:

thip:
  room:
    recommend-count: 5

그리고 @value 어노테이션으로 주입:

@Value("${thip.room.recommend-count:5}")
private final int recommendRoomCount;

61-61: 미구현 기능 추적을 위한 TODO 주석 추가 제안

현재 null로 하드코딩된 roomImageUrl에 대해 더 명확한 TODO 주석을 추가하는 것을 제안합니다.

-                null, // roomImageUrl 미구현
+                null, // TODO: #이슈번호 - Category 엔티티의 imageUrl 필드 추가 후 구현 예정
src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (2)

72-99: 테스트 헬퍼 메서드 리팩토링 제안

saveScienceRoomsaveLiteratureRoom 메서드 간에 코드 중복이 많습니다. 공통 로직을 추출하여 중복을 제거하는 것을 제안합니다.

+    private RoomJpaEntity saveRoom(String bookTitle, String isbn, String roomName, 
+                                   LocalDate startDate, int recruitCount, boolean isScience) {
+        AliasJpaEntity alias = aliasJpaRepository.save(
+            isScience ? TestEntityFactory.createScienceAlias() : TestEntityFactory.createLiteratureAlias());
+        
+        BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder()
+                .title(bookTitle)
+                .isbn(isbn)
+                .authorName("한강")
+                .bestSeller(false)
+                .publisher("문학동네")
+                .imageUrl("https://image1.jpg")
+                .pageCount(300)
+                .description("한강의 소설")
+                .build());
+
+        CategoryJpaEntity category = categoryJpaRepository.save(
+            isScience ? TestEntityFactory.createScienceCategory(alias) 
+                      : TestEntityFactory.createLiteratureCategory(alias));
+
+        return roomJpaRepository.save(RoomJpaEntity.builder()
+                .title(roomName)
+                .description("한강 작품 읽기 모임")
+                .isPublic(true)
+                .roomPercentage(0.0)
+                .startDate(startDate)
+                .endDate(LocalDate.now().plusDays(30))
+                .recruitCount(recruitCount)
+                .bookJpaEntity(book)
+                .categoryJpaEntity(category)
+                .build());
+    }

-    private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
+    private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
+        return saveRoom(bookTitle, isbn, roomName, startDate, recruitCount, true);
-        // existing implementation
    }

-    private RoomJpaEntity saveLiteratureRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
+    private RoomJpaEntity saveLiteratureRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
+        return saveRoom(bookTitle, isbn, roomName, startDate, recruitCount, false);
-        // existing implementation
    }

Also applies to: 101-128


211-217: 호스트 역할 변경 로직 개선 제안

호스트 테스트를 위한 UserRoomRole 변경 로직이 복잡합니다. 테스트의 가독성을 위해 별도 헬퍼 메서드로 추출하는 것을 제안합니다.

+    private UserJpaEntity changeUserToHost(RoomJpaEntity room, int userIndex) {
+        UserRoomJpaEntity targetMember = userRoomJpaRepository.findAllByRoomJpaEntity_RoomId(room.getRoomId()).get(userIndex);
+        userRoomJpaRepository.delete(targetMember);
+        UserRoomJpaEntity roomCreator = userRoomJpaRepository.save(UserRoomJpaEntity.builder()
+                .userJpaEntity(targetMember.getUserJpaEntity())
+                .roomJpaEntity(targetMember.getRoomJpaEntity())
+                .userRoomRole(UserRoomRole.HOST)
+                .build());
+        return roomCreator.getUserJpaEntity();
+    }

        //given
        RoomJpaEntity targetRoom = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), 10);
        saveUsersToRoom(targetRoom, 4);
-        UserRoomJpaEntity firstMember = userRoomJpaRepository.findAllByRoomJpaEntity_RoomId(targetRoom.getRoomId()).get(1);
-        userRoomJpaRepository.delete(firstMember);
-        UserRoomJpaEntity roomCreator = userRoomJpaRepository.save(UserRoomJpaEntity.builder()
-                .userJpaEntity(firstMember.getUserJpaEntity())
-                .roomJpaEntity(firstMember.getRoomJpaEntity())
-                .userRoomRole(UserRoomRole.HOST)
-                .build());      // firstMember 을 MEMBER -> HOST 로 수정
+        UserJpaEntity hostUser = changeUserToHost(targetRoom, 1);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f9ed731 and a52949d.

📒 Files selected for processing (12)
  • src/main/java/konkuk/thip/common/util/DateUtil.java (2 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (3 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (2 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java (2 hunks)
  • src/main/java/konkuk/thip/room/application/port/in/RoomShowRecruitingDetailViewUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java (1 hunks)
  • src/main/java/konkuk/thip/user/domain/RoomParticipants.java (1 hunks)
  • src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (1 hunks)
  • src/test/java/konkuk/thip/user/domain/RoomParticipantsTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (1)
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.771Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
🧬 Code Graph Analysis (1)
src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (16-115)
⏰ 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 (20)
src/main/java/konkuk/thip/common/util/DateUtil.java (3)

8-8: 새로운 DateTimeFormatter 추가 승인

새로운 DATE_FORMATTER 상수 추가가 적절합니다. 일관된 날짜 포맷팅을 위한 좋은 접근입니다.

Also applies to: 13-13


36-36: formatAfterTime 메서드 개선 승인

trailing space 제거로 더 깔끔한 문자열 반환이 가능해졌습니다.

Also applies to: 41-41, 45-46


48-50: 새로운 formatDate 메서드 승인

간단하고 명확한 날짜 포맷팅 유틸리티 메서드입니다. DATE_FORMATTER를 재사용하는 것이 좋습니다.

src/main/java/konkuk/thip/room/application/port/in/RoomShowRecruitingDetailViewUseCase.java (1)

1-8: Use Case 인터페이스 설계 승인

헥사고날 아키텍처 패턴을 잘 따르고 있습니다. 메서드명이 명확하고 파라미터와 반환 타입이 적절합니다.

src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java (1)

3-6: CQRS 패턴 준수한 QueryPort 메서드 추가 승인

프로젝트의 CQRS 컨벤션을 잘 따르고 있습니다. QueryPort에 response 데이터를 조회하는 메서드가 적절히 추가되었습니다. 메서드명도 의도를 명확히 표현하고 있습니다.

Also applies to: 11-11, 18-18

src/test/java/konkuk/thip/user/domain/RoomParticipantsTest.java (4)

13-22: 테스트 헬퍼 메서드 구현 승인

createUserRoom 헬퍼 메서드가 테스트 데이터 생성을 일관되게 처리하고 있어 좋습니다. 코드 중복을 줄이고 가독성을 향상시킵니다.


24-37: 멤버 수 계산 테스트 승인

멤버 카운트 계산 로직에 대한 테스트가 적절합니다. Given-When-Then 패턴을 잘 따르고 있습니다.


39-49: 방 참가 여부 확인 테스트 승인

참가 중인 유저와 참가하지 않은 유저에 대한 검증을 모두 포함하여 테스트 커버리지가 좋습니다.


51-63: 호스트 권한 확인 테스트 승인

HOST와 MEMBER 역할을 구분하는 로직과 존재하지 않는 유저에 대한 검증까지 포함하여 엣지 케이스를 잘 다루고 있습니다.

src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java (1)

3-3: Repository 메서드 추가 승인

새로운 추천 방 조회 메서드가 적절히 추가되었습니다. 메서드명이 의도를 명확히 표현하고 있으며, 파라미터와 반환 타입이 적절합니다. 기존 코드를 변경하지 않고 순수하게 추가만 된 점도 좋습니다.

Also applies to: 8-8, 14-14

src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (1)

33-36: LGTM! 깔끔한 어댑터 구현입니다.

새로운 추천 방 조회 메서드가 적절하게 구현되었습니다. 단순히 repository로 위임하는 깔끔한 어댑터 패턴을 따르고 있으며, CQRS 컨벤션에도 부합합니다.

src/main/java/konkuk/thip/user/domain/RoomParticipants.java (1)

9-36: LGTM! 훌륭한 일급 컬렉션 구현입니다.

RoomParticipants 클래스가 도메인 로직을 잘 캡슐화하고 있습니다:

  • 일급 컬렉션 패턴의 올바른 적용
  • 명확한 메서드명과 단일 책임 원칙 준수
  • Stream API를 활용한 깔끔한 구현
  • 적절한 한국어 주석으로 코드 의도 명시

도메인 레이어에서 비즈니스 로직을 관리하는 좋은 예시입니다.

src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java (1)

143-169: memberCount.intValue() 캐스팅 안전성 확인 완료
RoomCreateRequest에 @Max(30)으로 모집 인원을 제한하고 있어, 추천 방의 실제 참여자 수도 최대 30명으로 보장됩니다. 따라서 Long→int 캐스팅에서 overflow 위험이 없습니다.

src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (2)

23-24: 의존성 주입이 적절하게 구성되었습니다.

새로운 UseCase들이 올바르게 주입되어 있으며, 필드 순서도 일관성 있게 배치되었습니다.


44-49: LGTM! REST API 설계가 깔끔합니다.

새로운 엔드포인트가 잘 구현되었습니다:

  • RESTful한 URL 설계 (/rooms/{roomId}/recruiting)
  • @userid 어노테이션으로 인증된 사용자 정보 주입
  • 일관된 BaseResponse 래핑
  • 명확한 메서드명과 파라미터 구조

컨트롤러 레이어의 책임 분리가 적절하게 이루어졌습니다.

src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java (2)

33-47: 비즈니스 로직 구현 승인

서비스의 핵심 비즈니스 로직이 명확하게 단계별로 구현되어 있습니다. Room과 Book 조회, RoomParticipants 일급 컬렉션 생성, 추천 방 조회의 흐름이 잘 정리되어 있고, 적절한 도메인 객체 위임이 이루어져 있습니다.


35-36: CQRS 포트 사용 컨벤션 일치 확인
RoomCommandPort.findById 및 BookCommandPort.findById 호출은 CommandPort의 findByXXX 규칙에 부합하며, QueryPort에서는 조회 API 전용 메서드(findOtherRecruitingRoomsByCategoryOrderByStartDateAsc 등)를 제공하고 있어 CQRS 컨벤션과 일치합니다. 해당 코드에 수정이 필요 없습니다.

src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (3)

158-203: 테스트 시나리오 구현 품질 양호

첫 번째 테스트 케이스가 API의 핵심 기능을 잘 검증하고 있습니다:

  • 사용자 권한 상태 (isHost, isJoining) 확인
  • 방 정보와 책 정보 정확성 검증
  • 추천 방의 개수와 정렬 순서 확인 (모집 마감 임박 순)

각 assertion이 명확하고 비즈니스 요구사항을 잘 반영하고 있습니다.


258-308: 최대 추천 개수 제한 테스트 검증

추천 방이 5개로 제한되는 시나리오를 잘 테스트하고 있습니다. 6개의 추천 가능한 방을 생성하고 실제로 5개만 반환되는지, 그리고 정렬 순서가 올바른지 확인하는 로직이 적절합니다.


178-179: 불필요한 도메인 검증 적용 제외
RoomJpaEntity 빌더를 통해 직접 JPA 엔티티를 저장하기 때문에 도메인 레벨의 startDate > now() 검증이 적용되지 않습니다. 이 테스트는 만료된 방이 조회되지 않는 쿼리 필터링 로직을 검증하기 위한 것으로, 리플렉션이나 별도 테스트 전용 팩토리 메서드를 도입할 필요가 없습니다.

Likely an incorrect or invalid review comment.

buzz0331
buzz0331 previously approved these changes Jul 12, 2025
Copy link
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 +8 to +36

@Getter
@RequiredArgsConstructor
public class RoomParticipants {
/**
* 특정 Room 과 연관된 UserRoom 들을 모은 일급 컬렉션
*/

private final List<UserRoom> participants;

public static RoomParticipants from(List<UserRoom> participants) {
return new RoomParticipants(participants);
}

public int calculateMemberCount() {
return participants.size();
}

public boolean isJoiningToRoom(Long userId) {
return participants.stream()
.anyMatch(userRoom -> userRoom.getUserId().equals(userId));
}

public boolean isHostOfRoom(Long userId) {
return participants.stream()
.filter(userRoom -> userRoom.getUserId().equals(userId))
.anyMatch(userRoom -> userRoom.getUserRoomRole().equals(UserRoomRole.HOST.getType()));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

p3: 특정 Room과 연관된 UserRoom들이니까 Room 도메인에 속하지 않을까 싶은데 User 패키지 아래 두신 이유가 따로있나욥

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

딱히 이유 없습니다! 그냥 UserRoom 의 List를 포함하는 일급컬렉션이어서 UserRoom 과 같은 패키지에 위치시켰습니다!

Comment on lines 160 to 168
return tuples.stream()
.map(t -> new RoomRecruitingDetailViewResponse.RecommendRoom(
null, // roomImageUrl은 추후 구현
t.get(room.title),
t.get(memberCountExpr).intValue(),
t.get(room.recruitCount),
DateUtil.formatAfterTime(t.get(room.startDate))
))
.toList();
Copy link
Contributor

Choose a reason for hiding this comment

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

p3: 각 필드를 명확히 알 수 있게 빌더 패턴 적용하면 좋을 것 같습니다!!

Comment on lines 49 to 77
private RoomRecruitingDetailViewResponse buildResponse(
Long userId,
Room room,
Book book,
RoomParticipants participants,
List<RoomRecruitingDetailViewResponse.RecommendRoom> recommendRooms
) {
return new RoomRecruitingDetailViewResponse(
participants.isHostOfRoom(userId),
participants.isJoiningToRoom(userId),
room.getId(),
room.getTitle(),
null, // roomImageUrl 미구현
room.isPublic(),
DateUtil.formatDate(room.getStartDate()),
DateUtil.formatDate(room.getEndDate()),
DateUtil.formatAfterTime(room.getStartDate()),
room.getCategory().getValue(),
room.getDescription(),
participants.calculateMemberCount(),
room.getRecruitCount(),
book.getIsbn(),
book.getImageUrl(),
book.getTitle(),
book.getAuthorName(),
book.getDescription(),
recommendRooms
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

p3: 이 메서드는 RoomRecruitingDetailViewResponse의 정적 메서드로 만들어서 생성 책임을 dto가 갖도록 하면 코드가 깔끔해질 것 같아요!

엇 혹시 도메인 규칙을 서비스 레벨에서 사용하고 싶으셔서 이렇게 작성하신 거라면 그대로 놔둬도 될 것 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

response dto 에 정적 팩토리 메서드 구현할 경우, 서비스 레벨에서 메서드 호출할 때 넘겨줘야하는 파라미터가 너무 많아지기도 하고, response dto 내부에서 또 builder를 정의해주어야 해서 service의 private 메서드로 분리하긴 했습니다

Comment on lines +57 to +58
participants.isHostOfRoom(userId),
participants.isJoiningToRoom(userId),
Copy link
Contributor

@buzz0331 buzz0331 Jul 12, 2025

Choose a reason for hiding this comment

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

p3: 두 메서드의 조건이 중복되는 것 같은데 isJoiningToRoom(user)를 먼저 호출하여 true일 경우에만 isHostOfRoom(userId)을 호출하는거 어떨까요?

단순 의견이라 꼭 적용할 필요는 없습니다!

Copy link
Collaborator Author

@seongjunnoh seongjunnoh Jul 13, 2025

Choose a reason for hiding this comment

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

response를 구성할때 if 분기처리를 통해서 isJoining 이 true인 경우에만 isHost 메서드를 호출하는 흐름을 말하시는거 같은데, response를 구성할때 분기처리 로직을 추가하는거 보다, 단순히 메서드를 호출하는게 더 가독성이 있지않나 싶어서 이렇게 구현했긴합니다!

hd0rable
hd0rable previously approved these changes Jul 13, 2025
Copy link
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.

수고하셨습니다~!!

@RequiredArgsConstructor
public class RoomParticipants {
/**
* 특정 Room 과 연관된 UserRoom 들을 모은 일급 컬렉션
Copy link
Member

Choose a reason for hiding this comment

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

LGTM

@seongjunnoh seongjunnoh dismissed stale reviews from hd0rable and buzz0331 via f39cdf5 July 13, 2025 13:53
@seongjunnoh
Copy link
Collaborator Author

@buzz0331 @hd0rable Category 에 imageUrl 필드 추가해놓고, ERD도 수정해놨습니다!

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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a52949d and f39cdf5.

📒 Files selected for processing (12)
  • src/main/java/konkuk/thip/common/util/DateUtil.java (2 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (3 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/jpa/CategoryJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (2 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java (2 hunks)
  • src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java (1 hunks)
  • src/main/java/konkuk/thip/room/domain/Category.java (1 hunks)
  • src/test/java/konkuk/thip/common/util/TestEntityFactory.java (2 hunks)
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java
  • src/test/java/konkuk/thip/common/util/TestEntityFactory.java
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java
  • src/main/java/konkuk/thip/common/util/DateUtil.java
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java
  • src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java
  • src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java
  • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
⏰ 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/room/adapter/out/jpa/CategoryJpaEntity.java (1)

25-26: 마이그레이션 파일 누락 여부 직접 확인 필요
위 스크립트로 SQL/YAML/XML 형식의 마이그레이션 파일에서 category 테이블 정의를 찾을 수 없었습니다. 신규 non-nullable 필드 추가 시 기존 데이터에 영향을 줄 수 있으므로,

  • 프로젝트에 Flyway/Liquibase 등 마이그레이션 스크립트가 실제 존재하는지
  • 해당 스크립트에 category 테이블 변경 내역(신규 image_url 컬럼 추가)이 포함되어 있는지

를 수동으로 검토해 주세요.
필요하다면 기본값 지정, 데이터 백필(backfill), 단계적 null 허용 등 적절한 마이그레이션 전략을 구현해 주시기 바랍니다.

src/main/java/konkuk/thip/room/domain/Category.java (1)

26-26: LGTM!

새로운 imageUrl 필드가 올바르게 선언되었습니다.

@sonarqubecloud
Copy link

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 (1)
src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (1)

212-218: UserRoom 역할 변경 로직 개선 가능

UserRoom을 삭제하고 재생성하는 대신, 기존 엔티티의 역할을 직접 수정하는 것이 더 간단합니다.

-UserRoomJpaEntity firstMember = userRoomJpaRepository.findAllByRoomJpaEntity_RoomId(targetRoom.getRoomId()).get(1);
-userRoomJpaRepository.delete(firstMember);
-UserRoomJpaEntity roomCreator = userRoomJpaRepository.save(UserRoomJpaEntity.builder()
-        .userJpaEntity(firstMember.getUserJpaEntity())
-        .roomJpaEntity(firstMember.getRoomJpaEntity())
-        .userRoomRole(UserRoomRole.HOST)
-        .build());
+UserRoomJpaEntity firstMember = userRoomJpaRepository.findAllByRoomJpaEntity_RoomId(targetRoom.getRoomId()).get(1);
+firstMember.changeRole(UserRoomRole.HOST);  // 또는 setter 사용
+UserRoomJpaEntity roomCreator = userRoomJpaRepository.save(firstMember);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f39cdf5 and 0baeb59.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/room/domain/Category.java (1 hunks)
  • src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/room/domain/Category.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (1)
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.771Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
⏰ 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/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (3)

37-37: 보안 필터 비활성화 확인 필요

@AutoConfigureMockMvc(addFilters = false)로 모든 필터를 비활성화하고 있습니다. 이는 실제 환경의 보안 검증을 우회할 수 있습니다. 테스트 목적상 필요한 경우라면 주석으로 이유를 명시하는 것이 좋겠습니다.


131-131: 모든 사용자에게 동일한 Science alias 할당 확인

모든 사용자에게 Science alias를 할당하고 있습니다. 실제 비즈니스 로직에서도 방 참가자들이 모두 동일한 관심사 alias를 가져야 하는지 확인이 필요합니다.


190-190: roomImageUrl 검증 값 일치 확인

테스트에서 검증한 "과학/IT_image" 값이
TestEntityFactory.createScienceCategory()에서 설정된 imageUrl "과학/IT_image"과 일치함을 확인했습니다.
해당 부분은 그대로 두셔도 됩니다.

Comment on lines +178 to +179
RoomJpaEntity recruit_expired_room_4 = saveScienceRoom("과학-책", "isbn6", "모집기한-지난-과학방", LocalDate.now().minusDays(1), 8);
saveUsersToRoom(recruit_expired_room_4, 6);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

도메인 규칙 위반: 과거 startDate 설정

startDate를 과거(LocalDate.now().minusDays(1))로 설정하고 있는데, Room 도메인 규칙상 startDate는 현재 날짜 이후여야 합니다. 모집 기한이 지난 방을 테스트하려면 reflection을 사용해야 합니다.

이전 학습 내용을 참고하여 reflection을 사용한 방법을 적용하거나, 다른 방법으로 모집 기한 만료 상태를 시뮬레이션해야 합니다.

🤖 Prompt for AI Agents
In
src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java
around lines 178 to 179, the test sets startDate to a past date violating the
Room domain rule that startDate must be today or later. To fix this, create the
Room entity with a valid startDate (today or future), then use reflection to
modify the startDate field to a past date to simulate an expired recruiting
period without breaking domain constraints.

Comment on lines +72 to +129
private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias());

BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder()
.title(bookTitle)
.isbn(isbn)
.authorName("한강")
.bestSeller(false)
.publisher("문학동네")
.imageUrl("https://image1.jpg")
.pageCount(300)
.description("한강의 소설")
.build());

CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createScienceCategory(alias));

return roomJpaRepository.save(RoomJpaEntity.builder()
.title(roomName)
.description("한강 작품 읽기 모임")
.isPublic(true)
.roomPercentage(0.0)
.startDate(startDate)
.endDate(LocalDate.now().plusDays(30))
.recruitCount(recruitCount)
.bookJpaEntity(book)
.categoryJpaEntity(category)
.build());
}

private RoomJpaEntity saveLiteratureRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias());

BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder()
.title(bookTitle)
.isbn(isbn)
.authorName("한강")
.bestSeller(false)
.publisher("문학동네")
.imageUrl("https://image1.jpg")
.pageCount(300)
.description("한강의 소설")
.build());

CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createLiteratureCategory(alias));

return roomJpaRepository.save(RoomJpaEntity.builder()
.title(roomName)
.description("한강 작품 읽기 모임")
.isPublic(true)
.roomPercentage(0.0)
.startDate(startDate)
.endDate(LocalDate.now().plusDays(30))
.recruitCount(recruitCount)
.bookJpaEntity(book)
.categoryJpaEntity(category)
.build());
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

헬퍼 메서드 중복 제거 필요

saveScienceRoomsaveLiteratureRoom 메서드가 거의 동일한 구조를 가지고 있습니다. 카테고리 타입을 파라미터로 받는 하나의 메서드로 통합하면 코드 중복을 제거할 수 있습니다.

-private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
-    AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias());
-    // ... 중복 코드 ...
-}
-
-private RoomJpaEntity saveLiteratureRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
-    AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias());
-    // ... 중복 코드 ...
-}
+private RoomJpaEntity saveRoom(CategoryType categoryType, String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
+    AliasJpaEntity alias = aliasJpaRepository.save(
+        categoryType == CategoryType.SCIENCE ? TestEntityFactory.createScienceAlias() : TestEntityFactory.createLiteratureAlias()
+    );
+    
+    BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder()
+            .title(bookTitle)
+            .isbn(isbn)
+            .authorName("한강")
+            .bestSeller(false)
+            .publisher("문학동네")
+            .imageUrl("https://image1.jpg")
+            .pageCount(300)
+            .description("한강의 소설")
+            .build());
+
+    CategoryJpaEntity category = categoryJpaRepository.save(
+        categoryType == CategoryType.SCIENCE ? TestEntityFactory.createScienceCategory(alias) : TestEntityFactory.createLiteratureCategory(alias)
+    );
+
+    return roomJpaRepository.save(RoomJpaEntity.builder()
+            .title(roomName)
+            .description("한강 작품 읽기 모임")
+            .isPublic(true)
+            .roomPercentage(0.0)
+            .startDate(startDate)
+            .endDate(LocalDate.now().plusDays(30))
+            .recruitCount(recruitCount)
+            .bookJpaEntity(book)
+            .categoryJpaEntity(category)
+            .build());
+}
📝 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
private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias());
BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder()
.title(bookTitle)
.isbn(isbn)
.authorName("한강")
.bestSeller(false)
.publisher("문학동네")
.imageUrl("https://image1.jpg")
.pageCount(300)
.description("한강의 소설")
.build());
CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createScienceCategory(alias));
return roomJpaRepository.save(RoomJpaEntity.builder()
.title(roomName)
.description("한강 작품 읽기 모임")
.isPublic(true)
.roomPercentage(0.0)
.startDate(startDate)
.endDate(LocalDate.now().plusDays(30))
.recruitCount(recruitCount)
.bookJpaEntity(book)
.categoryJpaEntity(category)
.build());
}
private RoomJpaEntity saveLiteratureRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias());
BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder()
.title(bookTitle)
.isbn(isbn)
.authorName("한강")
.bestSeller(false)
.publisher("문학동네")
.imageUrl("https://image1.jpg")
.pageCount(300)
.description("한강의 소설")
.build());
CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createLiteratureCategory(alias));
return roomJpaRepository.save(RoomJpaEntity.builder()
.title(roomName)
.description("한강 작품 읽기 모임")
.isPublic(true)
.roomPercentage(0.0)
.startDate(startDate)
.endDate(LocalDate.now().plusDays(30))
.recruitCount(recruitCount)
.bookJpaEntity(book)
.categoryJpaEntity(category)
.build());
}
private RoomJpaEntity saveRoom(CategoryType categoryType,
String bookTitle,
String isbn,
String roomName,
LocalDate startDate,
int recruitCount) {
AliasJpaEntity alias = aliasJpaRepository.save(
categoryType == CategoryType.SCIENCE
? TestEntityFactory.createScienceAlias()
: TestEntityFactory.createLiteratureAlias()
);
BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder()
.title(bookTitle)
.isbn(isbn)
.authorName("한강")
.bestSeller(false)
.publisher("문학동네")
.imageUrl("https://image1.jpg")
.pageCount(300)
.description("한강의 소설")
.build());
CategoryJpaEntity category = categoryJpaRepository.save(
categoryType == CategoryType.SCIENCE
? TestEntityFactory.createScienceCategory(alias)
: TestEntityFactory.createLiteratureCategory(alias)
);
return roomJpaRepository.save(RoomJpaEntity.builder()
.title(roomName)
.description("한강 작품 읽기 모임")
.isPublic(true)
.roomPercentage(0.0)
.startDate(startDate)
.endDate(LocalDate.now().plusDays(30))
.recruitCount(recruitCount)
.bookJpaEntity(book)
.categoryJpaEntity(category)
.build());
}
🤖 Prompt for AI Agents
In
src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java
between lines 72 and 129, the methods saveScienceRoom and saveLiteratureRoom
have nearly identical code except for the category type and alias creation.
Refactor by creating a single helper method that accepts parameters for the
alias and category creation functions or types, then use this method to save
rooms for different categories. This will remove duplication and centralize the
room-saving logic.

Copy link
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.

😎

@seongjunnoh seongjunnoh merged commit 78e0066 into develop Jul 13, 2025
3 checks passed
@seongjunnoh seongjunnoh deleted the feat/#64-room-recruiting-detail branch July 13, 2025 14:37
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-103] [feat] 모집중인 방 상세보기

3 participants