Skip to content

[feat] 방 검색 api 개발#59

Merged
seongjunnoh merged 7 commits into
developfrom
feat/#55-room-search
Jul 9, 2025
Merged

[feat] 방 검색 api 개발#59
seongjunnoh merged 7 commits into
developfrom
feat/#55-room-search

Conversation

@seongjunnoh

@seongjunnoh seongjunnoh commented Jul 9, 2025

Copy link
Copy Markdown
Collaborator

#️⃣ 연관된 이슈

closes #55

📝 작업 내용

방 검색 api를 개발하였습니다

<방 검색 api 플로우>

  1. controller 진입
  2. service 진입
  • category, sort requestParam 유효성 검증
  • Pageable 생성
  • Port 호출 결과를 response dto 로 구성한 후 반환
  1. QueryPort 및 QueryDsl 구현체
  • 실제 검색 로직 구현

📸 스크린샷

💬 리뷰 요구사항

다양한 테스트 케이스를 고려해 '방 검색 api' 통합 테스트 코드를 작성해보았습니다. 테스트 코드를 참고해주시면 QueryDSL 로직이 더 잘 이해되실 것 같습니다!

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

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

Summary by CodeRabbit

  • 신규 기능

    • 방 검색 API가 추가되어 키워드, 카테고리, 정렬, 페이지별로 방을 조회할 수 있습니다.
    • 방 검색 결과는 모집 마감일, 인기순(멤버 수), 추천(개발 중) 등 다양한 정렬 옵션과 함께 제공됩니다.
    • 방 검색 결과에 모집 마감까지 남은 시간, 카테고리, 멤버 수 등 상세 정보가 포함됩니다.
  • 버그 수정

    • 잘못된 정렬 조건 입력 시 에러 메시지가 제공됩니다.
  • 테스트

    • 방 검색 API의 다양한 필터, 정렬, 페이지네이션 동작을 검증하는 통합 테스트가 추가되었습니다.

@seongjunnoh seongjunnoh linked an issue Jul 9, 2025 that may be closed by this pull request
2 tasks
@coderabbitai

coderabbitai Bot commented Jul 9, 2025

Copy link
Copy Markdown

"""

Walkthrough

방 검색 API가 새롭게 도입되었습니다. 이를 위해 컨트롤러, 서비스, 포트, 어댑터, 레포지토리, 도메인 객체, 예외 코드, 테스트 등 전 계층에 걸쳐 방 검색 기능을 위한 코드가 추가 및 수정되었습니다. 통합 테스트도 함께 작성되었습니다.

Changes

파일/경로 그룹 변경 요약
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java 방 검색 정렬 조건 오류 코드 추가
src/main/java/konkuk/thip/common/util/DateUtilsss.java 날짜 차이 포맷 유틸 클래스 신설
src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java 방 검색 API 엔드포인트 및 메서드 추가
src/main/java/konkuk/thip/room/adapter/in/web/response/RoomSearchResponse.java 방 검색 응답 및 결과 레코드 신설
src/main/java/konkuk/thip/room/adapter/out/persistence/CategoryName.java 카테고리명 enum 신설 및 문자열 매핑 메서드 추가
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomJpaRepository.java RoomQueryRepository 인터페이스 상속 추가
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java searchRoom 메서드 추가, RoomQueryPort 구현
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java 방 검색용 쿼리 레포지토리 인터페이스 신설
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java 방 검색 쿼리 구현(QueryDSL, 페이징, 정렬, DTO 매핑 포함)
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomSearchSortParam.java 방 검색 정렬 enum 신설 및 문자열 매핑 메서드 추가
src/main/java/konkuk/thip/room/application/port/in/RoomSearchUseCase.java 방 검색 유스케이스 인터페이스 신설
src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java searchRoom 메서드 추가
src/main/java/konkuk/thip/room/application/service/RoomSearchService.java 방 검색 서비스 구현(입력 검증, 정렬, 페이징, 예외처리 등)
src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java 방 검색 API 통합 테스트 신설(여러 케이스별 검증)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as RoomQueryController
    participant Service as RoomSearchService
    participant Port as RoomQueryPort
    participant Adapter as RoomQueryPersistenceAdapter
    participant Repo as RoomQueryRepositoryImpl

    Client->>Controller: GET /rooms/search?keyword&category&sort&page
    Controller->>Service: searchRoom(keyword, category, sort, page)
    Service->>Port: searchRoom(keyword, category, pageable)
    Port->>Adapter: searchRoom(keyword, category, pageable)
    Adapter->>Repo: searchRoom(keyword, category, pageable)
    Repo-->>Adapter: Page<RoomSearchResult>
    Adapter-->>Port: Page<RoomSearchResult>
    Port-->>Service: Page<RoomSearchResult>
    Service-->>Controller: RoomSearchResponse
    Controller-->>Client: BaseResponse<RoomSearchResponse>
Loading

Assessment against linked issues

Objective Addressed Explanation
방 검색 API 개발 (#55)
방 검색 API 정렬, 페이징, 카테고리/키워드 필터링 (#55)
방 검색 API 테스트 케이스 작성 및 검증 (#55)

Suggested labels

🛠️ feat, 🧸 현준

Suggested reviewers

  • buzz0331

Poem

🐇
방을 찾아 hop hop hop,
키워드와 정렬로 쏙쏙쏙!
카테고리 따라 페이지를 넘어,
토끼는 방을 금방 찾아내네.
테스트도 통과, 코드도 반짝!
새로운 기능에 귀가 쫑긋,
오늘도 개발은 즐겁다!
🏠✨
"""


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 50c25fc and 914f7fe.

