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

feat: 저장한 꿀조합 목록 조회 기능 추가 #70

Merged
merged 7 commits into from
Jun 4, 2024
43 changes: 43 additions & 0 deletions src/main/java/com/funeat/member/dto/MemberBookmarkRecipeDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.funeat.member.dto;

import com.funeat.product.domain.Product;
import com.funeat.recipe.domain.Recipe;
import com.funeat.recipe.domain.RecipeImage;

import java.time.LocalDateTime;
import java.util.List;

public record MemberBookmarkRecipeDto(
Long id,
String title,
String image,
String content,
Boolean favorite,
MemberResponse author,
MemberBookmarkRecipeProductsDto products,
LocalDateTime createdAt
) {

private MemberBookmarkRecipeDto(final Long id, final String title, final String content, final Boolean favorite,
final MemberResponse author, final MemberBookmarkRecipeProductsDto products,
final LocalDateTime createdAt) {
this(id, title, null, content, favorite, author, products, createdAt);
}

public static MemberBookmarkRecipeDto toDto(final Recipe recipe,
final List<RecipeImage> findRecipeImages,
final List<Product> recipeInProducts,
final Boolean isFavorite) {
70825 marked this conversation as resolved.
Show resolved Hide resolved
final MemberResponse author = MemberResponse.toResponse(recipe.getMember());
final MemberBookmarkRecipeProductsDto products = MemberBookmarkRecipeProductsDto.toDto(recipeInProducts);

if (findRecipeImages.isEmpty()) {
return new MemberBookmarkRecipeDto(recipe.getId(), recipe.getTitle(), recipe.getContent(), isFavorite,
author, products, recipe.getCreatedAt()
);
}
return new MemberBookmarkRecipeDto(recipe.getId(), recipe.getTitle(), findRecipeImages.get(0).getImage(),
recipe.getContent(), isFavorite, author, products, recipe.getCreatedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.funeat.member.dto;

import com.funeat.product.domain.Product;

public record MemberBookmarkRecipeProductDto(
Long id,
String name,
Long price,
String image,
Double averageRating
) {

public static MemberBookmarkRecipeProductDto toDto(final Product product) {
return new MemberBookmarkRecipeProductDto(product.getId(), product.getName(), product.getPrice(),
product.getImage(), product.getAverageRating());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.funeat.member.dto;

import com.funeat.product.domain.Product;

import java.util.List;

public record MemberBookmarkRecipeProductsDto(
List<MemberBookmarkRecipeProductDto> products
) {

public static MemberBookmarkRecipeProductsDto toDto(final List<Product> recipeInProducts) {
final List<MemberBookmarkRecipeProductDto> products = recipeInProducts.stream()
.map(MemberBookmarkRecipeProductDto::toDto)
.toList();

return new MemberBookmarkRecipeProductsDto(products);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.funeat.member.dto;

import com.funeat.common.dto.PageDto;

import java.util.List;

public record MemberBookmarkRecipesResponse(
PageDto page,
List<MemberBookmarkRecipeDto> recipes
) {

public static MemberBookmarkRecipesResponse toResponse(final PageDto page,
final List<MemberBookmarkRecipeDto> recipes) {
return new MemberBookmarkRecipesResponse(page, recipes);
}
}
26 changes: 0 additions & 26 deletions src/main/java/com/funeat/member/dto/MemberRecipeProductDto.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.funeat.auth.util.AuthenticationPrincipal;
import com.funeat.common.logging.Logging;
import com.funeat.member.application.MemberService;
import com.funeat.member.dto.MemberBookmarkRecipesResponse;
import com.funeat.member.dto.MemberProfileResponse;
import com.funeat.member.dto.MemberRecipesResponse;
import com.funeat.member.dto.MemberRequest;
Expand Down Expand Up @@ -80,4 +81,12 @@ public ResponseEntity<Void> deleteReview(@PathVariable final Long reviewId,

return ResponseEntity.noContent().build();
}

@GetMapping("/recipes/bookmark")
public ResponseEntity<MemberBookmarkRecipesResponse> getMemberBookmarkRecipe(@AuthenticationPrincipal final LoginInfo loginInfo,
@PageableDefault final Pageable pageable) {
final MemberBookmarkRecipesResponse response = recipeService.findBookmarkRecipeByMember(loginInfo.getId(), pageable);

return ResponseEntity.ok().body(response);
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/funeat/member/presentation/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.funeat.auth.dto.LoginInfo;
import com.funeat.auth.util.AuthenticationPrincipal;
import com.funeat.member.dto.MemberBookmarkRecipesResponse;
import com.funeat.member.dto.MemberProfileResponse;
import com.funeat.member.dto.MemberRecipesResponse;
import com.funeat.member.dto.MemberRequest;
Expand Down Expand Up @@ -66,4 +67,13 @@ ResponseEntity<MemberRecipesResponse> getMemberRecipe(@AuthenticationPrincipal f
@DeleteMapping
ResponseEntity<Void> deleteReview(@PathVariable final Long reviewId,
@AuthenticationPrincipal final LoginInfo loginInfo);

@Operation(summary = "사용자가 저장한 꿀조합 조회 (북마크)", description = "자신이 저장한 꿀조합을 조회한다.")
@ApiResponse(
responseCode = "200",
description = "저장한 꿀조합 조회 성공."
)
@GetMapping
ResponseEntity<MemberBookmarkRecipesResponse> getMemberBookmarkRecipe(@AuthenticationPrincipal final LoginInfo loginInfo,
@PageableDefault final Pageable pageable);
}
19 changes: 19 additions & 0 deletions src/main/java/com/funeat/recipe/application/RecipeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import com.funeat.member.domain.Member;
import com.funeat.member.domain.bookmark.RecipeBookmark;
import com.funeat.member.domain.favorite.RecipeFavorite;
import com.funeat.member.dto.MemberBookmarkRecipeDto;
import com.funeat.member.dto.MemberBookmarkRecipesResponse;
import com.funeat.member.dto.MemberRecipeDto;
import com.funeat.member.dto.MemberRecipesResponse;
import com.funeat.member.exception.MemberException.MemberDuplicateBookmarkException;
Expand Down Expand Up @@ -227,6 +229,23 @@ private RecipeBookmark createAndSaveRecipeBookmark(final Member member, final Re
}
}

public MemberBookmarkRecipesResponse findBookmarkRecipeByMember(final Long memberId, final Pageable pageable) {
final Member findMember = memberRepository.findById(memberId)
.orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId));

final Page<Recipe> sortedBookmarkRecipePages = recipeRepository.findBookmarkedRecipesByMember(findMember, pageable);

final PageDto page = PageDto.toDto(sortedBookmarkRecipePages);
final List<MemberBookmarkRecipeDto> dtos = sortedBookmarkRecipePages.stream()
.map(recipe -> MemberBookmarkRecipeDto.toDto(recipe,
recipeImageRepository.findByRecipe(recipe),
productRecipeRepository.findProductByRecipe(recipe),
recipeFavoriteRepository.existsByMemberAndRecipeAndFavoriteTrue(findMember, recipe)))
.toList();

return MemberBookmarkRecipesResponse.toResponse(page, dtos);
}

public SearchRecipeResultsResponse getSearchResults(final String query, final Long lastRecipeId) {
final List<Recipe> findRecipes = findAllByProductNameContaining(query, lastRecipeId);
final int resultSize = getResultSize(findRecipes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public interface RecipeRepository extends JpaRepository<Recipe, Long> {

Page<Recipe> findRecipesByMember(final Member member, final Pageable pageable);


70825 marked this conversation as resolved.
Show resolved Hide resolved
@Query("SELECT DISTINCT r FROM Recipe r "
+ "LEFT JOIN ProductRecipe pr ON pr.recipe.id = r.id "
+ "WHERE pr.product.name LIKE CONCAT('%', :name, '%')"
Expand Down Expand Up @@ -49,4 +50,7 @@ List<Recipe> findAllByProductNameContaining(@Param("name") final String name, fi
List<Recipe> findRecipesByFavoriteCountGreaterThanEqual(final Long favoriteCount);

Long countByMember(final Member member);

@Query("SELECT r FROM Recipe r JOIN RecipeBookmark rb ON r.id = rb.recipe.id WHERE rb.member = :member AND rb.bookmark = true")
Page<Recipe> findBookmarkedRecipesByMember(@Param("member") Member member, Pageable pageable);
}
129 changes: 129 additions & 0 deletions src/test/java/com/funeat/acceptance/member/MemberAcceptanceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
import static com.funeat.acceptance.member.MemberSteps.리뷰_삭제_요청;
import static com.funeat.acceptance.member.MemberSteps.사용자_꿀조합_조회_요청;
import static com.funeat.acceptance.member.MemberSteps.사용자_리뷰_조회_요청;
import static com.funeat.acceptance.member.MemberSteps.사용자_북마크한_꿀조합_조회_요청;
import static com.funeat.acceptance.member.MemberSteps.사용자_정보_수정_요청;
import static com.funeat.acceptance.member.MemberSteps.사용자_정보_조회_요청;
import static com.funeat.acceptance.recipe.RecipeSteps.레시피_북마크_요청;
import static com.funeat.acceptance.recipe.RecipeSteps.레시피_작성_요청;
import static com.funeat.acceptance.review.ReviewSteps.리뷰_작성_요청;
import static com.funeat.auth.exception.AuthErrorCode.LOGIN_MEMBER_NOT_FOUND;
Expand All @@ -40,7 +42,11 @@
import static com.funeat.fixture.RecipeFixture.레시피;
import static com.funeat.fixture.RecipeFixture.레시피1;
import static com.funeat.fixture.RecipeFixture.레시피2;
import static com.funeat.fixture.RecipeFixture.레시피3;
import static com.funeat.fixture.RecipeFixture.레시피북마크요청_생성;
import static com.funeat.fixture.RecipeFixture.레시피추가요청_생성;
import static com.funeat.fixture.RecipeFixture.북마크O;
import static com.funeat.fixture.RecipeFixture.북마크X;
import static com.funeat.fixture.ReviewFixture.리뷰1;
import static com.funeat.fixture.ReviewFixture.리뷰2;
import static com.funeat.fixture.ReviewFixture.리뷰추가요청_재구매O_생성;
Expand All @@ -56,6 +62,7 @@

import com.funeat.acceptance.common.AcceptanceTest;
import com.funeat.member.domain.Member;
import com.funeat.member.dto.MemberBookmarkRecipeDto;
import com.funeat.member.dto.MemberProfileResponse;
import com.funeat.member.dto.MemberRecipeDto;
import com.funeat.member.dto.MemberReviewDto;
Expand Down Expand Up @@ -394,6 +401,119 @@ class deleteReview_실패_테스트 {
}
}

@Nested
class getMemberBookmarkRecipe_성공_테스트 {

@Test
void 사용자가_저장한_꿀조합이_없을때_꿀조합은_빈상태로_조회된다() {
// given
final var 카테고리 = 카테고리_즉석조리_생성();
단일_카테고리_저장(카테고리);
final var 상품 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점5점_생성(카테고리));

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

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

// when
final var 응답 = 사용자_북마크한_꿀조합_조회_요청(로그인_쿠키_획득(멤버1), FIRST_PAGE);

// then
STATUS_CODE를_검증한다(응답, 정상_처리);
페이지를_검증한다(응답, 예상_응답_페이지);
사용자_북마크한_꿀조합_조회_결과를_검증한다(응답, Collections.emptyList());
}

@Test
void 사용자가_저장한_꿀조합을_조회하다() {
// given
final var 카테고리 = 카테고리_즉석조리_생성();
단일_카테고리_저장(카테고리);
final var 상품 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점5점_생성(카테고리));

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

레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피1, 레시피북마크요청_생성(북마크O));
레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피3, 레시피북마크요청_생성(북마크O));

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

// when
final var 응답 = 사용자_북마크한_꿀조합_조회_요청(로그인_쿠키_획득(멤버1), FIRST_PAGE);

// then
STATUS_CODE를_검증한다(응답, 정상_처리);
페이지를_검증한다(응답, 예상_응답_페이지);
사용자_북마크한_꿀조합_조회_결과를_검증한다(응답, List.of(레시피3, 레시피1));
}

@Test
void 사용자가_꿀조합_저장을_취소했으면_해당_꿀조합은_보이지_않아야_한다() {
// given
final var 카테고리 = 카테고리_즉석조리_생성();
단일_카테고리_저장(카테고리);
final var 상품 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점5점_생성(카테고리));

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

레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피1, 레시피북마크요청_생성(북마크O));
레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피2, 레시피북마크요청_생성(북마크O));

레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피1, 레시피북마크요청_생성(북마크X));

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

// when
final var 응답 = 사용자_북마크한_꿀조합_조회_요청(로그인_쿠키_획득(멤버1), FIRST_PAGE);

// then
STATUS_CODE를_검증한다(응답, 정상_처리);
페이지를_검증한다(응답, 예상_응답_페이지);
사용자_북마크한_꿀조합_조회_결과를_검증한다(응답, List.of(레시피2));
}

@Test
void 사용자가_저장한_꿀조합에_이미지가_없을때_저장된_꿀조합은_이미지없이_조회된다() {
// given
final var 카테고리 = 카테고리_즉석조리_생성();
단일_카테고리_저장(카테고리);
final var 상품 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점5점_생성(카테고리));

레시피_작성_요청(로그인_쿠키_획득(멤버1), null, 레시피추가요청_생성(상품));
레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피1, 레시피북마크요청_생성(북마크O));

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

