Skip to content

Conversation

@kmw10693
Copy link
Contributor

@kmw10693 kmw10693 commented Dec 23, 2025

📝작업 내용

  • 키워드 검색 API 추가

Summary by CodeRabbit

  • 새로운 기능

    • 최근 검색 기록 기능이 추가되었습니다.
    • 최근 검색어 목록을 조회할 수 있습니다 (기본값: 20개).
    • 개별 검색어를 삭제할 수 있습니다.
    • 모든 최근 검색어를 일괄 삭제할 수 있습니다.
    • 공지사항 검색 시 검색어가 자동으로 저장됩니다.
  • 테스트

    • 관련 컨트롤러의 엔드포인트 테스트 및 API 문서화 테스트가 추가되었습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@kmw10693 kmw10693 requested a review from david-parkk December 23, 2025 16:17
@kmw10693 kmw10693 self-assigned this Dec 23, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 23, 2025

Warning

Rate limit exceeded

@kmw10693 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 14 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 13eb57e and 3760223.

📒 Files selected for processing (2)
  • src/main/java/ku_rum/backend/domain/search/presentation/RecentSearchController.java
  • src/test/java/ku_rum/backend/domain/search/presentation/RecentSearchControllerTest.java

Walkthrough

공지사항 검색 시 입력 키워드를 최근검색으로 저장하는 기능을 추가했습니다. 새로운 RecentSearch 엔티티·리포지토리·서비스·컨트롤러를 도입하고, NoticeService.searchByKeyword에서 RecentSearchService.save(keyword)를 호출하도록 통합했습니다.

Changes

Cohort / File(s) 변경 요약
도메인: RecentSearch 엔티티
src/main/java/ku_rum/backend/domain/search/domain/RecentSearch.java
신규 JPA 엔티티 추가: id, userId, keyword, createdAt, updatedAt, (user_id, keyword) 유니크 제약 및 인덱스. 생성자와 touch() 메서드 구현.
리포지토리: RecentSearchRepository
src/main/java/ku_rum/backend/domain/search/domain/repository/RecentSearchRepository.java
JpaRepository 기반 인터페이스 추가: findByUserIdAndKeyword(...), findByUserIdOrderByUpdatedAtDesc(..., Pageable), deleteByIdAndUserId(...), deleteByUserId(...).
서비스: RecentSearchService
src/main/java/ku_rum/backend/domain/search/application/RecentSearchService.java
신규 서비스 추가: save(String keyword) (정규화·중복 처리·최대 20개 유지), list(int limit)(읽기 전용), delete(Long id), deleteAll(). 트랜잭션 적용.
컨트롤러: RecentSearchController
src/main/java/ku_rum/backend/domain/search/presentation/RecentSearchController.java
REST 컨트롤러 추가: GET /api/v1/notices/searches/recent(limit), DELETE /{id}, DELETE /all.
통합: NoticeService 변경
src/main/java/ku_rum/backend/domain/notice/application/NoticeService.java
RecentSearchService 주입 및 searchByKeyword에서 recentSearchService.save(keyword) 호출 추가.
프레젠테이션: NoticeController
src/main/java/ku_rum/backend/domain/notice/presentation/NoticeController.java
포맷(공백 라인) 변경 — 로직 불변.
테스트: RecentSearchControllerTest
src/test/java/ku_rum/backend/domain/search/presentation/RecentSearchControllerTest.java
컨트롤러 엔드포인트(목록/단건 삭제/전체 삭제) 테스트 및 REST Docs 문서화 추가.

Sequence Diagram(s)