📒 Files selected for processing (1)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.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
✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (8)
src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (1)

19-19: 필드 순서를 일관되게 유지하세요.

새로운 의존성 필드가 기존 필드보다 먼저 선언되었습니다. 코드의 일관성을 위해 필드 순서를 정리해주세요.

+    private final RoomVerifyPasswordUseCase roomVerifyPasswordUseCase;
     private final RoomSearchUseCase roomSearchUseCase;
-    private final RoomVerifyPasswordUseCase roomVerifyPasswordUseCase;
src/main/java/konkuk/thip/common/util/DateUtilsss.java (1)

9-30: 로직은 올바르지만 매직 스트링을 개선해보세요.

날짜 계산 로직은 올바르게 구현되어 있습니다. 하지만 "??"와 같은 매직 스트링 대신 의미있는 상수를 사용하는 것을 고려해보세요.

+    private static final String EXPIRED_TIME_DISPLAY = "마감";
+    
     public static String formatAfterTime(LocalDate date) {
         LocalDateTime now = LocalDateTime.now();
         LocalDateTime dateTime = date.atStartOfDay();
         Duration d = Duration.between(now, dateTime);
 
         if (d.isNegative() || d.isZero()) {
-            return "??";
+            return EXPIRED_TIME_DISPLAY;
         }
src/main/java/konkuk/thip/room/adapter/out/persistence/CategoryName.java (1)

24-31: 예외 메시지를 더 명확하게 개선하세요.

from 메서드의 로직은 올바르지만 예외 메시지가 불명확합니다.

         .orElseThrow(
-                () -> new IllegalArgumentException("현재 카테고리 이름 : " + value)
+                () -> new IllegalArgumentException("유효하지 않은 카테고리 이름입니다: " + value)
         );
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomSearchSortParam.java (1)

18-26: 예외 메시지를 더 명확하게 개선하세요.

from 메서드의 로직은 올바르지만 예외 메시지가 불명확합니다.

         .orElseThrow(
-                () -> new IllegalArgumentException("현재 정렬 조건 param : " + value)
+                () -> new IllegalArgumentException("유효하지 않은 정렬 조건입니다: " + value)
         );
src/main/java/konkuk/thip/room/application/service/RoomSearchService.java (1)

77-80: 추천 로직 구현 추적이 필요합니다.

TODO 주석으로 추후 구현이 필요함을 명시했습니다. 현재는 Sort.unsorted()로 안전하게 처리되고 있습니다.

추천 로직 구현을 위한 이슈를 생성하시겠습니까?

src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java (1)

71-137: 테스트 헬퍼 메서드에 중복 코드가 있습니다.

saveRoomsaveUsersToRoom 메서드 모두에서 동일한 AliasJpaEntity를 생성하고 있습니다. 별도의 헬퍼 메서드로 추출하면 중복을 제거할 수 있습니다.

Alias 생성을 별도 메서드로 추출:

+ private AliasJpaEntity createTestAlias() {
+     return aliasJpaRepository.save(AliasJpaEntity.builder()
+             .value("소설-칭호")
+             .color("blue")
+             .imageUrl("http://image.url")
+             .build());
+ }

  private RoomJpaEntity saveRoom(String categoryValue, String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) {
-     AliasJpaEntity alias = aliasJpaRepository.save(AliasJpaEntity.builder()
-             .value("소설-칭호")
-             .color("blue")
-             .imageUrl("http://image.url")
-             .build());
+     AliasJpaEntity alias = createTestAlias();
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java (2)

36-36: 주석의 오타를 수정해주세요.

"첵제목" → "책제목"

-        // 1. 검색 조건(where) 조립 : 방이름 or 첵제목에 keyword 포함, category 필터 적용, 멤버 모집중인(= 활동 시작전인) 방만 검색
+        // 1. 검색 조건(where) 조립 : 방이름 or 책제목에 keyword 포함, category 필터 적용, 멤버 모집중인(= 활동 시작전인) 방만 검색

87-87: 키셋 페이징 도입 검토가 좋은 접근입니다.

대량의 데이터에서 오프셋 페이징은 성능 문제를 일으킬 수 있으므로, 키셋 페이징 도입은 좋은 개선 방향입니다.

키셋 페이징 구현을 위한 이슈를 생성하시겠습니까?

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ef6d146 and 50c25fc.

📒 Files selected for processing (14)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/common/util/DateUtilsss.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/response/RoomSearchResponse.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/persistence/CategoryName.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomJpaRepository.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 (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/persistence/RoomSearchSortParam.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/port/in/RoomSearchUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/service/RoomSearchService.java (1 hunks)
  • src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 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/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/RoomQueryPersistenceAdapter.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/RoomSearchApiTest.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.756Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
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/application/port/in/RoomSearchUseCase.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/application/service/RoomSearchService.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로부터 조회하는 메서드를 추가함.
🧬 Code Graph Analysis (1)
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java (2)
src/main/java/konkuk/thip/common/util/DateUtilsss.java (1)
  • DateUtilsss (7-31)
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (1)
  • Repository (13-29)
⏰ 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/common/exception/code/ErrorCode.java (1)

74-75: 에러 코드 추가가 적절합니다.

기존 패턴을 잘 따르고 있으며, 방 검색 정렬 조건 검증을 위한 에러 코드가 적절하게 추가되었습니다. 코드 번호와 메시지도 일관성이 있습니다.

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

8-8: 인터페이스 확장이 적절합니다.

JPA 리포지토리에 커스텀 쿼리 기능을 추가하는 표준적인 방법입니다. 기존 메서드는 유지하면서 새로운 검색 기능을 깔끔하게 추가했습니다.

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

12-12: 메서드 시그니처는 적절합니다.

페이지네이션을 지원하는 검색 메서드 시그니처가 적절하게 정의되었습니다. 검색 키워드, 카테고리, 페이지 정보를 받아서 페이지네이션된 결과를 반환하는 구조가 명확합니다.

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

9-9: 인터페이스 선언이 명확합니다.

검색 메서드의 시그니처가 명확하고 간결하게 정의되었습니다. QueryDSL 구현체에서 이를 구현할 수 있도록 적절한 인터페이스를 제공하고 있습니다.

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

7-7: UseCase 메서드 시그니처가 적절합니다.

검색 파라미터들을 명확하게 정의했으며, 기본 타입을 사용하여 application layer에 적합한 인터페이스를 제공하고 있습니다.

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

3-3: 새로운 임포트 추가가 적절합니다.

방 검색 기능을 위한 필요한 의존성들이 올바르게 추가되었습니다.

Also applies to: 7-8


25-28: 메서드 구현이 깔끔합니다.

Adapter 패턴에 맞게 repository로 단순 위임하는 구조가 적절합니다.

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

4-5: 필요한 임포트가 적절히 추가되었습니다.

방 검색 API를 위한 의존성들이 올바르게 임포트되었습니다.

Also applies to: 10-12


21-29: 엔드포인트 구현이 적절하지만 파라미터 검증을 확인하세요.

REST API 엔드포인트가 올바르게 구현되었습니다. 하지만 page 파라미터에 대한 음수 값이나 잘못된 값에 대한 검증이 Service 계층에서 수행되는지 확인해주세요.

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

14-14: 미완성 기능에 대한 처리를 확인하세요.

RECOMMEND 상수가 "개발 미정"으로 표시되어 있는데, 현재 사용되지 않는 상수라면 제거하거나 사용 시 적절한 예외 처리가 필요합니다.

src/main/java/konkuk/thip/room/application/service/RoomSearchService.java (3)

28-47: 메서드 구현이 적절합니다!

searchRoom 메서드가 명확한 단계별 로직으로 잘 구성되어 있습니다. 페이지 인덱스 변환과 기본값 처리가 안전하게 구현되었습니다.


49-55: 정렬 파라미터 검증 로직이 적절합니다.

enum을 활용한 검증과 비즈니스 예외로의 적절한 변환이 구현되었습니다.


57-67: 카테고리 검증 로직이 잘 구현되었습니다.

null과 빈 문자열 처리가 적절하며, 일관된 예외 처리 패턴을 사용하고 있습니다.

src/main/java/konkuk/thip/room/adapter/in/web/response/RoomSearchResponse.java (1)

5-22: 응답 DTO 구조가 명확합니다!

record를 사용한 불변 DTO 구현이 적절하며, 페이지네이션 정보와 검색 결과가 잘 구성되어 있습니다.

src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java (2)

158-159: 도메인 규칙 위반 가능성을 확인해주세요.

LocalDate.now().minusDays(1)로 과거 날짜를 설정하고 있는데, Room 도메인에 startDate는 현재 날짜 이후여야 한다는 규칙이 있을 수 있습니다. 이 경우 도메인 규칙 위반으로 테스트가 실패할 수 있습니다.

만약 도메인 규칙이 있다면, reflection을 사용하여 만료된 상태를 시뮬레이션하는 것을 고려해보세요.


139-366: 테스트 케이스가 포괄적으로 잘 작성되었습니다!

키워드, 카테고리, 정렬, 페이지네이션 등 다양한 시나리오를 검증하고 있으며, 특히 방 이름과 책 제목 매칭의 우선순위까지 검증하는 점이 좋습니다.

@@ -1,7 +1,13 @@
package konkuk.thip.room.application.port.out;

import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse;

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

아키텍처 레이어 의존성 검토가 필요합니다.

Port 인터페이스(application layer)에서 web layer의 response DTO를 직접 의존하고 있습니다. Clean Architecture 원칙에 따르면 application layer는 adapter layer에 의존하지 않아야 합니다.

도메인/application layer에서 사용할 별도의 DTO나 도메인 객체를 정의하는 것을 고려해보세요.

🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java at
line 3, the import statement brings in a web layer response DTO into the
application layer port interface, violating Clean Architecture principles. To
fix this, remove the dependency on the web layer DTO by defining a separate DTO
or domain object within the domain or application layer that represents the data
needed. Then update the port interface to use this new internal DTO instead of
the web response class.

@@ -0,0 +1,10 @@
package konkuk.thip.room.adapter.out.persistence;

import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse;

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

레이어 의존성 검토가 필요합니다.

Persistence layer의 인터페이스에서 web layer의 response DTO를 직접 의존하고 있습니다. 이는 레이어 간 의존성을 증가시킬 수 있습니다.

가능하다면 persistence layer에서 사용할 별도의 DTO나 도메인 객체를 정의하는 것을 고려해보세요.

🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java
at line 3, the persistence layer is directly importing a web layer response DTO,
which increases layer dependency. To fix this, remove the import of the web
layer DTO and instead define and use a separate DTO or domain object within the
persistence layer that represents the data needed. This will decouple the
persistence layer from the web layer and maintain proper layer separation.

@@ -0,0 +1,8 @@
package konkuk.thip.room.application.port.in;

import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse;

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

UseCase에서 web layer 의존성 검토가 필요합니다.

Application layer의 UseCase에서 web layer의 response DTO를 직접 의존하고 있습니다. 이는 Clean Architecture의 의존성 규칙을 위반할 수 있습니다.

Application layer에서 사용할 독립적인 DTO나 도메인 객체를 정의하는 것을 고려해보세요.

🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/application/port/in/RoomSearchUseCase.java at
line 3, the import of RoomSearchResponse from the web layer creates an unwanted
dependency from the application layer to the web layer, violating Clean
Architecture principles. To fix this, remove the import of the web layer DTO and
define a new independent DTO or domain object within the application layer that
represents the response data. Refactor the UseCase interface and its
implementations to use this new application-layer DTO instead of the web layer
response class.

import java.time.LocalDate;
import java.time.LocalDateTime;

public class DateUtilsss {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

클래스 이름의 오타를 수정하세요.

클래스 이름에 DateUtilsss라고 되어 있는데, DateUtils로 수정해야 합니다.

-public class DateUtilsss {
+public class DateUtils {
📝 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
public class DateUtilsss {
-public class DateUtilsss {
+public class DateUtils {
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/common/util/DateUtilsss.java at line 7, the class
name is misspelled as DateUtilsss. Rename the class to DateUtils to correct the
typo and ensure consistency with naming conventions.

SCIENCE_IT("과학/IT"),
Literature("문학"),
ART("예술"),
SOCIAL_SCIENCE("사회과확"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

카테고리 이름의 오타를 수정하세요.

"사회과확"이 "사회과학"으로 수정되어야 합니다.

-    SOCIAL_SCIENCE("사회과확"),
+    SOCIAL_SCIENCE("사회과학"),
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/adapter/out/persistence/CategoryName.java at
line 19, correct the typo in the category name from "사회과확" to "사회과학" by updating
the string literal accordingly.

Comment on lines +121 to +139
private OrderSpecifier<?> toOrderSpecifier(Sort sort, QRoomJpaEntity room, NumberExpression<Long> memberCountExpr) {
// sort 파라미터가 없으면 기본 마감 임박순
if (sort.isUnsorted()) {
return room.startDate.asc();
}

// 클라이언트가 보낸 첫 번째 sort 키를 꺼냅니다.
String key = sort.stream().findFirst().get().getProperty();

switch (key) {
case "memberCount":
// user_rooms 테이블에서 현재 참여자 수 집계 → 내림차순
return new OrderSpecifier<>(Order.DESC, memberCountExpr);
case "deadLine":
default:
// deadLine: 마감 임박순 = startDate 빠른 순서대로(오름차순)
return room.startDate.asc();
}
}

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

정렬 키 추출 시 안전성을 개선해주세요.

findFirst().get() 사용은 NoSuchElementException을 발생시킬 수 있습니다.

  private OrderSpecifier<?> toOrderSpecifier(Sort sort, QRoomJpaEntity room, NumberExpression<Long> memberCountExpr) {
      // sort 파라미터가 없으면 기본 마감 임박순
      if (sort.isUnsorted()) {
          return room.startDate.asc();
      }

      // 클라이언트가 보낸 첫 번째 sort 키를 꺼냅니다.
-     String key = sort.stream().findFirst().get().getProperty();
+     String key = sort.stream()
+             .findFirst()
+             .map(Sort.Order::getProperty)
+             .orElse("deadLine");

      switch (key) {
📝 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 OrderSpecifier<?> toOrderSpecifier(Sort sort, QRoomJpaEntity room, NumberExpression<Long> memberCountExpr) {
// sort 파라미터가 없으면 기본 마감 임박순
if (sort.isUnsorted()) {
return room.startDate.asc();
}
// 클라이언트가 보낸 첫 번째 sort 키를 꺼냅니다.
String key = sort.stream().findFirst().get().getProperty();
switch (key) {
case "memberCount":
// user_rooms 테이블에서 현재 참여자 수 집계 → 내림차순
return new OrderSpecifier<>(Order.DESC, memberCountExpr);
case "deadLine":
default:
// deadLine: 마감 임박순 = startDate 빠른 순서대로(오름차순)
return room.startDate.asc();
}
}
private OrderSpecifier<?> toOrderSpecifier(Sort sort, QRoomJpaEntity room, NumberExpression<Long> memberCountExpr) {
// sort 파라미터가 없으면 기본 마감 임박순
if (sort.isUnsorted()) {
return room.startDate.asc();
}
// 클라이언트가 보낸 첫 번째 sort 키를 꺼냅니다.
String key = sort.stream()
.findFirst()
.map(Sort.Order::getProperty)
.orElse("deadLine");
switch (key) {
case "memberCount":
// user_rooms 테이블에서 현재 참여자 수 집계 → 내림차순
return new OrderSpecifier<>(Order.DESC, memberCountExpr);
case "deadLine":
default:
// deadLine: 마감 임박순 = startDate 빠른 순서대로(오름차순)
return room.startDate.asc();
}
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java
around lines 121 to 139, the code uses findFirst().get() on the sort stream
which can throw NoSuchElementException if no elements exist. To fix this, safely
check if the first sort key is present using isPresent() or use orElse to
provide a default value before accessing it. This will prevent exceptions when
the sort parameter is empty.

@sonarqubecloud

sonarqubecloud Bot commented Jul 9, 2025

Copy link
Copy Markdown

@seongjunnoh seongjunnoh merged commit f4c52bb into develop Jul 9, 2025
3 checks passed

@hd0rable hd0rable left a comment

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.

수고하셨습니다!! 중요 로직이 누락된것 같아 확인부탁드립니다!

// 1. validation
String sortVal = validateSort(sort);
String categoryVal = validateCategory(category);

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: keyword 검증이 필요할것 같습니다!

public Page<RoomSearchResponse.RoomSearchResult> searchRoom(String keyword, String category, Pageable pageable) {
// 1. 검색 조건(where) 조립 : 방이름 or 첵제목에 keyword 포함, category 필터 적용, 멤버 모집중인(= 활동 시작전인) 방만 검색
BooleanBuilder where = new BooleanBuilder();
// keyword 필터 (빈 문자열이면 생략)

@hd0rable hd0rable Jul 9, 2025

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: 저희 서비스에서 모임방 검색후 검색어와 일치하는 모임방을 카테고리/정렬 필터에 따른 값으로 보여주는 것으로 알고있는데 이렇게 되면 keyword 필터가 필수이지않을까요..? 현재 구현하신것으로는 키워드가 없으면 키워드 없이 필터나/정렬으로만 결과가 반환되서 전체 책의 정렬 결과가 반환 될 것같은데 키워드포함 정렬결과가 반환되어야할것같습니다..!


// 3. 방 검색
Page<RoomSearchResponse.RoomSearchResult> result = roomQueryPort.searchRoom(keyword, categoryVal, pageable);

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: 유저의 최근 검색어 추가 로직이 누락된것같습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-95] [feat] 방 검색 api 개발

2 participants