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 @@ -97,16 +97,18 @@ public RecipesResponse getFridgesRecipes(@Parameter(hidden = true) User user,
return recipeSearchService.findRecommendedRecipesByUserFridge(user, startAfter, size);
}

@Operation(summary = "등록한 레시피 목록 조회 API")
@Operation(summary = "등록한 레시피 목록 조회 API", description = "keyword 를 주면 내 레시피(비공개 포함) 내에서 검색, 없으면 전체 목록")
@GetMapping("/users")
@LoginCheck
public RecipesResponse getUserRecipes(@Parameter(hidden = true) User user,
@Parameter(example = "0", name = "마지막 조회 레시피 아이디")
@RequestParam(value = "startAfter") long startAfter,
@Parameter(example = "20", name = "사이즈")
@RequestParam(value = "size") int size) {
@RequestParam(value = "size") int size,
@Parameter(example = "김치", name = "검색어(선택)")
@RequestParam(value = "keyword", required = false) String keyword) {

return recipeSearchService.findRecipesByUser(user, startAfter, size);
return recipeSearchService.findRecipesByUser(user, keyword, startAfter, size);
}

@Operation(summary = "레시피 등록 API")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,34 @@ public RecipesResponse findScrapRecipes(User user, long lastRecipeId, int size)
}