// when
final var 응답 = 사용자_북마크한_꿀조합_조회_요청(로그인_쿠키_획득(멤버1), FIRST_PAGE);

// then
STATUS_CODE를_검증한다(응답, 정상_처리);
페이지를_검증한다(응답, 예상_응답_페이지);
사용자_북마크한_꿀조합_조회_결과를_검증한다(응답, List.of(레시피));
조회한_꿀조합의_이미지가_없는지_확인한다(응답);
}
}

@Nested
class getMemberBookmarkRecipe_실패_테스트 {

@ParameterizedTest
@NullAndEmptySource
void 로그인하지_않은_사용자가_저장한_꿀조합을_조회할때_예외가_발생한다(final String cookie) {
// given & when
final var 응답 = 사용자_북마크한_꿀조합_조회_요청(cookie, FIRST_PAGE);

// then
STATUS_CODE를_검증한다(응답, 인증되지_않음);
RESPONSE_CODE와_MESSAGE를_검증한다(응답, LOGIN_MEMBER_NOT_FOUND.getCode(),
LOGIN_MEMBER_NOT_FOUND.getMessage());
}
}

private void 사용자_리뷰_조회_결과를_검증한다(final ExtractableResponse<Response> response, final int expectedReviewSize) {
final var actual = response.jsonPath().getList("reviews", MemberReviewDto.class);

Expand Down Expand Up @@ -449,4 +569,13 @@ class deleteReview_실패_테스트 {

assertThat(actual).isNull();
}

private void 사용자_북마크한_꿀조합_조회_결과를_검증한다(final ExtractableResponse<Response> response,
final List<Long> recipeIds) {
final var actual = response.jsonPath()
.getList("recipes", MemberBookmarkRecipeDto.class);

assertThat(actual).extracting(MemberBookmarkRecipeDto::id)
.containsExactlyElementsOf(recipeIds);
}
}
10 changes: 10 additions & 0 deletions src/test/java/com/funeat/acceptance/member/MemberSteps.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,14 @@ public class MemberSteps {
.then()
.extract();
}

public static ExtractableResponse<Response> 사용자_북마크한_꿀조합_조회_요청(final String loginCookie, final Long page) {
return given()
.when()
.cookie("SESSION", loginCookie)
.queryParam("page", page)
.get("/api/members/recipes/bookmark")
.then()
.extract();
}
}
Loading