Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ public class BookQueryController {
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!

@Parameter(hidden = true) @UserId final Long userId) {
return BaseResponse.ok(BookSearchListResponse.of(bookSearchUseCase.searchBooks(keyword, page,userId), page));
return BaseResponse.ok(BookSearchListResponse.of(bookSearchUseCase.searchBooks(keyword, page, userId, isFinalized), page));
}

//책 상세검색 결과 조회
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@

public interface BookSearchUseCase {

NaverBookParseResult searchBooks(String keyword, int page,Long userId);
BookDetailSearchResult searchDetailBooks(String isbn,Long userId);
NaverBookParseResult searchBooks(String keyword, int page, Long userId, boolean isFinalized);
BookDetailSearchResult searchDetailBooks(String isbn, Long userId);
Comment on lines +8 to +9
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.

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import konkuk.thip.common.exception.BusinessException;
import konkuk.thip.feed.application.port.out.FeedQueryPort;
import konkuk.thip.recentSearch.application.port.out.RecentSearchCommandPort;
import konkuk.thip.recentSearch.domain.RecentSearch;
import konkuk.thip.recentSearch.application.service.manager.RecentSearchCreateManager;
import konkuk.thip.room.application.port.out.RoomQueryPort;
import konkuk.thip.user.application.port.out.UserCommandPort;
import konkuk.thip.user.application.port.out.UserQueryPort;
Expand Down Expand Up @@ -43,9 +43,11 @@ public class BookSearchService implements BookSearchUseCase {
private final UserCommandPort userCommandPort;
private final BookRedisCommandPort bookRedisCommandPort;

private final RecentSearchCreateManager recentSearchCreateManager;

@Override
@Transactional
public NaverBookParseResult searchBooks(String keyword, int page, Long userId) {
public NaverBookParseResult searchBooks(String keyword, int page, Long userId, boolean isFinalized) {

if (keyword == null || keyword.isBlank()) {
throw new BusinessException(BOOK_KEYWORD_REQUIRED);
Expand All @@ -65,13 +67,8 @@ public NaverBookParseResult searchBooks(String keyword, int page, Long userId) {
throw new BusinessException(BOOK_SEARCH_PAGE_OUT_OF_RANGE);
}

//최근검색어 추가
RecentSearch recentSearch = RecentSearch.builder()
.searchTerm(keyword)
.type(BOOK_SEARCH)
.userId(userId)
.build();
recentSearchCommandPort.save(recentSearch);
// 최근 검색어 저장
recentSearchCreateManager.saveRecentSearchByUser(userId, keyword, BOOK_SEARCH, isFinalized);

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ public class RecentSearchCreateManager {
private final RecentSearchCommandPort recentSearchCommandPort;
private final RecentSearchQueryPort recentSearchQueryPort;

public void saveRecentSearchByUser(Long userId, String keyword, RecentSearchType type) {
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)) // 없으면 새로 저장
);
}
Comment on lines +17 to +26
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.


// 동일 조건 (userId + keyword + type) 검색 기록이 이미 있는지 확인
recentSearchQueryPort.findRecentSearchByKeywordAndUserId(keyword, userId, type)
.ifPresentOrElse(
recentSearchCommandPort::touch, // 있으면 modifiedAt 갱신
() -> recentSearchCommandPort.save(RecentSearch.withoutId(keyword, type, userId)) // 없으면 새로 저장
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,19 @@ public class RoomQueryController {

@Operation(
summary = "방 검색",
description = "키워드, 카테고리, 정렬 방식, 페이지 번호를 기준으로 방을 검색합니다."
description = "키워드, 카테고리, 정렬 방식, 페이지 번호를 기준으로 방을 검색합니다. 아직 수정중입니다. 일단 request param 만 수정한 상태입니다."
)
@ExceptionDescription(ROOM_SEARCH)
@GetMapping("/rooms/search")
public BaseResponse<RoomSearchResponse> searchRooms(
@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 = "페이지 번호", example = "1") @RequestParam("page") final int page
@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
@Parameter(description = "페이지 번호", example = "1") @RequestParam("page") final int page,
@Parameter(hidden = true) @UserId final Long userId
) {
return BaseResponse.ok(roomSearchUseCase.searchRoom(keyword, category, sort, page));
return BaseResponse.ok(roomSearchUseCase.searchRoom(keyword, category, sort, page, isFinalized, userId));
}

@Operation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

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, Long userId);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package konkuk.thip.room.application.service;

import konkuk.thip.common.exception.BusinessException;
import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType;
import konkuk.thip.recentSearch.application.service.manager.RecentSearchCreateManager;
import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse;
import konkuk.thip.room.domain.Category;
import konkuk.thip.room.adapter.out.persistence.RoomSearchSortParam;
import konkuk.thip.room.application.port.in.RoomSearchUseCase;
import konkuk.thip.room.application.port.out.RoomQueryPort;
import konkuk.thip.room.domain.Category;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
Expand All @@ -14,7 +16,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static konkuk.thip.common.exception.code.ErrorCode.CATEGORY_NOT_FOUND;
import static konkuk.thip.common.exception.code.ErrorCode.INVALID_ROOM_SEARCH_SORT;

@Service
Expand All @@ -25,9 +26,11 @@ public class RoomSearchService implements RoomSearchUseCase {

private final RoomQueryPort roomQueryPort;

private final RecentSearchCreateManager recentSearchCreateManager;

@Override
@Transactional(readOnly = true)
public RoomSearchResponse searchRoom(String keyword, String category, String sort, int page) {
@Transactional // <- 최근 검색 저장으로 인한 트랜잭션
public RoomSearchResponse searchRoom(String keyword, String category, String sort, int page, boolean isFinalized, Long userId) {
// 1. validation
String sortVal = validateSort(sort);
String categoryVal = validateCategory(category);
Expand All @@ -39,6 +42,9 @@ public RoomSearchResponse searchRoom(String keyword, String category, String sor
// 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.

recentSearchCreateManager.saveRecentSearchByUser(userId, keyword, RecentSearchType.ROOM_SEARCH, isFinalized);

// 4. response 구성
return new RoomSearchResponse(
result.getContent(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,10 @@ public BaseResponse<UserIsFollowingResponse> checkIsFollowing(
@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.

@Parameter(description = "단일 검색 결과 페이지 크기 (1~30) / default : 30", example = "30") @RequestParam(required = false, defaultValue = "30") @Min(1) @Max(30) final Integer size,
@Parameter(hidden = true) @UserId final Long userId) {
return BaseResponse.ok(userSearchUsecase.searchUsers(UserSearchQuery.of(keyword, userId, size)));
return BaseResponse.ok(userSearchUsecase.searchUsers(UserSearchQuery.of(keyword, userId, size, isFinalized)));
}

@Operation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
public record UserSearchQuery(
String keyword,
Long userId,
Integer size
Integer size,
boolean isFinalized
) {
public static UserSearchQuery of(String keyword, Long userId, Integer size) {
return new UserSearchQuery(keyword, userId, size);
public static UserSearchQuery of(String keyword, Long userId, Integer size, boolean isFinalized) {
return new UserSearchQuery(keyword, userId, size, isFinalized);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ public UserSearchResponse searchUsers(UserSearchQuery userSearchQuery) {
userSearchQuery.size()
));

// 최근 검색어 저장
recentSearchCreateManager.saveRecentSearchByUser(userSearchQuery.userId(), userSearchQuery.keyword(), RecentSearchType.USER_SEARCH);

recentSearchCreateManager.saveRecentSearchByUser(userSearchQuery.userId(), userSearchQuery.keyword(), RecentSearchType.USER_SEARCH, userSearchQuery.isFinalized());

Comment on lines +32 to +33
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.

return UserSearchResponse.of(userDtoList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ void searchBooks_success() throws Exception {
.header("Authorization", "Bearer " + testToken)
.param("keyword", keyword)
.param("page", String.valueOf(page))
.param("isFinalized", String.valueOf(false))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.isSuccess").value(true))
Expand All @@ -115,6 +116,7 @@ void searchBooks_pageOutOfRange() throws Exception {
.header("Authorization", "Bearer " + testToken)
.param("keyword", keyword)
.param("page", String.valueOf(page))
.param("isFinalized", String.valueOf(false))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.isSuccess").value(false))
Expand All @@ -136,6 +138,7 @@ void searchBooks_keywordMissing_badRequest() throws Exception {
.header("Authorization", "Bearer " + testToken)
.param("keyword", keyword)
.param("page", String.valueOf(page))
.param("isFinalized", String.valueOf(false))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code").value(ErrorCode.BOOK_KEYWORD_REQUIRED.getCode()))
Expand All @@ -155,6 +158,7 @@ void searchBooks_pageInvalid_badRequest() throws Exception {
.header("Authorization", "Bearer " + testToken)
.param("keyword", keyword)
.param("page", String.valueOf(page))
.param("isFinalized", String.valueOf(false))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code").value(ErrorCode.BOOK_PAGE_NUMBER_INVALID.getCode()))
Expand All @@ -175,6 +179,7 @@ void searchBooks_savesRecentSearch() throws Exception {
.header("Authorization", "Bearer " + testToken)
.param("keyword", keyword)
.param("page", String.valueOf(page))
.param("isFinalized", String.valueOf(true)) // 검색 완료일 경우 : 최근검색어로 저장된다
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ void search_keyword_and_sort_deadline() throws Exception {
.requestAttr("userId", 1L)
.param("keyword", "과학")
.param("sort", "deadline")
.param("isFinalized", String.valueOf(false))
.param("page", "1"));

//then
Expand Down Expand Up @@ -233,6 +234,7 @@ void search_keyword_and_sort_member_count() throws Exception {
.requestAttr("userId", 1L)
.param("keyword", "과학")
.param("sort", "memberCount")
.param("isFinalized", String.valueOf(false))
.param("page", "1"));

//then
Expand Down Expand Up @@ -285,6 +287,7 @@ void search_category_and_sort_deadline() throws Exception {
.requestAttr("userId", 1L)
.param("category", "과학·IT")
.param("sort", "deadline")
.param("isFinalized", String.valueOf(false))
.param("page", "1"));

//then
Expand Down Expand Up @@ -324,6 +327,7 @@ void search_sort_deadline() throws Exception {
ResultActions result = mockMvc.perform(get("/rooms/search")
.requestAttr("userId", 1L)
.param("sort", "deadline")
.param("isFinalized", String.valueOf(false))
.param("page", "1"));

//then
Expand Down Expand Up @@ -366,6 +370,7 @@ void search_keyword_and_category() throws Exception {
.param("keyword", "과학")
.param("category", Category.SCIENCE_IT.getValue())
.param("sort", "deadline")
.param("isFinalized", String.valueOf(false))
.param("page", "1"));

// then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ void searchUsersAndSaveRecentSearch() throws Exception {
.param("keyword", keyword)
.requestAttr("userId", currentUserId)
.param("size", "10")

.param("isFinalized", String.valueOf(true)) // 검색 완료 -> 최근 검색어 저장
);

// then: 검색 결과 검증
Expand All @@ -91,4 +91,4 @@ void searchUsersAndSaveRecentSearch() throws Exception {
assertEquals(RecentSearchType.USER_SEARCH, saved.getType());
assertEquals(currentUserId, saved.getUserJpaEntity().getUserId());
}
}
}