@Transactional(readOnly = true)
public RecipesResponse findRecipesByUser(User user, long lastRecipeId, int size) {
public RecipesResponse findRecipesByUser(User user, String keyword, long lastRecipeId, int size) {

long totalCnt = recipeRepository.countByUserId(user.getUserId());
Long userId = user.getUserId();
SearchQuery query = SearchKeywordNormalizer.normalize(keyword);

long totalCnt;
List<Recipe> foundRecipes;
if (query instanceof SearchQuery.Empty) {
// 검색어 없음 → 내 레시피 전체 (recipeId 역순)
totalCnt = recipeRepository.countByUserId(userId);
foundRecipes = findLimitByUserId(userId, lastRecipeId, size);
} else {
// 검색어 있음 → 내 레시피 검색 (내 비공개 포함, 일치율 → 최신순)
badWordFiltering.check(keyword);
totalCnt = recipeRepository.countByKeywordAndUserId(query, userId);
Double lastRelevance = recipeRepository.findRelevanceScoreByRecipeId(query, lastRecipeId);
Recipe last = recipeRepository.findById(lastRecipeId).orElse(null);
foundRecipes = recipeRepository.findByKeywordAndUserIdLimitOrderByCreatedAtDesc(query, userId, lastRecipeId, lastRelevance,
last != null ? last.getCreatedAt() : null, size);
}

Recipes recipes = new Recipes(findLimitByUserId(user.getUserId(), lastRecipeId, size));
Recipes recipes = new Recipes(foundRecipes);

List<User> recipePostUsers = userService.findByUserIds(recipes.getUserIds());

List<RecipeScrap> recipeScraps = recipeScrapService.findByRecipeIds(recipes.getRecipeIds());

List<String> ingredientNamesInFridge = fridgeService.findIngredientNamesInFridge(user.getUserId());
List<String> ingredientNamesInFridge = fridgeService.findIngredientNamesInFridge(userId);

return RecipesResponse.from(totalCnt, recipes, recipePostUsers, recipeScraps, user, ingredientNamesInFridge);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ public interface RecipeCustomRepository {

Long countByKeyword(SearchQuery query);

// 특정 유저의 레시피(비공개 포함) 내 검색 — 나만의 레시피 검색용
Long countByKeywordAndUserId(SearchQuery query, Long userId);

// 아래 정렬들은 모두 "검색어 일치율(relevance) 대분류 → 각 정렬키 소분류 → recipeId" 순.
// lastRelevance 는 커서(직전 페이지 마지막 레시피)의 현재 검색어 기준 점수. findRelevanceScoreByRecipeId 로 구해 넘긴다.
Double findRelevanceScoreByRecipeId(SearchQuery query, Long recipeId);

List<Recipe> findByKeywordLimitOrderByCreatedAtDesc(SearchQuery query, Long lastRecipeId, Double lastRelevance, LocalDateTime createdAt, int size);

// 특정 유저의 레시피(비공개 포함) 내 검색, 최신순(일치율→제목우선→createdAt→id) — 나만의 레시피 검색용
List<Recipe> findByKeywordAndUserIdLimitOrderByCreatedAtDesc(SearchQuery query, Long userId, Long lastRecipeId, Double lastRelevance, LocalDateTime createdAt, int size);

List<Recipe> findByKeywordLimitOrderByRecipeScrapCntDesc(SearchQuery query, Long lastRecipeId, Double lastRelevance, long recipeScrapCnt, int size);

List<Recipe> findByKeywordLimitOrderByRecipeViewCntDesc(SearchQuery query, Long lastRecipeId, Double lastRelevance, long recipeViewCnt, int size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ public Long countByKeyword(SearchQuery query) {
.fetchOne();
}

@Override
public Long countByKeywordAndUserId(SearchQuery query, Long userId) {
return queryFactory
.select(recipe.recipeId.countDistinct())
.from(recipe)
.where(
recipe.userId.eq(userId),
keywordMatch(query)
)
.fetchOne();
}

@Override
public List<Recipe> findByKeywordLimitOrderByCreatedAtDesc(SearchQuery query, Long lastRecipeId, Double lastRelevance, LocalDateTime lastCreatedAt, int size) {

Expand All @@ -72,6 +84,22 @@ public List<Recipe> findByKeywordLimitOrderByCreatedAtDesc(SearchQuery query, Lo
.fetch();
}

@Override
public List<Recipe> findByKeywordAndUserIdLimitOrderByCreatedAtDesc(SearchQuery query, Long userId, Long lastRecipeId, Double lastRelevance, LocalDateTime lastCreatedAt, int size) {

return queryFactory
.selectFrom(recipe)
.where(
recipe.userId.eq(userId),
keywordMatch(query),
relevanceCursor(query, lastRecipeId, lastRelevance,
() -> recipe.createdAt.lt(lastCreatedAt), () -> recipe.createdAt.eq(lastCreatedAt))
)
.orderBy(relevanceOrder(query, recipe.createdAt.desc()))
.limit(size)
.fetch();
}

@Override
public Double findRelevanceScoreByRecipeId(SearchQuery query, Long recipeId) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ class RecipeSearchServiceTest extends Specification {
fridgeService.findIngredientNamesInFridge(users.get(0).getUserId()) >> []

when:
RecipesResponse result = recipeSearchService.findRecipesByUser(users.get(0), lastRecipeId, size)
RecipesResponse result = recipeSearchService.findRecipesByUser(users.get(0), null, lastRecipeId, size)

then:
result.totalCnt == 2
Expand All @@ -522,6 +522,41 @@ class RecipeSearchServiceTest extends Specification {
result.recipes.viewCnt == recipes.viewCnt
}

def "등록한 레시피 조회 - keyword 있으면 내 레시피 검색 경로(userId 전달), 목록 경로 안 탐"() {

given:
User user = User.builder().userId(1).socialId("naver_1").nickname("테스터1").build()
String keyword = "테스트"
long lastRecipeId = 0
int size = 10

recipeRepository.countByKeywordAndUserId(_, user.userId) >> 1
recipeRepository.findRelevanceScoreByRecipeId(_, lastRecipeId) >> null
recipeRepository.findById(lastRecipeId) >> Optional.empty()

Recipe recipe = Recipe.builder()
.recipeId(1)
.recipeNm("테스트제목")
.introduction("설명")
.level(RecipeLevel.NORMAL)
.userId(user.userId)
.isHidden(false)
.build()

userService.findByUserIds(_) >> [user]
recipeScrapService.findByRecipeIds(_) >> []
fridgeService.findIngredientNamesInFridge(user.userId) >> []

when:
RecipesResponse result = recipeSearchService.findRecipesByUser(user, keyword, lastRecipeId, size)

then: "keyword 있으면 userId 로 내 레시피 검색 쿼리를 호출하고, 전체 목록 경로는 타지 않는다"
1 * recipeRepository.findByKeywordAndUserIdLimitOrderByCreatedAtDesc(_, user.userId, lastRecipeId, _, _, size) >> [recipe]
0 * recipeRepository.findLimitByUserId(_, _, _)
result.totalCnt == 1
result.recipes.recipeId == [recipe.recipeId]
}

def "냉장고 재료 일치하는 레시피 목록 조회"() {

given:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,47 @@ class RecipeCustomRepositoryTest extends Specification {
response.get(1).recipeId == descMatch.recipeId
}

def "내 레시피 검색 - userId 로 내 레시피(비공개 포함)만, 남의 레시피는 제외"() {

given:
Recipe mine = Recipe.builder()
.recipeNm("테스트제목")
.introduction("설명")
.level(RecipeLevel.NORMAL)
.userId(users.get(0).userId)
.isHidden(false)
.build()
Recipe mineHidden = Recipe.builder()
.recipeNm("테스트제목")
.introduction("설명")
.level(RecipeLevel.NORMAL)
.userId(users.get(0).userId)
.isHidden(true) // 내 비공개 → 포함돼야
.build()
Recipe others = Recipe.builder()
.recipeNm("테스트제목")
.introduction("설명")
.level(RecipeLevel.NORMAL)
.userId(users.get(1).userId) // 남의 레시피 → 제외돼야
.isHidden(false)
.build()

committedTx.executeWithoutResult { status -> recipeRepository.saveAll([mine, mineHidden, others]) }

SearchQuery query = SearchKeywordNormalizer.normalize("테스트")
Long myUserId = users.get(0).userId

when:
long count = recipeRepository.countByKeywordAndUserId(query, myUserId)
List<Recipe> response = recipeRepository.findByKeywordAndUserIdLimitOrderByCreatedAtDesc(query, myUserId, 0L, null, null, 10)

then: "내 레시피(공개+비공개) 2건만, 남의 것 제외"
count == 2
response.size() == 2
response*.recipeId.toSet() == [mine.recipeId, mineHidden.recipeId].toSet()
!(others.recipeId in response*.recipeId)
}

def "검색어로 레시피 목록 조회 - 스크랩 수 많은 순 정렬"() {

given:
Expand Down
Loading