Skip to content
Open
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 @@ -80,6 +80,7 @@ public ApiResponse<SearchResultResponseDTO> searchStars(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
starCommandService.saveRecentSearches(userId, query);
SearchResultResponseDTO result = starQueryService.searchStarsV2(query, userId, page, size);
return ApiResponse.onSuccess(result);
Comment on lines +83 to 85

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Avoid saving blank queries; reconsider side-effect in GET.

  • Guard against blank q to prevent storing empty recent searches.
  • Saving state in a GET handler is debatable REST-wise. If possible, consider emitting an async domain event instead of directly updating state here.

Suggested guard:

-        starCommandService.saveRecentSearches(userId, query);
+        if (query != null && !query.isBlank()) {
+            starCommandService.saveRecentSearches(userId, query);
+        }

Confirm there are no other code paths persisting blank queries:


🏁 Script executed:

#!/bin/bash
rg -n --pretty -e 'saveRecentSearch(es)?\s*\(' -e '@GetMapping\(\"/search\"' -A3

Length of output: 240


🏁 Script executed:

#!/bin/bash
set -e

# Find all calls to saveRecentSearches in the codebase
rg -n --pretty "saveRecentSearches"

echo "-----"

# Find all @GetMapping annotations that include "search" in the path, with context
rg -n --pretty '@GetMapping.*search' -A3

Length of output: 1778


Guard blank search queries & reconsider side-effect in GET

We searched the entire codebase and found only one direct call to saveRecentSearches in a GET handler (StarV2Controller). To prevent empty keywords from being stored—and to keep this endpoint free of side-effects—please apply the following patch:

• File: src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java

--- a/src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java
+++ b/src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java
@@ public ApiResponse<SearchResultResponseDTO> searchStars(
-        starCommandService.saveRecentSearches(userId, query);
+        if (query != null && !query.isBlank()) {
+            starCommandService.saveRecentSearches(userId, query);
+        }

Also consider emitting an asynchronous domain event for “recent search” recording instead of performing this side-effect directly within a GET handler.

📝 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
starCommandService.saveRecentSearches(userId, query);
SearchResultResponseDTO result = starQueryService.searchStarsV2(query, userId, page, size);
return ApiResponse.onSuccess(result);
if (query != null && !query.isBlank()) {
starCommandService.saveRecentSearches(userId, query);
}
SearchResultResponseDTO result = starQueryService.searchStarsV2(query, userId, page, size);
return ApiResponse.onSuccess(result);
🤖 Prompt for AI Agents
In src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java
around lines 83 to 85, the method saveRecentSearches is called directly in a GET
handler without checking if the search query is blank, causing empty keywords to
be stored and introducing side-effects in a GET request. Fix this by adding a
guard clause to check if the query string is not blank before calling
saveRecentSearches. Additionally, consider refactoring this side-effect to emit
an asynchronous domain event for recording recent searches instead of performing
it synchronously within the GET handler.

}
Expand All @@ -93,4 +94,11 @@ public ApiResponse<List<String>> getAutoComplete(
List<String> suggestions = starQueryService.getAutoComplete(query, userId, 5);
return ApiResponse.onSuccess(suggestions);
}

@Operation(summary = "최근 검색어 조회", description = "사용자 최근 검색어 10개 조회")
@GetMapping("/recent-searches")
public ApiResponse<List<String>> getRecentSearches(@AuthUser Long userId) {
List<String> recentSearches = starQueryService.getRecentSearches(userId);
return ApiResponse.onSuccess(recentSearches);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public List<String> getAutoComplete(String query, Long userId, int size) {
}
}

// 새로운 검색 메서드 (다른 용도로 사용)
// 검색 메서드 (더 넓은 범위 검색)
@Cacheable(value = "starSearch", key = "#userId + '_' + #keyword + '_' + #page + '_' + #size")
public List<StarSearchDocument> searchStarsSimple(Long userId, String keyword, int page, int size) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.team_nebula.nebula.domain.star.dto.response.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;
import java.util.UUID;

public interface StarCommandService {
Expand All @@ -23,4 +24,9 @@ public interface StarCommandService {
public AddBookMarkResponseDTO addBookMark(Long userId, MultipartFile htmlFile, String title, String siteUrl);

public CreateStarResponseDTO createStar(Long userId, CreateStarRequestDTO requestDTO);

public void saveRecentSearches(Long userId, String keyword);

public void deleteRecentSearch(Long userId, String keyword);

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,24 @@
import com.team_nebula.nebula.global.apipayload.exception.GeneralException;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.Duration;
import java.time.LocalDateTime;

import java.time.OffsetDateTime;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log;

Comment on lines +48 to +49

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Incorrect logger import and usage; use a proper class logger.

import static ...ReactiveNeo4jClient.log; is not appropriate. Also, current log.error(e, "...") order won’t log stack traces.

  • Add Lombok @Slf4j (or define a Logger).
  • Remove the static import.
  • Use log.error("...", e).

Within this file’s changed lines, remove the static import:

-import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log;

And update error calls in the methods (see diffs on those ranges).

📝 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
import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log;
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java
around lines 48 to 49, remove the incorrect static import of
ReactiveNeo4jClient.log. Instead, add the Lombok @Slf4j annotation to the class
to define a proper logger. Then, update all log.error calls in the file to use
the correct parameter order: pass the log message first and the exception
second, like log.error("message", e), to ensure stack traces are logged
properly.

@Service
@RequiredArgsConstructor
@Transactional
Expand All @@ -59,6 +65,9 @@ public class StarCommandServiceImpl implements StarCommandService {
private final FaviconRepository faviconRepository;
private final ApplicationEventPublisher eventPublisher;

private final RedisTemplate<String, Object> redisTemplate;
private static final int MAX_RECENT_SEARCHES = 10;


@Override
public CreateStarResponseDTO createFirstStar(Long userId, MultipartFile htmlFile, String title, String siteUrl){
Expand Down Expand Up @@ -314,5 +323,32 @@ public CreateStarResponseDTO createStar(Long userId, CreateStarRequestDTO reques
.build();
}

@Override
@Async
public void saveRecentSearches(Long userId, String keyword){
try {
String key = "recent_search:" + userId;
String normalizedKeyword = keyword.trim().toLowerCase();

// 중복 제거
redisTemplate.opsForList().remove(key, 0, normalizedKeyword);
redisTemplate.opsForList().leftPush(key, normalizedKeyword);
redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1);
redisTemplate.expire(key, Duration.ofDays(30));
}
catch (Exception e) {
log.error(e, "Failed to save recent search");
}
}
Comment on lines +326 to +342

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden save logic: null/blank guard, locale-safe lowercasing, correct logging, and avoid unnecessary DB transactions.

  • Guard against null/blank to avoid storing empty entries.
  • Use Locale.ROOT for consistent lowercasing.
  • Fix log.error argument order.
  • Since class-level @Transactional applies, consider overriding with @Transactional(propagation = Propagation.NOT_SUPPORTED) here to avoid opening a JPA transaction for Redis I/O.

Proposed changes:

 @Override
 @Async
 public void saveRecentSearches(Long userId, String keyword){
     try {
-        String key = "recent_search:" + userId;
-        String normalizedKeyword = keyword.trim().toLowerCase();
+        if (keyword == null) return;
+        String trimmed = keyword.trim();
+        if (trimmed.isEmpty()) return;
+        String key = "recent_search:" + userId;
+        String normalizedKeyword = trimmed.toLowerCase(java.util.Locale.ROOT);

         // 중복 제거
         redisTemplate.opsForList().remove(key, 0, normalizedKeyword);
         redisTemplate.opsForList().leftPush(key, normalizedKeyword);
         redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1);
         redisTemplate.expire(key, java.time.Duration.ofDays(30));
     }
     catch (Exception e) {
-        log.error(e, "Failed to save recent search");
+        log.error("Failed to save recent search", e);
     }
 }
📝 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
@Override
@Async
public void saveRecentSearches(Long userId, String keyword){
try {
String key = "recent_search:" + userId;
String normalizedKeyword = keyword.trim().toLowerCase();
// 중복 제거
redisTemplate.opsForList().remove(key, 0, normalizedKeyword);
redisTemplate.opsForList().leftPush(key, normalizedKeyword);
redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1);
redisTemplate.expire(key, Duration.ofDays(30));
}
catch (Exception e) {
log.error(e, "Failed to save recent search");
}
}
@Override
@Async
public void saveRecentSearches(Long userId, String keyword){
try {
if (keyword == null) return;
String trimmed = keyword.trim();
if (trimmed.isEmpty()) return;
String key = "recent_search:" + userId;
String normalizedKeyword = trimmed.toLowerCase(java.util.Locale.ROOT);
// 중복 제거
redisTemplate.opsForList().remove(key, 0, normalizedKeyword);
redisTemplate.opsForList().leftPush(key, normalizedKeyword);
redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1);
redisTemplate.expire(key, java.time.Duration.ofDays(30));
}
catch (Exception e) {
log.error("Failed to save recent search", e);
}
}
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java
around lines 326 to 342, enhance saveRecentSearches by adding a null or blank
check for keyword to prevent storing empty entries, use
keyword.toLowerCase(Locale.ROOT) for locale-safe lowercasing, correct the
log.error call to have the message first and exception second, and add
@Transactional(propagation = Propagation.NOT_SUPPORTED) to avoid opening
unnecessary JPA transactions during Redis operations.


@Override
public void deleteRecentSearch(Long userId, String keyword) {
try {
String key = "recent_search:" + userId;
redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase());
} catch (Exception e) {
log.error(e, "Failed to delete recent search");
}
}
Comment on lines +326 to +352

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Optional: prevent JPA transaction for Redis-only methods and add @slf4j.