sequenceDiagram
    actor User as 사용자
    participant NoticeController as NoticeController
    participant NoticeService as NoticeService
    participant RecentSearchService as RecentSearchService
    participant NoticeRepo as NoticeRepository
    participant RecentSearchRepo as RecentSearchRepository
    participant UserService as UserService
    participant DB as Database

    User->>NoticeController: GET /api/v1/notices/search?keyword=test
    NoticeController->>NoticeService: searchByKeyword("test")
    NoticeService->>RecentSearchService: save("test")
    RecentSearchService->>UserService: getCurrentUser()
    UserService-->>RecentSearchService: User(id)
    RecentSearchService->>RecentSearchRepo: findByUserIdAndKeyword(userId,"test")
    RecentSearchRepo->>DB: SELECT recent_search WHERE user_id=? AND keyword=?
    DB-->>RecentSearchRepo: existing?/none
    alt existing
        RecentSearchService->>RecentSearchRepo: save(touchedRecentSearch)
    else new
        RecentSearchService->>RecentSearchRepo: save(newRecentSearch)
    end
    RecentSearchService->>RecentSearchRepo: (if >20) delete oldest
    NoticeService->>NoticeRepo: findByKeyword("test")
    NoticeRepo->>DB: SELECT notice WHERE keyword LIKE ?
    DB-->>NoticeRepo: notice list
    NoticeService-->>NoticeController: results
    NoticeController-->>User: BaseResponse(200, results)
Loading
sequenceDiagram
    actor User as 사용자
    participant RecentSearchController as RecentSearchController
    participant RecentSearchService as RecentSearchService
    participant UserService as UserService
    participant RecentSearchRepo as RecentSearchRepository
    participant DB as Database

    User->>RecentSearchController: GET /api/v1/notices/searches/recent?limit=20
    RecentSearchController->>RecentSearchService: list(20)
    RecentSearchService->>UserService: getCurrentUser()
    UserService-->>RecentSearchService: User(id)
    RecentSearchService->>RecentSearchRepo: findByUserIdOrderByUpdatedAtDesc(userId, pageable)
    RecentSearchRepo->>DB: SELECT ... ORDER BY updated_at DESC LIMIT ?
    DB-->>RecentSearchRepo: recent searches
    RecentSearchService-->>RecentSearchController: List<RecentSearch>
    RecentSearchController-->>User: BaseResponse(200, 목록)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • david-parkk

Poem

🐰✨ 키워드를 남기며 달려왔네
스무 개 기억엔 소중한 발자국
검색할 때마다 시간이 반짝여
오래된 건 정리하고 새로움만 쌓아
토끼처럼 살금살금, 기록은 반짝🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명은 템플릿의 필수 섹션(연관된 이슈, 작업 내용, 작업 상세 내용, 리뷰 요구사항)이 대부분 누락되어 있습니다. 연관된 이슈 번호, 상세한 작업 설명, 그리고 리뷰어를 위한 특별 요구사항을 추가하여 템플릿을 완성해 주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 최근 검색 기능 구현을 포함한 전반적인 변경 사항을 명확하게 요약하고 있습니다.

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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 (4)
src/main/java/ku_rum/backend/domain/search/domain/repository/RecentSearchRepository.java (1)

16-16: 삭제 메서드의 반환값 활용 고려

deleteByIdAndUserIddeleteByUserId 메서드가 삭제된 레코드 수를 반환하지만, 서비스에서 이 값을 사용하지 않고 있습니다. 삭제 성공 여부를 확인하려면 반환값을 검증하는 것을 고려해보세요.

Also applies to: 18-18

src/main/java/ku_rum/backend/domain/search/domain/RecentSearch.java (1)

29-30: User 엔티티 관계 설계 확인

userIdLong 타입으로 저장하고 있습니다. @ManyToOne으로 User 엔티티와 직접 관계를 맺지 않는 설계인데, 이는 구조를 단순하게 유지하는 장점이 있지만 다음 사항을 고려해야 합니다:

  • 데이터베이스 레벨의 참조 무결성(FK 제약조건) 부재
  • 사용자 삭제 시 고아(orphaned) 레코드 발생 가능성

현재 설계가 의도된 것이라면 문제없지만, 향후 사용자 삭제 시 관련 검색 기록도 함께 정리하는 로직이 필요할 수 있습니다.

src/main/java/ku_rum/backend/domain/search/application/RecentSearchService.java (2)

40-49: 성능 최적화 고려: 매 저장마다 조회 및 삭제 수행

