Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: 꿀조합 목록 조회 API 수정 #44

Merged
merged 6 commits into from
Apr 20, 2024
37 changes: 29 additions & 8 deletions src/main/java/com/funeat/product/application/ProductService.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.funeat.product.application;

import static com.funeat.member.exception.MemberErrorCode.MEMBER_NOT_FOUND;
import static com.funeat.product.exception.CategoryErrorCode.CATEGORY_NOT_FOUND;
import static com.funeat.product.exception.ProductErrorCode.PRODUCT_NOT_FOUND;

import com.funeat.common.dto.PageDto;
import com.funeat.member.domain.Member;
import com.funeat.member.exception.MemberException.MemberNotFoundException;
import com.funeat.member.persistence.MemberRepository;
import com.funeat.member.persistence.RecipeFavoriteRepository;
import com.funeat.product.domain.Category;
import com.funeat.product.domain.Product;
import com.funeat.product.dto.ProductInCategoryDto;
Expand Down Expand Up @@ -51,6 +56,7 @@ public class ProductService {
private static final int RANKING_SIZE = 3;
private static final int DEFAULT_PAGE_SIZE = 10;
private static final int DEFAULT_CURSOR_PAGINATION_SIZE = 11;
private static final long GUEST_ID = -1L;

private final CategoryRepository categoryRepository;
private final ProductRepository productRepository;
Expand All @@ -59,19 +65,24 @@ public class ProductService {
private final ProductRecipeRepository productRecipeRepository;
private final RecipeImageRepository recipeImageRepository;
private final RecipeRepository recipeRepository;
private final MemberRepository memberRepository;
private final RecipeFavoriteRepository recipeFavoriteRepository;

public ProductService(final CategoryRepository categoryRepository, final ProductRepository productRepository,
final ReviewTagRepository reviewTagRepository, final ReviewRepository reviewRepository,
final ProductRecipeRepository productRecipeRepository,
final RecipeImageRepository recipeImageRepository,
final RecipeRepository recipeRepository) {
final RecipeRepository recipeRepository, final MemberRepository memberRepository,
final RecipeFavoriteRepository recipeFavoriteRepository) {
this.categoryRepository = categoryRepository;
this.productRepository = productRepository;
this.reviewTagRepository = reviewTagRepository;
this.reviewRepository = reviewRepository;
this.productRecipeRepository = productRecipeRepository;
this.recipeImageRepository = recipeImageRepository;
this.recipeRepository = recipeRepository;
this.memberRepository = memberRepository;
this.recipeFavoriteRepository = recipeFavoriteRepository;
}

public ProductsInCategoryResponse getAllProductsInCategory(final Long categoryId, final Long lastProductId,
Expand Down Expand Up @@ -174,20 +185,30 @@ private List<ProductReviewCountDto> findAllWithReviewCountByNameContaining(final
return productRepository.findAllWithReviewCountByNameContaining(query, lastProductId, size);
}

public SortingRecipesResponse getProductRecipes(final Long productId, final Pageable pageable) {
public SortingRecipesResponse getProductRecipes(final Long memberId, final Long productId, final Pageable pageable) {
final Product product = productRepository.findById(productId)
.orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId));

final Page<Recipe> recipes = recipeRepository.findRecipesByProduct(product, pageable);

final PageDto pageDto = PageDto.toDto(recipes);
final List<RecipeDto> recipeDtos = recipes.stream()
.map(recipe -> {
final List<RecipeImage> images = recipeImageRepository.findByRecipe(recipe);
final List<Product> products = productRecipeRepository.findProductByRecipe(recipe);
return RecipeDto.toDto(recipe, images, products);
})
.collect(Collectors.toList());
.map(recipe -> createRecipeDto(memberId, recipe))
.toList();
return SortingRecipesResponse.toResponse(pageDto, recipeDtos);
}

private RecipeDto createRecipeDto(final Long memberId, final Recipe recipe) {
final List<RecipeImage> images = recipeImageRepository.findByRecipe(recipe);
final List<Product> products = productRecipeRepository.findProductByRecipe(recipe);

if (memberId == GUEST_ID) {
return RecipeDto.toDto(recipe, images, products, false);
}

final Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId));
final Boolean favorite = recipeFavoriteRepository.existsByMemberAndRecipeAndFavoriteTrue(member, recipe);
return RecipeDto.toDto(recipe, images, products, favorite);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.funeat.product.presentation;

import com.funeat.auth.dto.LoginInfo;
import com.funeat.auth.util.AuthenticationPrincipal;
import com.funeat.product.application.ProductService;
import com.funeat.product.dto.ProductResponse;
import com.funeat.product.dto.ProductSortCondition;
Expand All @@ -8,7 +10,6 @@
import com.funeat.product.dto.SearchProductResultsResponse;
import com.funeat.product.dto.SearchProductsResponse;
import com.funeat.recipe.dto.SortingRecipesResponse;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -64,9 +65,10 @@ public ResponseEntity<SearchProductResultsResponse> getSearchResults(@RequestPar
}

@GetMapping("/products/{productId}/recipes")
public ResponseEntity<SortingRecipesResponse> getProductRecipes(@PathVariable final Long productId,
public ResponseEntity<SortingRecipesResponse> getProductRecipes(@AuthenticationPrincipal final LoginInfo loginInfo,
@PathVariable final Long productId,
@PageableDefault final Pageable pageable) {
final SortingRecipesResponse response = productService.getProductRecipes(productId, pageable);
final SortingRecipesResponse response = productService.getProductRecipes(loginInfo.getId(), productId, pageable);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.funeat.product.presentation;

import com.funeat.auth.dto.LoginInfo;
import com.funeat.auth.util.AuthenticationPrincipal;
import com.funeat.product.dto.ProductResponse;
import com.funeat.product.dto.ProductsInCategoryResponse;
import com.funeat.product.dto.RankingProductsResponse;
Expand Down Expand Up @@ -71,6 +73,7 @@ ResponseEntity<SearchProductResultsResponse> getSearchResults(@RequestParam fina
description = "해당 상품 꿀조합 목록 조회 성공."
)
@GetMapping
ResponseEntity<SortingRecipesResponse> getProductRecipes(@PathVariable final Long productId,
ResponseEntity<SortingRecipesResponse> getProductRecipes(@AuthenticationPrincipal final LoginInfo loginInfo,
@PathVariable final Long productId,
@PageableDefault final Pageable pageable);
}
24 changes: 17 additions & 7 deletions src/main/java/com/funeat/recipe/application/RecipeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -156,21 +156,31 @@ public MemberRecipesResponse findRecipeByMember(final Long memberId, final Pagea
return MemberRecipesResponse.toResponse(page, dtos);
}

public SortingRecipesResponse getSortingRecipes(final Pageable pageable) {
public SortingRecipesResponse getSortingRecipes(final Long memberId, final Pageable pageable) {
final Page<Recipe> pages = recipeRepository.findAll(pageable);

final PageDto page = PageDto.toDto(pages);
final List<RecipeDto> recipes = pages.getContent().stream()
.map(recipe -> {
final List<RecipeImage> images = recipeImageRepository.findByRecipe(recipe);
final List<Product> products = productRecipeRepository.findProductByRecipe(recipe);
return RecipeDto.toDto(recipe, images, products);
})
.collect(Collectors.toList());
.map(recipe -> createRecipeDto(memberId, recipe))
.toList();

return SortingRecipesResponse.toResponse(page, recipes);
}

private RecipeDto createRecipeDto(final Long memberId, final Recipe recipe) {
final List<RecipeImage> images = recipeImageRepository.findByRecipe(recipe);
final List<Product> products = productRecipeRepository.findProductByRecipe(recipe);

if (memberId == GUEST_ID) {
return RecipeDto.toDto(recipe, images, products, false);
}

final Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId));
final Boolean favorite = recipeFavoriteRepository.existsByMemberAndRecipeAndFavoriteTrue(member, recipe);
return RecipeDto.toDto(recipe, images, products, favorite);
}

@Transactional
public void likeRecipe(final Long memberId, final Long recipeId, final RecipeFavoriteRequest request) {
final Member member = memberRepository.findById(memberId)
Expand Down
18 changes: 16 additions & 2 deletions src/main/java/com/funeat/recipe/dto/ProductRecipeDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@ public class ProductRecipeDto {
private final Long id;
private final String name;
private final Long price;
private final String image;
private final Double averageRating;

private ProductRecipeDto(final Long id, final String name, final Long price) {
private ProductRecipeDto(final Long id, final String name, final Long price, final String image,
final Double averageRating) {
this.id = id;
this.name = name;
this.price = price;
this.image = image;
this.averageRating = averageRating;
}

public static ProductRecipeDto toDto(final Product product) {
return new ProductRecipeDto(product.getId(), product.getName(), product.getPrice());
return new ProductRecipeDto(product.getId(), product.getName(), product.getPrice(), product.getImage(),
product.getAverageRating());
}

public Long getId() {
Expand All @@ -29,4 +35,12 @@ public String getName() {
public Long getPrice() {
return price;
}

public String getImage() {
return image;
}

public Double getAverageRating() {
return averageRating;
}
}
34 changes: 20 additions & 14 deletions src/main/java/com/funeat/recipe/dto/RecipeDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,44 @@
import com.funeat.recipe.domain.RecipeImage;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

public class RecipeDto {

private final Long id;
private final String image;
private final String title;
private final String content;
private final RecipeAuthorDto author;
private final List<ProductRecipeDto> products;
private final Long favoriteCount;
private final Boolean favorite;
private final LocalDateTime createdAt;

public RecipeDto(final Long id, final String image, final String title, final RecipeAuthorDto author,
final List<ProductRecipeDto> products,
final Long favoriteCount, final LocalDateTime createdAt) {
public RecipeDto(final Long id, final String image, final String title, final String content,
final RecipeAuthorDto author, final List<ProductRecipeDto> products, final Boolean favorite,
final LocalDateTime createdAt) {
this.id = id;
this.image = image;
this.title = title;
this.content = content;
this.author = author;
this.products = products;
this.favoriteCount = favoriteCount;
this.favorite = favorite;
this.createdAt = createdAt;
}

public static RecipeDto toDto(final Recipe recipe, final List<RecipeImage> recipeImages,
final List<Product> products) {
final List<Product> products, final Boolean favorite) {
final RecipeAuthorDto authorDto = RecipeAuthorDto.toDto(recipe.getMember());
final List<ProductRecipeDto> productDtos = products.stream()
.map(ProductRecipeDto::toDto)
.collect(Collectors.toList());
.toList();

if (recipeImages.isEmpty()) {
return new RecipeDto(recipe.getId(), null, recipe.getTitle(), authorDto, productDtos,
recipe.getFavoriteCount(), recipe.getCreatedAt());
return new RecipeDto(recipe.getId(), null, recipe.getTitle(), recipe.getContent(), authorDto,
productDtos, favorite, recipe.getCreatedAt());
}
return new RecipeDto(recipe.getId(), recipeImages.get(0).getImage(), recipe.getTitle(), authorDto, productDtos,
recipe.getFavoriteCount(), recipe.getCreatedAt());
return new RecipeDto(recipe.getId(), recipeImages.get(0).getImage(), recipe.getTitle(), recipe.getContent(),
authorDto, productDtos, favorite, recipe.getCreatedAt());
}

public Long getId() {
Expand All @@ -55,6 +57,10 @@ public String getTitle() {
return title;
}

public String getContent() {
return content;
}

public RecipeAuthorDto getAuthor() {
return author;
}
Expand All @@ -63,8 +69,8 @@ public List<ProductRecipeDto> getProducts() {
return products;
}

public Long getFavoriteCount() {
return favoriteCount;
public Boolean getFavorite() {
return favorite;
}

public LocalDateTime getCreatedAt() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ public ResponseEntity<RecipeDetailResponse> getRecipeDetail(@AuthenticationPrinc
}

@GetMapping(value = "/api/recipes")
public ResponseEntity<SortingRecipesResponse> getSortingRecipes(@PageableDefault final Pageable pageable) {
final SortingRecipesResponse response = recipeService.getSortingRecipes(pageable);
public ResponseEntity<SortingRecipesResponse> getSortingRecipes(@AuthenticationPrincipal final LoginInfo loginInfo,
@PageableDefault final Pageable pageable) {
final SortingRecipesResponse response = recipeService.getSortingRecipes(loginInfo.getId(), pageable);

return ResponseEntity.ok(response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ ResponseEntity<RecipeDetailResponse> getRecipeDetail(@AuthenticationPrincipal fi
description = "꿀조합 목록 조회 성공."
)
@GetMapping
ResponseEntity<SortingRecipesResponse> getSortingRecipes(@PageableDefault final Pageable pageable);
ResponseEntity<SortingRecipesResponse> getSortingRecipes(@AuthenticationPrincipal final LoginInfo loginInfo,
@PageableDefault final Pageable pageable);

@Operation(summary = "꿀조합 좋아요", description = "꿀조합에 좋아요 또는 취소를 한다.")
@ApiResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ class getSortingRecipes_성공_테스트 {
// then
STATUS_CODE를_검증한다(응답, 정상_처리);
페이지를_검증한다(응답, 예상_응답_페이지);
레시피_목록_조회_결과를_검증한다(응답, List.of(레시피2, 레시피1, 레시피3));
레시피_목록_조회_결과를_검증한다(응답, List.of(레시피2, 레시피1, 레시피3), List.of(false, false, false));
}

@Test
Expand All @@ -494,7 +494,7 @@ class getSortingRecipes_성공_테스트 {
// then
STATUS_CODE를_검증한다(응답, 정상_처리);
페이지를_검증한다(응답, 예상_응답_페이지);
레시피_목록_조회_결과를_검증한다(응답, List.of(레시피3, 레시피2, 레시피1));
레시피_목록_조회_결과를_검증한다(응답, List.of(레시피3, 레시피2, 레시피1), List.of(false, false, false));
}

@Test
Expand All @@ -516,7 +516,31 @@ class getSortingRecipes_성공_테스트 {
// then
STATUS_CODE를_검증한다(response, 정상_처리);
페이지를_검증한다(response, 예상_응답_페이지);
레시피_목록_조회_결과를_검증한다(response, List.of(레시피1, 레시피2, 레시피3));
레시피_목록_조회_결과를_검증한다(response, List.of(레시피1, 레시피2, 레시피3), List.of(false, false, false));
}

@Test
void 로그인_후_꿀조합을_좋아요가_많은_순으로_정렬할_수_있다() {
// given
final var 카테고리 = 카테고리_간편식사_생성();
단일_카테고리_저장(카테고리);
final var 상품 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점5점_생성(카테고리));

레시피_작성_요청(로그인_쿠키_획득(멤버1), 여러개_사진_명세_요청(이미지1), 레시피추가요청_생성(상품));
레시피_작성_요청(로그인_쿠키_획득(멤버1), 여러개_사진_명세_요청(이미지2), 레시피추가요청_생성(상품));
레시피_작성_요청(로그인_쿠키_획득(멤버1), 여러개_사진_명세_요청(이미지3), 레시피추가요청_생성(상품));
여러명이_레시피_좋아요_요청(List.of(멤버1), 레시피1, 좋아요O);
여러명이_레시피_좋아요_요청(List.of(멤버1, 멤버2), 레시피2, 좋아요O);

final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE);

// when
final var 응답 = 레시피_목록_요청(로그인_쿠키_획득(멤버2), 좋아요수_내림차순, FIRST_PAGE);

// then
STATUS_CODE를_검증한다(응답, 정상_처리);
페이지를_검증한다(응답, 예상_응답_페이지);
레시피_목록_조회_결과를_검증한다(응답, List.of(레시피2, 레시피1, 레시피3), List.of(true, false, false));
}
}

Expand Down Expand Up @@ -755,11 +779,14 @@ class getRecipeComment_실패_테스트 {
}
}

private void 레시피_목록_조회_결과를_검증한다(final ExtractableResponse<Response> response, final List<Long> recipeIds) {
private void 레시피_목록_조회_결과를_검증한다(final ExtractableResponse<Response> response, final List<Long> recipeIds,
final List<Boolean> favorites) {
final var actual = response.jsonPath().getList("recipes", RecipeDto.class);

assertThat(actual).extracting(RecipeDto::getId)
.containsExactlyElementsOf(recipeIds);
assertThat(actual).extracting(RecipeDto::getFavorite)
.containsExactlyElementsOf(favorites);
}

private void 레시피_상세_정보_조회_결과를_검증한다(final ExtractableResponse<Response> response) {
Expand Down
11 changes: 11 additions & 0 deletions src/test/java/com/funeat/acceptance/recipe/RecipeSteps.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ public class RecipeSteps {
.extract();
}

public static ExtractableResponse<Response> 레시피_목록_요청(final String loginCookie, final String sort, final Long page) {
return given()
.queryParam("sort", sort)
.queryParam("page", page)
.cookie("SESSION", loginCookie)
.when()
.get("/api/recipes")
.then()
.extract();
}

public static ExtractableResponse<Response> 레시피_좋아요_요청(final String loginCookie, final Long recipeId,
final RecipeFavoriteRequest request) {
return given()
Expand Down
Loading
Loading