Skip to content

[hotfix] 검색 관련 api 의 request 에 검색중/검색 완료를 구분하는 flag 추가#208

Merged
seongjunnoh merged 12 commits intodevelopfrom
hotfix/#204-add-flag-request-in-all-search-api
Aug 12, 2025
Merged

[hotfix] 검색 관련 api 의 request 에 검색중/검색 완료를 구분하는 flag 추가#208
seongjunnoh merged 12 commits intodevelopfrom
hotfix/#204-add-flag-request-in-all-search-api

Conversation

@seongjunnoh
Copy link
Collaborator

@seongjunnoh seongjunnoh commented Aug 12, 2025

#️⃣ 연관된 이슈

closes #204

📝 작업 내용

이슈 참고해 주시면 됩니다

📸 스크린샷

💬 리뷰 요구사항

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

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

Summary by CodeRabbit

  • New Features

    • 도서/룸/사용자 검색 API에 isFinalized(boolean) 파라미터 추가로 최종 입력 여부 전달 지원
    • isFinalized 플래그를 기준으로 최근검색어 저장 동작 제어: isFinalized=true일 때만 저장
  • Tests

    • 모든 관련 검색 API 테스트에 isFinalized 파라미터 반영
    • isFinalized=true일 때 최근검색어가 저장되는 시나리오 검증 추가

@coderabbitai
Copy link

coderabbitai bot commented Aug 12, 2025

Walkthrough

검색 API들(도서/방/사용자)에 isFinalized(boolean) 쿼리 파라미터가 추가되었고, 컨트롤러 → 포트(유스케이스) → 서비스로 시그니처가 확장되어 플래그가 전달됩니다. isFinalized가 true일 때만 최근검색어 저장 로직이 호출되도록 관련 서비스와 RecentSearchCreateManager 호출이 변경되었습니다. 테스트들도 해당 파라미터로 업데이트되었습니다.

Changes

Cohort / File(s) Summary
Book search flow
src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java, src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java, src/main/java/konkuk/thip/book/application/service/BookSearchService.java, src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java
searchBooks 시그니처에 boolean isFinalized 추가 및 호출 전달. 서비스에서 RecentSearchCreateManager로 최근검색 저장 호출이 isFinalized에 따라 이루어지도록 변경. 테스트에 쿼리 파라미터 추가 및 저장 케이스 포함.
Room search flow
src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java, src/main/java/konkuk/thip/room/application/port/in/RoomSearchUseCase.java, src/main/java/konkuk/thip/room/application/service/RoomSearchService.java, src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java
/rooms/search 컨트롤러/포트/서비스 시그니처에 isFinalizeduserId 전달 인자 확장. 서비스는 검색 후 RecentSearchCreateManager 호출을 추가(조건부). 테스트에 쿼리 파라미터 추가.
User search flow
src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java, src/main/java/konkuk/thip/user/application/port/in/dto/UserSearchQuery.java, src/main/java/konkuk/thip/user/application/service/UserSearchService.java, src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java
컨트롤러에 isFinalized 파라미터 추가. UserSearchQuery 레코드와 of(...) 팩토리에 isFinalized 추가. 서비스에서 최근검색 저장을 isFinalized==true일 때만 수행하도록 변경. 테스트는 저장 검증 추가.
Recent search manager
src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java
saveRecentSearchByUser(..., boolean isFinalized) 시그니처로 변경; 내부에서 isFinalized가 true인 경우에만 upsert(존재시 touch, 미존재시 save) 수행하도록 분기 처리.

Sequence Diagram(s)

sequenceDiagram
  actor Client
  participant Controller
  participant UseCase
  participant Service
  participant RecentSearchManager

  Client->>Controller: GET /.../search?keyword=...&isFinalized={true|false}
  Controller->>UseCase: search(..., isFinalized)
  UseCase->>Service: search(..., isFinalized, userId)
  Service->>Service: 검색 수행
  alt isFinalized == true
    Service->>RecentSearchManager: saveRecentSearchByUser(userId, keyword, type, true)
    RecentSearchManager->>RecentSearchManager: upsert (touch or save)
  else isFinalized == false
    Note right of Service: 최근검색 저장 호출 없음
  end
  Service-->>Controller: 응답 데이터
  Controller-->>Client: HTTP response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
