diff --git a/src/main/java/com/funeat/member/exception/MemberErrorCode.java b/src/main/java/com/funeat/member/exception/MemberErrorCode.java index 91b37d3c..9d70c4d3 100644 --- a/src/main/java/com/funeat/member/exception/MemberErrorCode.java +++ b/src/main/java/com/funeat/member/exception/MemberErrorCode.java @@ -7,6 +7,7 @@ public enum MemberErrorCode { MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 회원입니다. 회원 id를 확인하세요.", "5001"), MEMBER_UPDATE_ERROR(HttpStatus.BAD_REQUEST, "닉네임 또는 이미지를 확인하세요.", "5002"), MEMBER_DUPLICATE_FAVORITE(HttpStatus.CONFLICT, "이미 좋아요를 누른 상태입니다.", "5003"), + MEMBER_DUPLICATE_BOOKMARK(HttpStatus.CONFLICT, "이미 북마크를 누른 상태입니다.", "5004"), ; private final HttpStatus status; diff --git a/src/main/java/com/funeat/member/exception/MemberException.java b/src/main/java/com/funeat/member/exception/MemberException.java index c0dab99e..1a6092ea 100644 --- a/src/main/java/com/funeat/member/exception/MemberException.java +++ b/src/main/java/com/funeat/member/exception/MemberException.java @@ -27,4 +27,10 @@ public MemberDuplicateFavoriteException(final MemberErrorCode errorCode, final L super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage(), memberId)); } } + + public static class MemberDuplicateBookmarkException extends MemberException { + public MemberDuplicateBookmarkException(final MemberErrorCode errorCode, final Long memberId) { + super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage(), memberId)); + } + } } diff --git a/src/main/java/com/funeat/member/persistence/RecipeBookmarkRepository.java b/src/main/java/com/funeat/member/persistence/RecipeBookmarkRepository.java new file mode 100644 index 00000000..32e3e7f1 --- /dev/null +++ b/src/main/java/com/funeat/member/persistence/RecipeBookmarkRepository.java @@ -0,0 +1,13 @@ +package com.funeat.member.persistence; + +import com.funeat.member.domain.Member; +import com.funeat.member.domain.bookmark.RecipeBookmark; +import com.funeat.recipe.domain.Recipe; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface RecipeBookmarkRepository extends JpaRepository { + + Optional findByMemberAndRecipe(final Member member, final Recipe recipe); +} diff --git a/src/main/java/com/funeat/recipe/application/RecipeService.java b/src/main/java/com/funeat/recipe/application/RecipeService.java index 7a13851f..17b321c2 100644 --- a/src/main/java/com/funeat/recipe/application/RecipeService.java +++ b/src/main/java/com/funeat/recipe/application/RecipeService.java @@ -1,5 +1,6 @@ package com.funeat.recipe.application; +import static com.funeat.member.exception.MemberErrorCode.MEMBER_DUPLICATE_BOOKMARK; import static com.funeat.member.exception.MemberErrorCode.MEMBER_DUPLICATE_FAVORITE; import static com.funeat.member.exception.MemberErrorCode.MEMBER_NOT_FOUND; import static com.funeat.product.exception.ProductErrorCode.PRODUCT_NOT_FOUND; @@ -11,12 +12,15 @@ import com.funeat.common.ImageUploader; import com.funeat.common.dto.PageDto; 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.MemberRecipeDto; import com.funeat.member.dto.MemberRecipesResponse; +import com.funeat.member.exception.MemberException.MemberDuplicateBookmarkException; import com.funeat.member.exception.MemberException.MemberDuplicateFavoriteException; import com.funeat.member.exception.MemberException.MemberNotFoundException; import com.funeat.member.persistence.MemberRepository; +import com.funeat.member.persistence.RecipeBookmarkRepository; import com.funeat.member.persistence.RecipeFavoriteRepository; import com.funeat.product.domain.Product; import com.funeat.product.domain.ProductRecipe; @@ -28,6 +32,7 @@ import com.funeat.recipe.dto.RankingRecipeDto; import com.funeat.recipe.dto.RankingRecipesResponse; import com.funeat.recipe.dto.RecipeAuthorDto; +import com.funeat.recipe.dto.RecipeBookmarkRequest; import com.funeat.recipe.dto.RecipeCommentCondition; import com.funeat.recipe.dto.RecipeCommentCreateRequest; import com.funeat.recipe.dto.RecipeCommentResponse; @@ -74,6 +79,7 @@ public class RecipeService { private final RecipeRepository recipeRepository; private final RecipeImageRepository recipeImageRepository; private final RecipeFavoriteRepository recipeFavoriteRepository; + private final RecipeBookmarkRepository recipeBookmarkRepository; private final CommentRepository commentRepository; private final ImageUploader imageUploader; @@ -81,6 +87,7 @@ public RecipeService(final MemberRepository memberRepository, final ProductRepos final ProductRecipeRepository productRecipeRepository, final RecipeRepository recipeRepository, final RecipeImageRepository recipeImageRepository, final RecipeFavoriteRepository recipeFavoriteRepository, + final RecipeBookmarkRepository recipeBookmarkRepository, final CommentRepository commentRepository, final ImageUploader imageUploader) { this.memberRepository = memberRepository; this.productRepository = productRepository; @@ -88,6 +95,7 @@ public RecipeService(final MemberRepository memberRepository, final ProductRepos this.recipeRepository = recipeRepository; this.recipeImageRepository = recipeImageRepository; this.recipeFavoriteRepository = recipeFavoriteRepository; + this.recipeBookmarkRepository = recipeBookmarkRepository; this.commentRepository = commentRepository; this.imageUploader = imageUploader; } @@ -196,6 +204,29 @@ private RecipeFavorite createAndSaveRecipeFavorite(final Member member, final Re } } + @Transactional + public void bookmarkRecipe(final Long memberId, final Long recipeId, final RecipeBookmarkRequest request) { + final Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId)); + final Recipe recipe = recipeRepository.findByIdForUpdate(recipeId) + .orElseThrow(() -> new RecipeNotFoundException(RECIPE_NOT_FOUND, recipeId)); + + final RecipeBookmark recipeBookmark = recipeBookmarkRepository.findByMemberAndRecipe(member, recipe) + .orElseGet(() -> createAndSaveRecipeBookmark(member, recipe, request.bookmark())); + + recipeBookmark.updateBookmark(request.bookmark()); + } + + private RecipeBookmark createAndSaveRecipeBookmark(final Member member, final Recipe recipe, + final Boolean bookmark) { + try { + final RecipeBookmark recipeBookmark = RecipeBookmark.create(member, recipe, bookmark); + return recipeBookmarkRepository.save(recipeBookmark); + } catch (final DataIntegrityViolationException e) { + throw new MemberDuplicateBookmarkException(MEMBER_DUPLICATE_BOOKMARK, member.getId()); + } + } + public SearchRecipeResultsResponse getSearchResults(final String query, final Long lastRecipeId) { final List findRecipes = findAllByProductNameContaining(query, lastRecipeId); final int resultSize = getResultSize(findRecipes); diff --git a/src/main/java/com/funeat/recipe/presentation/RecipeApiController.java b/src/main/java/com/funeat/recipe/presentation/RecipeApiController.java index b87206ce..40af2b39 100644 --- a/src/main/java/com/funeat/recipe/presentation/RecipeApiController.java +++ b/src/main/java/com/funeat/recipe/presentation/RecipeApiController.java @@ -83,8 +83,7 @@ public ResponseEntity likeRecipe(@AuthenticationPrincipal final LoginInfo public ResponseEntity bookmarkRecipe(@AuthenticationPrincipal final LoginInfo loginInfo, @PathVariable final Long recipeId, @RequestBody @Valid final RecipeBookmarkRequest request) { - // TODO: 서비스 테스트 추가 - // TODO: 서비스 로직에 북마크 기능 추가 + recipeService.bookmarkRecipe(loginInfo.getId(), recipeId, request); return ResponseEntity.noContent().build(); } diff --git a/src/test/java/com/funeat/common/ServiceTest.java b/src/test/java/com/funeat/common/ServiceTest.java index 6544accb..e0dbe9e6 100644 --- a/src/test/java/com/funeat/common/ServiceTest.java +++ b/src/test/java/com/funeat/common/ServiceTest.java @@ -10,6 +10,7 @@ import com.funeat.member.domain.favorite.RecipeFavorite; import com.funeat.member.domain.favorite.ReviewFavorite; import com.funeat.member.persistence.MemberRepository; +import com.funeat.member.persistence.RecipeBookmarkRepository; import com.funeat.member.persistence.RecipeFavoriteRepository; import com.funeat.member.persistence.ReviewFavoriteRepository; import com.funeat.product.application.CategoryService; @@ -57,6 +58,9 @@ public abstract class ServiceTest { @Autowired protected ReviewFavoriteRepository reviewFavoriteRepository; + @Autowired + protected RecipeBookmarkRepository recipeBookmarkRepository; + @Autowired protected CategoryRepository categoryRepository;