Outside these changed lines, consider:

  • Annotate class with @Slf4j.
  • Annotate methods with @Transactional(propagation = Propagation.NOT_SUPPORTED) to avoid opening a JPA transaction for Redis ops.

Example (outside selected lines):

import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Propagation;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class StarCommandServiceImpl implements StarCommandService {

    @Override
    @Async
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void saveRecentSearches(Long userId, String keyword) { ... }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void deleteRecentSearch(Long userId, String keyword) { ... }
}

Check that async execution is enabled and key usage is consistent:


🏁 Script executed:

#!/bin/bash
echo "Search for @EnableAsync (required for @Async to work via proxies):"
rg -n '@EnableAsync'

echo ""
echo "Check consistent Redis key usage for recent searches:"
rg -n --pretty 'recent_search:'

Length of output: 604


Add @slf4j and mark Redis‐only methods as non‐transactional

To avoid opening unnecessary JPA transactions for Redis operations and enable logging in this class:

• Annotate StarCommandServiceImpl with @Slf4j (import lombok.extern.slf4j.Slf4j).
• Add @Transactional(propagation = Propagation.NOT_SUPPORTED) (import org.springframework.transaction.annotation.*) to both Redis‐only methods.

Suggested diff in src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java:

@@
-import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+import lombok.extern.slf4j.Slf4j;

 @Slf4j
 @Service
 @RequiredArgsConstructor
 @Transactional
 public class StarCommandServiceImpl implements StarCommandService {
 
     @Override
     @Async
-    public void saveRecentSearches(Long userId, String keyword){
+    @Transactional(propagation = Propagation.NOT_SUPPORTED)
+    public void saveRecentSearches(Long userId, String keyword) {
         try {
             String key = "recent_search:" + userId;
             String normalizedKeyword = keyword.trim().toLowerCase();
@@
     @Override
-    public void deleteRecentSearch(Long userId, String keyword) {
+    @Transactional(propagation = Propagation.NOT_SUPPORTED)
+    public void deleteRecentSearch(Long userId, String keyword) {
         try {
             String key = "recent_search:" + userId;
             redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase());
📝 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
@Override
@Async
public void saveRecentSearches(Long userId, String keyword){
try {
String key = "recent_search:" + userId;
String normalizedKeyword = keyword.trim().toLowerCase();
// 중복 제거
redisTemplate.opsForList().remove(key, 0, normalizedKeyword);
redisTemplate.opsForList().leftPush(key, normalizedKeyword);
redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1);
redisTemplate.expire(key, Duration.ofDays(30));
}
catch (Exception e) {
log.error(e, "Failed to save recent search");
}
}
@Override
public void deleteRecentSearch(Long userId, String keyword) {
try {
String key = "recent_search:" + userId;
redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase());
} catch (Exception e) {
log.error(e, "Failed to delete recent search");
}
}
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class StarCommandServiceImpl implements StarCommandService {
@Override
@Async
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveRecentSearches(Long userId, String keyword) {
try {
String key = "recent_search:" + userId;
String normalizedKeyword = keyword.trim().toLowerCase();
// 중복 제거
redisTemplate.opsForList().remove(key, 0, normalizedKeyword);
redisTemplate.opsForList().leftPush(key, normalizedKeyword);
redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1);
redisTemplate.expire(key, Duration.ofDays(30));
} catch (Exception e) {
log.error(e, "Failed to save recent search");
}
}
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void deleteRecentSearch(Long userId, String keyword) {
try {
String key = "recent_search:" + userId;
redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase());
} catch (Exception e) {
log.error(e, "Failed to delete recent search");
}
}
}
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java
around lines 326 to 352, add the @Slf4j annotation to the class to enable
logging and import it from lombok.extern.slf4j.Slf4j. Also, add
@Transactional(propagation = Propagation.NOT_SUPPORTED) annotation to the
saveRecentSearches and deleteRecentSearch methods to mark them as
non-transactional for Redis operations, importing the necessary classes from
org.springframework.transaction.annotation. This prevents unnecessary JPA
transactions during Redis calls and enables proper logging.

Comment on lines +344 to +352

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Normalize with Locale.ROOT and fix logging; mirror save behavior.

Ensure delete uses the same normalization and correct logging:

 @Override
 public void deleteRecentSearch(Long userId, String keyword) {
     try {
         String key = "recent_search:" + userId;
-        redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase());
+        if (keyword == null) return;
+        String trimmed = keyword.trim();
+        if (trimmed.isEmpty()) return;
+        redisTemplate.opsForList().remove(key, 0, trimmed.toLowerCase(java.util.Locale.ROOT));
     } catch (Exception e) {
-        log.error(e, "Failed to delete recent search");
+        log.error("Failed to delete recent search", e);
     }
 }
📝 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
@Override
public void deleteRecentSearch(Long userId, String keyword) {
try {
String key = "recent_search:" + userId;
redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase());
} catch (Exception e) {
log.error(e, "Failed to delete recent search");
}
}
@Override
public void deleteRecentSearch(Long userId, String keyword) {
try {
String key = "recent_search:" + userId;
if (keyword == null) return;
String trimmed = keyword.trim();
if (trimmed.isEmpty()) return;
redisTemplate.opsForList().remove(key, 0, trimmed.toLowerCase(java.util.Locale.ROOT));
} catch (Exception e) {
log.error("Failed to delete recent search", e);
}
}
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java
around lines 344 to 352, the deleteRecentSearch method normalizes the keyword
using toLowerCase() without specifying Locale.ROOT, unlike the save method.
Update the normalization to use keyword.trim().toLowerCase(Locale.ROOT) to
ensure consistent behavior. Also, fix the logging statement to correctly log the
exception by swapping the parameters so the exception is logged properly with
the message.


}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ public interface StarQueryService {

public List<String> getAutoComplete(String query, Long userId, int size);

public List<String> getRecentSearches(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import com.team_nebula.nebula.domain.star.dto.response.*;
import com.team_nebula.nebula.domain.star.repository.StarNeo4jRepositoryCustom;
import com.team_nebula.nebula.domain.star.search.document.StarSearchDocument;
import com.team_nebula.nebula.domain.star.search.dto.response.SearchResultResponseDTO;
import com.team_nebula.nebula.domain.star.search.dto.response.SearchStarResponseDTO;
import com.team_nebula.nebula.domain.star.search.service.ElasticsearchService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -21,6 +21,8 @@

import lombok.RequiredArgsConstructor;

import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix logger: invalid static import and wrong error(...) signature

  • Static-importing ReactiveNeo4jClient.log is incorrect here and likely won’t compile.
  • The call log.error(e, "Failed to get recent searches"); has reversed parameters.

Apply this diff and add Lombok logger:

- import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log;
+ import lombok.extern.slf4j.Slf4j;
-      log.error(e, "Failed to get recent searches");
+      log.error("Failed to get recent searches for userId={}", userId, e);

Outside this hunk, annotate the class:

// add above class declaration
@Slf4j

Also applies to: 199-199

🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java
at line 24 and line 199, remove the incorrect static import of
ReactiveNeo4jClient.log and instead add the Lombok @Slf4j annotation above the
class declaration to enable logging. Then, fix the log.error call by swapping
the parameters to have the message first and the exception second, i.e.,
log.error("Failed to get recent searches", e).


@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
Expand All @@ -31,6 +33,9 @@ public class StarQueryServiceImpl implements StarQueryService {
private final StarNeo4jRepositoryCustom starNeo4jRepositoryCustom;
private final ElasticsearchService elasticsearchService;

private final RedisTemplate<String, Object> redisTemplate;
private static final int MAX_RECENT_SEARCHES = 10;

// 스타 + 링크 전체 조회
@Override
public GetStarListResponseDTO getStarList(Long userId) {
Expand Down Expand Up @@ -164,8 +169,6 @@ public SearchResultResponseDTO searchStarsV2(String keyword, Long userId, int pa
.build();
}



@Override
public List<String> getAutoComplete(String query, Long userId, int size) {

Expand All @@ -181,4 +184,20 @@ public List<String> getAutoComplete(String query, Long userId, int size) {
return elasticsearchService.getAutoComplete(query, userId, size);

}

@Override
@Cacheable(value = "recent_searches", key = "#userId")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Avoid stale results: @Cacheable without eviction on writes

@Cacheable("recent_searches") will cache results, but unless corresponding save/delete methods evict/refresh this cache, users can see stale lists.

  • Ensure write paths (save/delete recent search) use @CacheEvict(value = "recent_searches", key = "#userId") or @CachePut accordingly.
  • Confirm cache TTL aligns with the stated 30-day retention or choose a shorter TTL to reduce staleness risk.

Run this to check for cache eviction on write paths and cache TTL config:


🏁 Script executed:

#!/bin/bash
# Find recent searches write operations and verify cache eviction
rg -n --hidden --glob '!**/node_modules/**' '@CacheEvict\([^)]*value\s*=\s*"recent_searches"' -A 2
rg -n --hidden --glob '!**/node_modules/**' 'saveRecentSearch|saveRecentSearches|deleteRecentSearch' -A 3

# Look for CacheManager/RedisCacheManager TTL config
rg -n --hidden --glob '!**/node_modules/**' 'RedisCacheManager|CacheManager|cacheNames|ttl|timeToLive' -A 3

# Check for any other usage of "recent_searches" to avoid key/cachename collisions
rg -n --hidden --glob '!**/node_modules/**' '"recent_searches"'

Length of output: 6160


Ensure cache invalidation and correct TTL for recent_searches

  • No @CacheEvict found on the write methods in StarCommandServiceImpl—add
    @CacheEvict(value = "recent_searches", key = "#userId")
    to
    saveRecentSearches(Long userId, String keyword) (StarCommandServiceImpl.java:328)
    deleteRecentSearch(Long userId, String keyword) (StarCommandServiceImpl.java:345)
  • Global cache TTL is set to 5 minutes in CacheConfig.java:27–28. If you intend to retain “recent_searches” for 30 days, add an initial cache configuration for that cache, e.g.:
    cacheConfigurations.put(
      "recent_searches",
      RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofDays(30))
        … 
    );
  • The Redis list key "recent_search:<userId>" used by RedisTemplate has no expiration. If you need automatic data cleanup, apply an expire on that key when you write to it.
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java
at line 189, the @Cacheable annotation for "recent_searches" lacks corresponding
cache eviction on write operations. To fix this, add @CacheEvict(value =
"recent_searches", key = "#userId") annotations to the methods
saveRecentSearches(Long userId, String keyword) and deleteRecentSearch(Long
userId, String keyword) in StarCommandServiceImpl.java at lines 328 and 345
respectively. Additionally, update CacheConfig.java around lines 27-28 to
configure a specific TTL of 30 days for the "recent_searches" cache by adding a
RedisCacheConfiguration with entryTtl(Duration.ofDays(30)). Finally, ensure that
the Redis list key "recent_search:<userId>" used by RedisTemplate has an
expiration set when writing to it to enable automatic cleanup.

public List<String> getRecentSearches(Long userId) {
try {
String key = "recent_searches"+userId.toString();
List<Object> searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1);
Comment on lines +190 to +193

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Validate userId and improve Redis key format

  • Add a null-guard consistent with other methods.
  • Use a namespaced key with a delimiter to avoid ambiguous keys and to align with Redis key best practices.
 public List<String> getRecentSearches(Long userId) {
-    try {
-        String key = "recent_searches"+userId.toString();
+    if (userId == null) {
+        throw new GeneralException(ErrorStatus._USER_NOT_FOUND);
+    }
+    try {
+        final String key = String.format("star:recent-searches:%d", 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 List<String> getRecentSearches(Long userId) {
try {
String key = "recent_searches"+userId.toString();
List<Object> searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1);
public List<String> getRecentSearches(Long userId) {
if (userId == null) {
throw new GeneralException(ErrorStatus._USER_NOT_FOUND);
}
try {
final String key = String.format("star:recent-searches:%d", userId);
List<Object> searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1);
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java
around lines 190 to 193, add a null check for userId at the start of the
getRecentSearches method to prevent null pointer exceptions, consistent with
other methods. Also, update the Redis key format to include a clear namespace
and delimiter, such as "recent_searches:userId", to avoid ambiguous keys and
follow Redis key naming best practices.

return searches.stream()
.map(Object::toString)
.collect(Collectors.toList());
}
catch (Exception e) {
log.error(e, "Failed to get recent searches");
return Collections.emptyList();
}
}
}