검색이 발생할 때마다 MAX_RECENT_SEARCH + 1개의 레코드를 조회하고 초과분을 삭제하는 로직이 실행됩니다. 검색 빈도가 높은 경우 성능에 영향을 줄 수 있습니다.

다음과 같은 최적화 방안을 고려해보세요:

  1. 조건부 정리: 새 레코드가 추가될 때만 정리 수행
  2. 배치 정리: 별도의 스케줄러로 주기적으로 정리
  3. 카운트 확인: 먼저 count 쿼리로 초과 여부 확인 후 필요시에만 삭제
🔎 조건부 정리 예시
+        boolean isNewRecord = repository.findByUserIdAndKeyword(user.getId(), normalized).isEmpty();
+        
         repository.findByUserIdAndKeyword(user.getId(), normalized)
                 .ifPresentOrElse(
                         rs -> rs.touch(now),
                         () -> repository.save(
                                 new RecentSearch(user.getId(), normalized, now)
                         )
                 );

-        // 최대 개수 초과 시 오래된 것 삭제
-        List<RecentSearch> list =
-                repository.findByUserIdOrderByUpdatedAtDesc(
-                        user.getId(),
-                        PageRequest.of(0, MAX_RECENT_SEARCH + 1)
-                );
-
-        if (list.size() > MAX_RECENT_SEARCH) {
-            repository.deleteAll(list.subList(MAX_RECENT_SEARCH, list.size()));
-        }
+        // 새 레코드가 추가된 경우에만 정리
+        if (isNewRecord) {
+            List<RecentSearch> list =
+                    repository.findByUserIdOrderByUpdatedAtDesc(
+                            user.getId(),
+                            PageRequest.of(0, MAX_RECENT_SEARCH + 1)
+                    );
+
+            if (list.size() > MAX_RECENT_SEARCH) {
+                repository.deleteAll(list.subList(MAX_RECENT_SEARCH, list.size()));
+            }
+        }

61-71: 삭제 작업 결과 확인

삭제 메서드들이 실제로 레코드를 삭제했는지 확인하지 않습니다. 리포지토리 메서드의 반환값(삭제된 레코드 수)을 확인하면 사용자에게 더 명확한 피드백을 제공할 수 있습니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f416a9c and 96020e2.

📒 Files selected for processing (7)
  • src/main/java/ku_rum/backend/domain/notice/application/NoticeService.java
  • src/main/java/ku_rum/backend/domain/notice/presentation/NoticeController.java
  • src/main/java/ku_rum/backend/domain/search/application/RecentSearchService.java
  • src/main/java/ku_rum/backend/domain/search/domain/RecentSearch.java
  • src/main/java/ku_rum/backend/domain/search/domain/repository/RecentSearchRepository.java
  • src/main/java/ku_rum/backend/domain/search/presentation/RecentSearchController.java
  • src/test/java/ku_rum/backend/domain/search/presentation/RecentSearchControllerTest.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/ku_rum/backend/domain/search/application/RecentSearchService.java (1)
src/main/java/ku_rum/backend/domain/notice/application/NoticeService.java (1)
  • Service (29-94)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (3)
src/main/java/ku_rum/backend/domain/notice/presentation/NoticeController.java (1)

56-57: 코드 변경사항 확인 완료

공백 라인 추가만 있는 포맷팅 변경으로 기능에 영향이 없습니다.

src/main/java/ku_rum/backend/domain/notice/application/NoticeService.java (1)

12-12: 최근 검색어 저장 기능 통합 확인

RecentSearchService를 통해 검색 키워드를 저장하는 로직이 올바르게 추가되었습니다. save 메서드가 자체 트랜잭션을 가지고 있어 트랜잭션 경계도 적절합니다.

Also applies to: 40-40, 89-90

src/test/java/ku_rum/backend/domain/search/presentation/RecentSearchControllerTest.java (1)

50-149: 테스트 커버리지 우수

모든 컨트롤러 엔드포인트에 대한 테스트가 잘 작성되었습니다. REST Docs 통합과 적절한 모킹이 적용되어 있습니다.