모든 검색 API 요청에 isFinalized 플래그 추가 [#204]
플래그를 컨트롤러→유스케이스(포트)→서비스까지 전달하도록 시그니처 확장 [#204]
방 검색 API에 동일 플래그 반영(전달 기준) [#204]

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
최근검색어 저장 조건 변경: BookSearchService.searchBooks 내부 분기 처리 (src/main/java/konkuk/thip/book/application/service/BookSearchService.java) 이슈 #204는 플래그 추가/전달이 핵심으로, 저장 로직의 동작 변경 여부는 명시되어 있지 않음—동작 변경으로 보임.
최근검색어 저장 조건 변경: UserSearchService.searchUsers 내부 분기 처리 (src/main/java/konkuk/thip/user/application/service/UserSearchService.java) 이슈 #204의 요구사항에 저장 동작 변경이 명시되어 있지 않아 범위 밖일 수 있음.

Possibly related PRs

Suggested labels

🧸 현준

Suggested reviewers

  • hd0rable

Poem

"깡충깡충, 내가 토끼, 플래그를 물었네 🥕
검색은 달리고 멈추고, 완료면 기록하네.
책·방·사람, 같은 신호에 귀 기울이고,
작은 발자국은 DB에 남겨지네.
오늘도 깡충, isFinalized 찰칵!"

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch hotfix/#204-add-flag-request-in-all-search-api

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

github-actions bot commented Aug 12, 2025

Test Results

374 tests  ±0   374 ✅ ±0   31s ⏱️ ±0s
110 suites ±0     0 💤 ±0 
110 files   ±0     0 ❌ ±0 

Results for commit 8250755. ± Comparison against base commit 83160d8.

♻️ This comment has been updated with latest results.

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

🧹 Nitpick comments (6)
src/main/java/konkuk/thip/user/application/port/in/dto/UserSearchQuery.java (1)

9-10: 팩토리 메서드 오버로드(기본값 false) 추가를 고려해 주세요

신규 호출부는 모두 4-파라미터로 정리된 듯 하나, 사용성/이행성을 위해 아래 오버로드를 일시 제공하면 컴파일 의존성 파급을 줄일 수 있습니다(추후 @deprecated로 안내 후 제거 권장).

public static UserSearchQuery of(String keyword, Long userId, Integer size) {
    return new UserSearchQuery(keyword, userId, size, false);
}
  • 장점: 기존 호출부가 점진 이행 가능, 테스트 픽스처도 간결화.
  • 단점: 일시적인 API 중복. 릴리즈 노트로 제거 계획 명시 권장.

또한 isFinalized의 의미(“검색 완료를 의미하며, true일 때만 최근검색어 저장” 등)를 Javadoc으로 기록해 두면 후속 유지보수 시 오해를 줄일 수 있습니다.

src/main/java/konkuk/thip/user/application/service/UserSearchService.java (1)

32-35: 최근검색어 저장 시 키워드 정규화(공백·대소문자) 적용 고려

isFinalized 게이트 자체는 적절합니다. 저장 시 아래와 같이 strip + Locale-독립 lower-case를 적용하면 중복 저장(예: "Thip", "thip", "thip ")과 인덱스 파편화를 줄일 수 있습니다.

-        if (userSearchQuery.isFinalized()) {
-            recentSearchCreateManager.saveRecentSearchByUser(userSearchQuery.userId(), userSearchQuery.keyword(), RecentSearchType.USER_SEARCH);
-        }
+        if (userSearchQuery.isFinalized()) {
+            var kw = userSearchQuery.keyword();
+            var normalizedKeyword = kw == null ? null : kw.strip().toLowerCase(java.util.Locale.ROOT);
+            if (normalizedKeyword != null && !normalizedKeyword.isEmpty()) {
+                recentSearchCreateManager.saveRecentSearchByUser(
+                        userSearchQuery.userId(), normalizedKeyword, RecentSearchType.USER_SEARCH);
+            }
+        }

추가 참고:

  • 위 정규화는 저장 시점에만 적용했습니다(검색 쿼리는 이미 toLowerCase 사용). 검색부도 Locale.ROOT 사용을 권장합니다(터키어 i 등 로케일 이슈 회피).
  • RecentSearch 저장 정책이 “사용자 입력 원형 보존”이라면 현행 유지가 맞습니다. 그 경우 최소한 strip()만 적용해 공백 중복은 방지하는 절충안을 고려해 주세요.
src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java (1)

75-75: isFinalized=false 케이스(미저장) 테스트 추가 및 단언 강화 제안

  • 현재 테스트는 isFinalized=true 저장 성공만 검증합니다. 미완료(false)일 때 저장되지 않는지 역조건 테스트를 추가해 주세요.
  • 또한 최근검색어 건수를 size==1로 단언하면 향후 픽스처 변경에 취약합니다. 아래처럼 사용자/타입/키워드로 필터링해 기대 카운트를 검증하는 쪽이 견고합니다.

예시(새 테스트 또는 단언 교체 코드):

// 미저장 테스트
@Test
@DisplayName("사용자 검색 시 isFinalized=false 이면 최근 검색어가 저장되지 않는다")
void searchUsers_doNotSaveWhenNotFinalized() throws Exception {
    String keyword = "thip";

    mockMvc.perform(
            get("/users")
                    .param("keyword", keyword)
                    .requestAttr("userId", currentUserId)
                    .param("size", "10")
                    .param("isFinalized", "false")
    ).andExpect(status().isOk());

    long count = recentSearchJpaRepository.findAll().stream()
            .filter(rs -> rs.getUserJpaEntity().getUserId().equals(currentUserId))
            .filter(rs -> rs.getType() == RecentSearchType.USER_SEARCH)
            .filter(rs -> rs.getSearchTerm().equals(keyword))
            .count();

    assertEquals(0, count);
}
src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java (1)

187-188: 중복 파라미터 구성 정리 및 경량화 제안

  • 동일한 .param("isFinalized", String.valueOf(false))가 반복됩니다. "false" 리터럴로 단순화 가능합니다.
  • 테스트 메서드 간 공통 Request 구성은 헬퍼로 추출하면 중복을 줄일 수 있습니다(가독성/유지보수성 향상).

예시:

private ResultActions getRoomsSearch(String keyword, String category, String sort, boolean isFinalized, String page) throws Exception {
    var builder = get("/rooms/search").requestAttr("userId", 1L);
    if (keyword != null) builder = builder.param("keyword", keyword);
    if (category != null) builder = builder.param("category", category);
    if (sort != null) builder = builder.param("sort", sort);
    return mockMvc.perform(builder.param("isFinalized", String.valueOf(isFinalized))
                                  .param("page", page));
}

또한 isFinalized=true 케이스에 대한 회귀 테스트 1건을 추가해 두면(현재 서비스에서 미사용이라도) 미래 변경 시 부작용 감지에 도움이 됩니다.

Also applies to: 237-238, 290-291, 330-331, 373-374

src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java (1)

97-99: 불필요한 변환 제거 및 누락 파라미터 검증 테스트 추가 제안

  • String.valueOf(false) 대신 "false" 리터럴 사용이 간결합니다.
  • 신규 필수 파라미터(isFinalized) 도입에 따라, 누락 시 400을 기대하는 부정 테스트를 추가하면 컨트롤러 제약이 명확해집니다.

예시:

@Test
@DisplayName("책 검색 API 실패 - isFinalized 누락 시 400 에러")
void searchBooks_isFinalizedMissing_badRequest() throws Exception {
    mockMvc.perform(get("/books")
                    .header("Authorization", "Bearer " + testToken)
                    .param("keyword", "테스트")
                    .param("page", "1")
                    .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isBadRequest());
}

테스트 전반의 공통 파라미터 구성도 헬퍼 메서드로 추출하면 중복을 줄일 수 있습니다.

Also applies to: 119-121, 141-143, 161-163

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

36-36: 공개 API 문서에서 작업상태 안내 문구 제거 권장

Swagger description에 “아직 수정중입니다...”와 같은 내부 작업상태는 외부 소비자에 혼선을 줄 수 있습니다. 기능 설명만 남기고 내부 TODO는 코드 주석으로 관리해 주세요.

적용 diff:

-            description = "키워드, 카테고리, 정렬 방식, 페이지 번호를 기준으로 방을 검색합니다. 아직 수정중입니다. 일단 request param 만 수정한 상태입니다."
+            description = "키워드, 카테고리, 정렬 방식, 페이지 번호를 기준으로 방을 검색합니다."
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 83160d8 and faf2cb3.

📒 Files selected for processing (12)
  • src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java (1 hunks)
  • src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/book/application/service/BookSearchService.java (2 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/port/in/RoomSearchUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/service/RoomSearchService.java (2 hunks)
  • src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/port/in/dto/UserSearchQuery.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/service/UserSearchService.java (1 hunks)
  • src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java (5 hunks)
  • src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java (5 hunks)
  • src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java (2 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 (7)
src/main/java/konkuk/thip/user/application/port/in/dto/UserSearchQuery.java (2)

6-7: DTO 확장 방향 적절합니다

검색 흐름에 boolean 플래그를 마지막 파라미터로 추가한 선택이 자연스럽고, 기존 필드 순서도 유지되어 가독성이 좋습니다.


9-10: 호출부 일괄 갱신 완료 확인

레포지토리 전역에 3-인자 UserSearchQuery.of 호출은 없으며, 모두 4-인자 호출(of(keyword, userId, size, isFinalized))으로 업데이트되었습니다.

src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java (1)

182-183: isFinalized=true 저장 검증 테스트 👍

저장 성공 경로에 대한 단언이 충분하며, 사용자/키워드/타입으로 필터링해 정확히 해당 레코드를 검증하는 방식이 견고합니다.

src/main/java/konkuk/thip/book/application/service/BookSearchService.java (1)

48-48: 시그니처 확장 LGTM

isFinalized를 마지막 인자로 추가한 점, 트랜잭션 쓰기 컨텍스트에서 처리하는 방향 모두 적절합니다.

src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java (1)

47-47: 유스케이스로의 isFinalized 전달 OK

컨트롤러 → 포트-in 시그니처 전달이 정확합니다.

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

126-126: UserSearchQuery.of(...) 4-인자 시그니처 전역 적용 확인 완료
전체 코드베이스에 3-인자 호출이 남아있지 않음을 확인했습니다.

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

47-47: 검증 완료: 4-인자 searchRoom 호출 없음
프로젝트 전반을 검색한 결과, 더 이상 4-인자를 사용하는 searchRoom 호출이 존재하지 않습니다.

  • Controller, UseCase, Service 모두 5-인자 호출로 일관되게 반영되어 있습니다.

public BaseResponse<BookSearchListResponse> showBookSearchList(
@Parameter(description = "검색 키워드", example = "해리포터") @RequestParam final String keyword,
@Parameter(description = "페이지 번호 (1부터 시작)", example = "1") @RequestParam final int page,
@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
Copy link

@coderabbitai coderabbitai bot Aug 12, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

API 호환성 이슈: isFinalized를 선택값 + 기본값 false로 처리해 기존 클라이언트 파손 방지

현재 boolean 원시 타입으로 @RequestParam을 추가하여 필수 파라미터가 되었습니다. 기존 호출에서 해당 쿼리 파라미터를 누락하면 400이 발생합니다. hotfix 상황에서는 하위 호환성을 위해 defaultValue="false"로 두고 required=false를 권장합니다.

권장 변경(diff):

-            @Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
+            @Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false")
+            @RequestParam(name = "isFinalized", required = false, defaultValue = "false") final boolean isFinalized,
📝 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
@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false")
@RequestParam(name = "isFinalized", required = false, defaultValue = "false") final boolean isFinalized,
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java around
line 45, the @RequestParam currently makes isFinalized a required primitive
boolean which breaks clients that omit the query param; change the annotation to
make the parameter optional by adding required=false and defaultValue="false" so
missing params bind as false (you can keep the parameter as final boolean
isFinalized), e.g., update the @RequestParam to required=false,
defaultValue="false" to preserve backward compatibility and avoid 400 responses.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이 파라미터는 필수 파라미터를 의도했으니 required = false 는 적절하지 않다고 생각해.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +8 to +9
NaverBookParseResult searchBooks(String keyword, int page, Long userId, boolean isFinalized);
BookDetailSearchResult searchDetailBooks(String isbn, Long userId);
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

포트 시그니처 변경에 따른 호출부/구현체 동기화 확인 + 파라미터 의미 Javadoc 추가 제안

boolean isFinalized 추가는 의도와 맞습니다. 다만 외부 공개 포트 변경이므로 모든 구현체와 호출부가 4개 인자 시그니처로 동기화되었는지 확인해 주세요. 또한 isFinalized의 의미(입력 중 vs. 확정)와 저장 정책을 Javadoc으로 명시하면 유지보수성이 좋아집니다.

다음 스크립트로 잠재적 미동기 호출을 점검할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# searchBooks 호출부를 전수 조사합니다. (컨텍스트 2줄 포함)
rg -n --no-ignore -S 'searchBooks\(' -A 2 | sed 's/^/>> /'

Length of output: 1251


searchBooks 호출부·구현체 모두 4개 인자 동기화 완료 확인

  • BookSearchUseCase#searchBooks, BookSearchService, BookQueryController 모두 String keyword, int page, Long userId, boolean isFinalized 시그니처로 일치합니다.

isFinalized 파라미터 의미 명시를 위한 Javadoc 추가 제안

 src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java
@@ interface BookSearchUseCase {
-    NaverBookParseResult searchBooks(String keyword, int page, Long userId, boolean isFinalized);
+    /**
+     * 네이버 도서 검색을 수행합니다.
+     *
+     * @param keyword 검색어
+     * @param page    페이지 번호 (1부터 시작)
+     * @param userId  사용자 식별자
+     * @param isFinalized 최종 확정 여부.
+     *                     - false: 사용자가 입력 중인 임시 상태의 결과 조회  
+     *                     - true: 최종 확정된 상태의 결과 조회 및 저장
+     * @return 검색 결과를 담은 {@link NaverBookParseResult}
+     */
+    NaverBookParseResult searchBooks(String keyword, int page, Long userId, boolean isFinalized);
📝 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
NaverBookParseResult searchBooks(String keyword, int page, Long userId, boolean isFinalized);
BookDetailSearchResult searchDetailBooks(String isbn, Long userId);
/**
* 네이버 도서 검색을 수행합니다.
*
* @param keyword 검색어
* @param page 페이지 번호 (1부터 시작)
* @param userId 사용자 식별자
* @param isFinalized 최종 확정 여부.
* - false: 사용자가 입력 중인 임시 상태의 결과 조회
* - true: 최종 확정된 상태의 결과 조회 저장
* @return 검색 결과를 담은 {@link NaverBookParseResult}
*/
NaverBookParseResult searchBooks(String keyword, int page, Long userId, boolean isFinalized);
BookDetailSearchResult searchDetailBooks(String isbn, Long userId);
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java
around lines 8-9, the searchBooks method signature now matches other call sites
but lacks documentation for the isFinalized parameter; add a concise Javadoc
comment above searchBooks explaining what isFinalized represents (e.g., whether
to include only finalized/published book data vs. drafts, or to indicate the
caller is marking the search as the final page), its expected values/behavior,
and any side effects or permissions required so callers and implementers have
clear intent.

Comment on lines 41 to 46
@Parameter(description = "검색 키워드 (책 이름 or 방 이름", example = "해리") @RequestParam(value = "keyword", required = false, defaultValue = "") final String keyword,
@Parameter(description = "모임방 카테고리", example = "문학") @RequestParam(value = "category", required = false, defaultValue = "") final String category,
@Parameter(description = "정렬 방식 (마감 임박 : deadline, 신청 인원 : memberCount)", example = "deadline") @RequestParam("sort") final String sort,
@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
@Parameter(description = "페이지 번호", example = "1") @RequestParam("page") final int page
) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

isFinalized 호환성(기본값 false) + page 입력값 검증 + 오타 보정 제안

  • 기존 클라이언트 호환을 위해 isFinalized에 defaultValue="false" 권장.
  • page 파라미터에 @min(1)로 입력 검증 추가 권장.
  • keyword 설명에 닫는 괄호가 누락되었습니다.

적용 diff:

-            @Parameter(description = "검색 키워드 (책 이름 or 방 이름", example = "해리") @RequestParam(value = "keyword", required = false, defaultValue = "") final String keyword,
+            @Parameter(description = "검색 키워드 (책 이름 or 방 이름)", example = "해리") @RequestParam(value = "keyword", required = false, defaultValue = "") final String keyword,
@@
-            @Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
-            @Parameter(description = "페이지 번호", example = "1") @RequestParam("page") final int page
+            @Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false")
+            @RequestParam(name = "isFinalized", required = false, defaultValue = "false") final boolean isFinalized,
+            @Parameter(description = "페이지 번호(최소 1)", example = "1") @Min(1) @RequestParam("page") final int page

추가로 필요한 import:

import jakarta.validation.constraints.Min;

참고: 추후 서비스에서 isFinalized=true일 때 최근검색 저장을 구현하실 때, keyword가 빈 문자열("")인 경우는 저장에서 제외하는 가드 처리를 함께 고려해 주세요.

🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java around
lines 41-46: make isFinalized backward-compatible by adding defaultValue="false"
(and required=false) to its @RequestParam; add input validation to page by
annotating the page parameter with @Min(1) and import
jakarta.validation.constraints.Min; fix the keyword @Parameter description by
adding the missing closing parenthesis; and ensure you add the import line for
jakarta.validation.constraints.Min at the top of the file.

public interface RoomSearchUseCase {

RoomSearchResponse searchRoom(String keyword, String category, String sort, int page);
RoomSearchResponse searchRoom(String keyword, String category, String sort, int page, boolean isFinalized);
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

포트 시그니처 변경 영향 점검

RoomSearchUseCase 구현체/호출부가 모두 (keyword, category, sort, page, isFinalized) 시그니처로 동기화되었는지 확인해 주세요. Javadoc으로 isFinalized 의미를 명시하면 API 사용성이 좋아집니다.

검증 스크립트:


🏁 Script executed:

#!/bin/bash
# 해당 포트를 구현하는 클래스와 searchRoom 호출부를 확인
rg -n --no-ignore -S 'implements\s+RoomSearchUseCase' -A 3
rg -n --no-ignore -S 'searchRoom\(' -A 2

Length of output: 4106


포트 시그니처 변경 적용 확인 및 Javadoc 추가 요청

  • RoomSearchUseCase 인터페이스의 시그니처
    searchRoom(String keyword, String category, String sort, int page, boolean isFinalized)
    → 구현체(RoomSearchService)와 호출부(RoomQueryController)에 모두 정상 반영됨을 확인했습니다.
  • isFinalized 파라미터의 의미를 RoomSearchUseCase#searchRoom 메서드에 Javadoc으로 명시해 주시면 API 사용성이 더욱 좋아집니다.
    • 파일: src/main/java/konkuk/thip/room/application/port/in/RoomSearchUseCase.java
    • 메서드 선언부 위에 간략한 설명 추가
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/application/port/in/RoomSearchUseCase.java
around line 7, the interface method searchRoom(...) lacks Javadoc describing the
parameters — specifically the meaning of the boolean isFinalized; add a concise
Javadoc block immediately above the method declaration that explains the method
purpose and documents each parameter (keyword, category, sort, page, and
especially isFinalized with its semantics, e.g., whether true filters for
finalized rooms and false includes drafts/unfinalized), and include the return
description for RoomSearchResponse.

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

// TODO 검색 완료일 경우, 최근 검색어로 저장되도록
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

파라미터 미사용(TODO만 존재) — 모듈 간 동작 일관성 유지를 위해 즉시 구현 권장

Book/User 모듈과 동일하게 isFinalized==true일 때만 최근검색어를 저장하도록 구현하면 UX/데이터 일관성이 확보됩니다. 성능 측면에서도 입력 중 요청의 쓰기를 배제해 DB 부하와 write amplification을 줄일 수 있습니다.

원하시면 RecentSearchCommandPort 주입 및 저장 블록(중복/널 가드 포함)까지 패치 제안 드리겠습니다.

🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/application/service/RoomSearchService.java
around line 42, the TODO indicates unused parameter and missing behavior:
implement saving recent searches only when isFinalized==true to match Book/User
modules. Inject RecentSearchCommandPort into this service, and inside the search
handling flow check if isFinalized is true; if so, guard against null/empty
search terms and duplicates before calling the RecentSearchCommandPort save
method (use user id + term dedupe logic), ensuring no writes occur for
intermediate typing and maintaining module consistency.

@GetMapping("/users")
public BaseResponse<UserSearchResponse> showSearchUsers(
@Parameter(description = "검색어", example = "thip") @RequestParam @NotBlank(message = "검색어는 필수입니다.") final String keyword,
@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

필수 파라미터 추가로 인한 API 호환성 깨짐 — 기본값 false로 다운타임 없이 적용 권장

현재 isFinalized가 필수(boolean, defaultValue 미지정)여서 기존 클라이언트는 즉시 400 Bad Request를 받을 가능성이 큽니다. hotfix 성격이라면 점진적 적용을 위해 기본값을 false로 두고 optional로 받는 것이 안전합니다.

적용 diff:

-@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
+@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false")
+@RequestParam(name = "isFinalized", required = false, defaultValue = "false") final boolean isFinalized,
📝 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
@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false")
@RequestParam(name = "isFinalized", required = false, defaultValue = "false") final boolean isFinalized,
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java around
line 123, the isFinalized @RequestParam is currently mandatory which breaks
existing clients; make it optional with a default of false to preserve backward
compatibility by setting the request param to not required and providing
defaultValue "false" (so calls that omit the parameter receive false), and
update the OpenAPI @Parameter metadata to reflect required=false/default=false.

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

♻️ Duplicate comments (3)
src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (2)

41-41: 키워드 설명 주석의 닫는 괄호 보정

문서화 오탈자입니다.

-            @Parameter(description = "검색 키워드 (책 이름 or 방 이름", example = "해리") @RequestParam(value = "keyword", required = false, defaultValue = "") final String keyword,
+            @Parameter(description = "검색 키워드 (책 이름 or 방 이름)", example = "해리") @RequestParam(value = "keyword", required = false, defaultValue = "") final String keyword,

44-46: isFinalized 파라미터 기본값 추가(하위호환) + page 입력값 검증 추가

기존 클라이언트가 isFinalized를 보내지 않으면 400이 발생합니다. 기본값을 false로 두고 page에는 최소값 검증을 권장합니다.

적용 제안(diff):

-            @Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
-            @Parameter(description = "페이지 번호", example = "1") @RequestParam("page") final int page,
+            @Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false")
+            @RequestParam(name = "isFinalized", required = false, defaultValue = "false") final boolean isFinalized,
+            @Parameter(description = "페이지 번호(최소 1)", example = "1") @jakarta.validation.constraints.Min(1) @RequestParam("page") final int page,

추가로, 키워드 설명의 닫는 괄호가 누락되어 있습니다(아래 코멘트 참고).

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

32-34: readOnly 트랜잭션에서 write 수행 — 트랜잭션 속성 수정 필요

아래에서 최근검색 저장(write)이 발생하는데 본 메서드는 @transactional(readOnly = true)입니다. JPA/드라이버 구성에 따라 플러시 억제/예외 등 문제가 생길 수 있습니다. write를 허용하도록 변경하세요.

적용 제안(diff):

-    @Transactional(readOnly = true)
+    @Transactional
     public RoomSearchResponse searchRoom(String keyword, String category, String sort, int page, boolean isFinalized, Long userId) {

대안: 매니저 메서드에 @transactional(propagation = REQUIRES_NEW)로 별도 쓰기 트랜잭션을 여는 방법도 있습니다.

🧹 Nitpick comments (1)
src/main/java/konkuk/thip/room/application/service/RoomSearchService.java (1)

45-47: TODO 주석 제거 및 의도 명확화

이미 저장 로직이 구현되었으므로 TODO는 혼선을 유발합니다. 주석을 정리하고, 저장 조건은 매니저에서 일관되게 가드하는 것이 좋습니다.

적용 제안(diff):

-        // TODO 검색 완료일 경우, 최근 검색어로 저장되도록
-        recentSearchCreateManager.saveRecentSearchByUser(userId, keyword, RecentSearchType.ROOM_SEARCH, isFinalized);
+        // 검색 완료(isFinalized=true)인 경우에만 최근 검색어 처리
+        recentSearchCreateManager.saveRecentSearchByUser(userId, keyword, RecentSearchType.ROOM_SEARCH, isFinalized);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between faf2cb3 and 4976f19.

📒 Files selected for processing (5)
  • src/main/java/konkuk/thip/book/application/service/BookSearchService.java (3 hunks)
  • src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/port/in/RoomSearchUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/service/RoomSearchService.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/room/application/port/in/RoomSearchUseCase.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#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 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
🔇 Additional comments (2)
src/main/java/konkuk/thip/book/application/service/BookSearchService.java (1)

70-72: 최근검색 저장 위임(LGTM) + 매니저 레벨 가드 확인 요청

매니저로 위임해 일관성을 확보한 점 좋습니다. 다만 Room/User 흐름에서 userId가 null일 수 있으니, 매니저가 userId null/빈 키워드 가드를 포함하도록 보장해 주세요(제안 드린 매니저 수정 적용 시 해결).

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

48-49: 호출 파라미터 순서 일치 확인(LGTM)

컨트롤러 → 유스케이스 호출 시 (keyword, category, sort, page, isFinalized, userId) 순서가 서비스 시그니처와 일치합니다.

Comment on lines +17 to +26
public void saveRecentSearchByUser(Long userId, String keyword, RecentSearchType type, boolean isFinalized) {
// 검색완료일 경우에 최근검색어 추가
if (isFinalized) {
// 동일 조건 (userId + keyword + type) 검색 기록이 이미 있는지 확인
recentSearchQueryPort.findRecentSearchByKeywordAndUserId(keyword, userId, type)
.ifPresentOrElse(
recentSearchCommandPort::touch, // 있으면 modifiedAt 갱신
() -> recentSearchCommandPort.save(RecentSearch.withoutId(keyword, type, userId)) // 없으면 새로 저장
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

중복 저장/레이스 대응을 위한 DB 유니크 제약 + 포트 레벨 upsert 권장

현재 find-then-save 패턴은 동시성 하에서 중복 insert 경쟁이 발생할 수 있습니다. (user_id, keyword, type) 유니크 인덱스를 추가하고, 포트에 upsert(or touch) API를 도입해 단일 쿼리로 처리하면 안전성과 성능이 개선됩니다.

예시:

  • DB: unique index on (user_id, type, keyword)
  • Port: recentSearchCommandPort.upsertOrTouch(userId, normalizedKeyword, type) → DB의 ON CONFLICT/ON DUPLICATE KEY UPDATE 활용

🛠️ Refactor suggestion

⚠️ Potential issue

isFinalized=true라도 userId null/keyword 공백 저장을 차단해야 합니다

익명 요청(userId=null) 또는 공백 키워드가 저장 경로로 들어오면 제약 조건 위반/데이터 오염 가능성이 있습니다. 매니저 레벨에서 가드하면 모든 호출부(Book/Room/User)에서 일관되게 방어됩니다.

적용 제안(diff):

-    public void saveRecentSearchByUser(Long userId, String keyword, RecentSearchType type, boolean isFinalized) {
-        // 검색완료일 경우에 최근검색어 추가
-        if (isFinalized) {
-            // 동일 조건 (userId + keyword + type) 검색 기록이 이미 있는지 확인
-            recentSearchQueryPort.findRecentSearchByKeywordAndUserId(keyword, userId, type)
-                    .ifPresentOrElse(
-                            recentSearchCommandPort::touch, // 있으면 modifiedAt 갱신
-                            () -> recentSearchCommandPort.save(RecentSearch.withoutId(keyword, type, userId)) // 없으면 새로 저장
-                    );
-        }
-    }
+    public void saveRecentSearchByUser(Long userId, String keyword, RecentSearchType type, boolean isFinalized) {
+        // 입력 중이면 write 방지
+        if (!isFinalized) {
+            return;
+        }
+        // 익명/비정상 요청 방어
+        if (userId == null) {
+            return;
+        }
+        // 공백/빈 키워드 저장 방지 + 정규화
+        final String normalizedKeyword = keyword == null ? "" : keyword.trim();
+        if (normalizedKeyword.isEmpty()) {
+            return;
+        }
+        // 동일 조건 (userId + keyword + type) 존재 시 touch, 없으면 저장
+        recentSearchQueryPort.findRecentSearchByKeywordAndUserId(normalizedKeyword, userId, type)
+                .ifPresentOrElse(
+                        recentSearchCommandPort::touch,
+                        () -> recentSearchCommandPort.save(RecentSearch.withoutId(normalizedKeyword, type, userId))
+                );
+    }
📝 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 void saveRecentSearchByUser(Long userId, String keyword, RecentSearchType type, boolean isFinalized) {
// 검색완료일 경우에 최근검색어 추가
if (isFinalized) {
// 동일 조건 (userId + keyword + type) 검색 기록이 이미 있는지 확인
recentSearchQueryPort.findRecentSearchByKeywordAndUserId(keyword, userId, type)
.ifPresentOrElse(
recentSearchCommandPort::touch, // 있으면 modifiedAt 갱신
() -> recentSearchCommandPort.save(RecentSearch.withoutId(keyword, type, userId)) // 없으면 새로 저장
);
}
public void saveRecentSearchByUser(Long userId, String keyword, RecentSearchType type, boolean isFinalized) {
// 입력 중이면 write 방지
if (!isFinalized) {
return;
}
// 익명/비정상 요청 방어
if (userId == null) {
return;
}
// 공백/빈 키워드 저장 방지 + 정규화
final String normalizedKeyword = keyword == null ? "" : keyword.trim();
if (normalizedKeyword.isEmpty()) {
return;
}
// 동일 조건 (userId + keyword + type) 존재 시 touch, 없으면 저장
recentSearchQueryPort.findRecentSearchByKeywordAndUserId(normalizedKeyword, userId, type)
.ifPresentOrElse(
recentSearchCommandPort::touch,
() -> recentSearchCommandPort.save(
RecentSearch.withoutId(normalizedKeyword, type, userId))
);
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java
around lines 17-26, currently the method allows saving when isFinalized=true
even if userId is null or keyword is blank; add a guard that returns early (no
save/touch) when userId is null or keyword is null/blank. Check
keyword.trim().isEmpty() to catch whitespace-only strings and ensure you only
proceed to the existing find/touch/save logic when userId != null and keyword is
non-empty after trimming.

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 (2)
src/main/java/konkuk/thip/user/application/service/UserSearchService.java (2)

32-32: 검색용 키워드 정규화(lowercase)와 저장용 키워드(raw)의 불일치 가능성

현재 조회는 keyword.toLowerCase()를 사용하지만 저장은 원문을 그대로 저장합니다. 이로 인해 동일 의미의 검색(예: "John" vs "john")이 최근검색에 중복 저장되거나 통계가 왜곡될 수 있습니다. 의도적으로 원문을 보존하려는 UX 요구가 없다면 저장 시에도 동일한 정규화를 적용하는 것이 일관적입니다.

다음과 같이 저장 시에도 동일 정규화를 적용하는 최소 수정안을 제안합니다:

-        recentSearchCreateManager.saveRecentSearchByUser(userSearchQuery.userId(), userSearchQuery.keyword(), RecentSearchType.USER_SEARCH, userSearchQuery.isFinalized());
+        recentSearchCreateManager.saveRecentSearchByUser(
+            userSearchQuery.userId(),
+            userSearchQuery.keyword().toLowerCase(Locale.ROOT),
+            RecentSearchType.USER_SEARCH,
+            userSearchQuery.isFinalized()
+        );

추가로 필요한 import:

import java.util.Locale;

대안:

  • 원문(표시용)과 정규화 값(중복 제거/집계용)을 함께 저장하는 2-필드 전략
  • 영속 계층에서 case-insensitive 인덱스/제약(예: lower(keyword) UNIQUE)으로 중복 방지

32-32: 계층 의존성 주의: Application 서비스에서 adapter 레이어 타입 직접 참조

RecentSearchTypeadapter.out.jpa 패키지에 존재하는데, Application 서비스가 이를 직접 참조하면 레이어드/헥사고날 아키텍처 원칙을 어긋날 수 있습니다. 타입을 domain/common 또는 port 레이어로 승격하거나, 포트가 enum을 캡슐화하도록 리팩토링을 고려해 주세요.

가능한 방향:

  • RecentSearchType을 도메인 공유 모듈로 이동
  • 포트에 의존하는 메서드(saveRecentSearchByUserSearch(userId, keyword, isFinalized))를 제공해 Service에서 enum 의존을 제거
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4976f19 and 8250755.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/room/application/service/RoomSearchService.java (3 hunks)
  • src/main/java/konkuk/thip/user/application/service/UserSearchService.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/room/application/service/RoomSearchService.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#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 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
🔇 Additional comments (1)
src/main/java/konkuk/thip/user/application/service/UserSearchService.java (1)

32-32: isFinalized 플래그 전파 의도에 부합합니다 (LGTM).

검색 완료 시점에만 최근검색 저장 로직이 동작하도록 플래그를 전달한 방향성은 적절합니다.

Comment on lines +32 to +33
recentSearchCreateManager.saveRecentSearchByUser(userSearchQuery.userId(), userSearchQuery.keyword(), RecentSearchType.USER_SEARCH, userSearchQuery.isFinalized());

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

테스트: isFinalized=true/false 분기 동작 검증 추가 권장

isFinalized=true일 때만 저장이 발생하고 false일 때는 호출되지 않음을 검증하는 단위/슬라이스 테스트가 필요합니다(Mock 기반 verify).

다음 스크립트로 테스트 존재 여부를 빠르게 탐색할 수 있습니다:


🏁 Script executed:

#!/bin/bash
# isFinalized 분기 검증 및 4-인자 시그니처 전환 적용 여부 점검

echo "1) 최근검색 저장 호출 위치 검색"
rg -n "saveRecentSearchByUser\\(" -A 3

echo ""
echo "2) isFinalized 사용 테스트 존재 여부"
fd -t f -e java -e kt test | xargs rg -n "isFinalized\\(|isFinalized\\s*=" -A 2

echo ""
echo "3) UserSearchService 테스트에서 저장 호출 verify 여부"
rg -n "UserSearchService" -A 5 test | rg -n "RecentSearchCreateManager|saveRecentSearchByUser|verify\\("

Length of output: 2624


isFinalized 분기 동작 검증용 단위 테스트 추가 필요
saveRecentSearchByUserisFinalized=true일 때만 호출되고, false일 땐 호출되지 않아야 하지만 현재 아래 서비스들에 대해 해당 분기를 검증하는 Mock 기반 단위/슬라이스 테스트가 없습니다. 각 서비스 대상 테스트를 추가하시고, verify(recentSearchCreateManager).saveRecentSearchByUser(…)로 호출 여부를 확인해주세요.

  • src/main/java/konkuk/thip/user/application/service/UserSearchService.java
  • src/main/java/konkuk/thip/book/application/service/BookSearchService.java
  • src/main/java/konkuk/thip/room/application/service/RoomSearchService.java
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/user/application/service/UserSearchService.java
around lines 32-33, the call to recentSearchCreateManager.saveRecentSearchByUser
should be covered by unit/slice tests that assert behavior for isFinalized=true
and isFinalized=false; add Mock-based tests for UserSearchService (and similarly
for BookSearchService and RoomSearchService) that invoke the search method with
userSearchQuery.isFinalized() true and false, and use
verify(recentSearchCreateManager).saveRecentSearchByUser(...) to assert the
method is called exactly when isFinalized==true and not called when
isFinalized==false (use verify(..., times(0)) or verifyNoMoreInteractions where
appropriate).

💡 Verification agent

🧩 Analysis chain

최근검색 저장 실패가 전체 검색을 실패시키지 않도록 격리 필요

현재 @Transactional 범위 안에서 저장을 수행하므로, 최근검색 저장 중 예외가 발생하면 전체 요청(조회 포함)이 실패할 수 있습니다. 최근검색 저장은 부가 기능이므로, 실패 시 검색 결과는 그대로 반환하도록 예외를 흡수하는 것을 권장합니다.

권장 대응:

  • 애플리케이션 레벨에서 try-catch로 감싸고 로그만 남기기
  • 또는 RecentSearch 저장 쪽 트랜잭션을 REQUIRES_NEW로 분리

간단한 try-catch 적용 예시는 다음과 같습니다:

-        recentSearchCreateManager.saveRecentSearchByUser(userSearchQuery.userId(), userSearchQuery.keyword(), RecentSearchType.USER_SEARCH, userSearchQuery.isFinalized());
+        try {
+            recentSearchCreateManager.saveRecentSearchByUser(
+                userSearchQuery.userId(),
+                userSearchQuery.keyword(),
+                RecentSearchType.USER_SEARCH,
+                userSearchQuery.isFinalized()
+            );
+        } catch (Exception e) {
+            log.warn("최근검색 저장 실패 - userId={}, keyword={}", userSearchQuery.userId(), userSearchQuery.keyword(), e);
+        }

위 변경을 위해 로거 추가가 필요합니다(아래 코드 추가):

// 클래스 상단
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserSearchService implements UserSearchUsecase {
    ...
}

검증 요청:

  • RecentSearchCreateManager 내부에서 발생 가능한 예외(DataAccessException 등)가 현재 상위로 전파되어 5xx를 유발하는지 확인 부탁드립니다.

RecentSearchCreateManager와 saveRecentSearchByUser 구현을 확인하기 위해 아래 스크립트를 실행하겠습니다.


🏁 Script executed:

#!/bin/bash
# RecentSearchCreateManager 클래스 정의 위치 확인
rg "class RecentSearchCreateManager" -n src

# saveRecentSearchByUser 메서드 구현 위치 확인
rg "saveRecentSearchByUser" -n src

Length of output: 1059


아래 스크립트로 RecentSearchCreateManager의 구현 내용을 확인해 보겠습니다.


🏁 Script executed:

#!/bin/bash
# 클래스 선언부 및 saveRecentSearchByUser 메서드 내용 출력
sed -n '1,200p' src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java

Length of output: 1365


아래 스크립트를 실행해 UserSearchService에 트랜잭션이 적용된 범위를 확인하겠습니다.


🏁 Script executed:

#!/bin/bash
sed -n '1,200p' src/main/java/konkuk/thip/user/application/service/UserSearchService.java

Length of output: 1689


최근검색 저장 실패 시 전체 검색 트랜잭션 격리 및 예외 흡수 필요

현재 UserSearchService.searchUsers() 메서드는 @Transactional 범위 안에서 RecentSearchCreateManager.saveRecentSearchByUser()를 호출하므로, 저장 중 DataAccessException 등 예외 발생 시 전체 검색 요청이 5xx 에러로 응답하게 됩니다. 최근검색 저장은 부가 기능이므로, 저장 실패 시에도 검색 결과는 정상 반환하도록 아래와 같이 수정해 주세요.

수정안:

  • UserSearchService 클래스 맨 위에 Lombok 로거 어노테이션 추가
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    @Service
    @RequiredArgsConstructor
    public class UserSearchService implements UserSearchUsecase {
        ...
    }
  • searchUsers 메서드 내 recentSearchCreateManager.saveRecentSearchByUser(...) 호출을 try–catch로 감싸기
             var userDtoList = userQueryMapper.toUserDtoList(
                 userQueryPort.findUsersByNicknameOrderByAccuracy(
                     userSearchQuery.keyword().toLowerCase(),
                     userSearchQuery.userId(),
                     userSearchQuery.size()
                 )
             );
    
  •      recentSearchCreateManager.saveRecentSearchByUser(
    
  •          userSearchQuery.userId(),
    
  •          userSearchQuery.keyword(),
    
  •          RecentSearchType.USER_SEARCH,
    
  •          userSearchQuery.isFinalized()
    
  •      );
    
  •      try {
    
  •          recentSearchCreateManager.saveRecentSearchByUser(
    
  •              userSearchQuery.userId(),
    
  •              userSearchQuery.keyword(),
    
  •              RecentSearchType.USER_SEARCH,
    
  •              userSearchQuery.isFinalized()
    
  •          );
    
  •      } catch (Exception e) {
    
  •          log.warn("최근검색 저장 실패 – userId={}, keyword={}", 
    
  •              userSearchQuery.userId(), userSearchQuery.keyword(), e);
    
  •      }
    
         return UserSearchResponse.of(userDtoList);
    
  • (선택) RecentSearchCreateManager.saveRecentSearchByUser() 메서드에
    @Transactional(propagation = Propagation.REQUIRES_NEW) 추가하여 별도 트랜잭션 격리 적용 가능

위 변경으로 검색 결과는 항상 반환되며, 최근검색 저장 실패 시에도 전체 트랜잭션이 롤백되지 않습니다.

🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/user/application/service/UserSearchService.java
around lines 32-33, the call to
recentSearchCreateManager.saveRecentSearchByUser(...) runs inside the main
@Transactional searchUsers method so failures there can propagate and cause the
whole search to fail; add Lombok logging by importing lombok.extern.slf4j.Slf4j
and annotating the class with @Slf4j (in addition to @Service and
@RequiredArgsConstructor), then wrap the
recentSearchCreateManager.saveRecentSearchByUser(...) invocation in a try-catch
that catches RuntimeException/DataAccessException, logs the error with
log.error(...) including the exception, and continues so the search result is
still returned; optionally annotate
RecentSearchCreateManager.saveRecentSearchByUser(...) with
@Transactional(propagation = Propagation.REQUIRES_NEW) to run it in a separate
transaction if stricter isolation is desired.

@seongjunnoh seongjunnoh merged commit 076b6a9 into develop Aug 12, 2025
4 checks passed
@seongjunnoh seongjunnoh deleted the hotfix/#204-add-flag-request-in-all-search-api branch August 12, 2025 17:44
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-254] [hotfix] 검색 api 의 모든 요청에 검색중/검색완료 를 구분하는 flag 추가

2 participants