@github-actions
Copy link

github-actions bot commented Dec 23, 2025

Test Results

 38 files   38 suites   12s ⏱️
155 tests 155 ✅ 0 💤 0 ❌
156 runs  156 ✅ 0 💤 0 ❌

Results for commit 3760223.

♻️ This comment has been updated with latest results.


@Transactional
public void save(String keyword) {
String normalized = keyword == null ? "" : keyword.trim();
Copy link
Contributor

Choose a reason for hiding this comment

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

? 연산자 지양해봐요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

private으로 뺐습니다!

import java.util.List;

@RestController
@RequestMapping("/api/v1/recent-searches")
Copy link
Contributor

Choose a reason for hiding this comment

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

엔드포인트 prefix가 적합하지 않아보입니다

Copy link
Contributor Author

Choose a reason for hiding this comment

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

/api/v1/notices/searches/recent 로 수정했습니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/main/java/ku_rum/backend/domain/search/presentation/RecentSearchController.java (2)

19-20: limit 파라미터 검증 미반영

이전 리뷰에서 지적된 limit 파라미터의 입력 검증이 아직 추가되지 않았습니다. 음수, 0, 또는 매우 큰 값에 대한 검증을 추가해주세요.


11-14: 입력 검증을 위한 @Validated 어노테이션 추가 필요

컨트롤러에서 요청 파라미터 검증을 수행하려면 클래스 레벨에 @Validated 어노테이션이 필요합니다. 이전 리뷰에서 제안된 limit 파라미터 검증과 아래에서 제안하는 id 파라미터 검증이 동작하려면 반드시 추가되어야 합니다.

🔎 제안하는 수정사항
+import org.springframework.validation.annotation.Validated;
+
 @RestController
 @RequestMapping("/api/v1/notices/searches/recent")
 @RequiredArgsConstructor
+@Validated
 public class RecentSearchController {
🧹 Nitpick comments (2)
src/main/java/ku_rum/backend/domain/search/presentation/RecentSearchController.java (2)

24-28: id 파라미터 검증 추가 권장

id 파라미터에 대한 검증이 없습니다. 음수 또는 0과 같은 유효하지 않은 값이 전달될 수 있습니다. 컨트롤러 레벨에서 입력 검증을 추가하는 것이 좋습니다.

🔎 제안하는 수정사항
+import jakarta.validation.constraints.Positive;
+
     @DeleteMapping("/{id}")
-    public BaseResponse<Void> delete(@PathVariable Long id) {
+    public BaseResponse<Void> delete(@PathVariable @Positive Long id) {
         recentSearchService.delete(id);
         return BaseResponse.ok(null);
     }

12-12: 엔드포인트 경로 재검토

이전 리뷰에서 david-parkk님이 지적하신 엔드포인트 prefix 관련 의견이 반영되지 않았습니다. 현재 경로 /api/v1/notices/searches/recent가 적합한지 팀 내에서 논의 후 결정해주세요.

대안 예시:

  • /api/v1/notices/recent-searches
  • /api/v1/recent-searches (공지사항 특화가 아닌 경우)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96020e2 and 13eb57e.

📒 Files selected for processing (2)
  • src/main/java/ku_rum/backend/domain/search/application/RecentSearchService.java
  • src/main/java/ku_rum/backend/domain/search/presentation/RecentSearchController.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/ku_rum/backend/domain/search/application/RecentSearchService.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
🔇 Additional comments (1)
src/main/java/ku_rum/backend/domain/search/presentation/RecentSearchController.java (1)

30-34: deleteAll 엔드포인트는 안전하게 구현되어 있습니다

repository.deleteByUserId(user.getId())를 통해 현재 사용자의 최근 검색 기록만 삭제하며, SecurityConfig에서 전역적으로 인증을 필수로 요구하므로 사용자별 격리가 제대로 처리됩니다. 관리자 권한 체크는 사용자 개별 데이터 삭제 작업이므로 불필요합니다.

@kmw10693 kmw10693 merged commit 9cef170 into develop Dec 23, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants