Skip to content

Commit

Permalink
feat: 꿀조합 저장 기능 추가 (#67)
Browse files Browse the repository at this point in the history
* feat: 꿀조합 북마크 엔티티 및 단위 테스트 추가

* test: 레시피 저장 기능 인수 테스트 추가

* test: 북마크 서비스 테스트 추가

* feat: 레시피 저장 기능 서비스 로직 추가
  • Loading branch information
70825 authored May 30, 2024
1 parent 72c1c83 commit 5546d43
Show file tree
Hide file tree
Showing 15 changed files with 464 additions and 5 deletions.
8 changes: 8 additions & 0 deletions src/main/java/com/funeat/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static com.funeat.member.exception.MemberErrorCode.MEMBER_UPDATE_ERROR;

import com.funeat.member.domain.bookmark.RecipeBookmark;
import com.funeat.member.domain.favorite.RecipeFavorite;
import com.funeat.member.domain.favorite.ReviewFavorite;
import com.funeat.member.exception.MemberException.MemberUpdateException;
Expand Down Expand Up @@ -34,6 +35,9 @@ public class Member {
@OneToMany(mappedBy = "member")
private List<RecipeFavorite> recipeFavorites;

@OneToMany(mappedBy = "member")
private List<RecipeBookmark> recipeBookmarks;

protected Member() {
}

Expand Down Expand Up @@ -71,6 +75,10 @@ public List<RecipeFavorite> getRecipeFavorites() {
return recipeFavorites;
}

public List<RecipeBookmark> getRecipeBookmarks() {
return recipeBookmarks;
}

public void modifyProfile(final String nickname, final String profileImage) {
if (!StringUtils.hasText(nickname) || Objects.isNull(profileImage)) {
throw new MemberUpdateException(MEMBER_UPDATE_ERROR);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.funeat.member.domain.bookmark;

import com.funeat.member.domain.Member;
import com.funeat.recipe.domain.Recipe;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;

@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"member_id", "recipe_id"}))
public class RecipeBookmark {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "recipe_id")
private Recipe recipe;

private Boolean bookmark;

protected RecipeBookmark() {
}

public RecipeBookmark(final Member member, final Recipe recipe, final Boolean bookmark) {
this.member = member;
this.recipe = recipe;
this.bookmark = bookmark;
}

public static RecipeBookmark create(final Member member, final Recipe recipe, final Boolean bookmark) {
return new RecipeBookmark(member, recipe, bookmark);
}

public void updateBookmark(final Boolean bookmark) {
this.bookmark = bookmark;
}

public Long getId() {
return id;
}

public Member getMember() {
return member;
}

public Recipe getRecipe() {
return recipe;
}

public Boolean getBookmark() {
return bookmark;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
}
Original file line number Diff line number Diff line change
@@ -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<RecipeBookmark, Long> {

Optional<RecipeBookmark> findByMemberAndRecipe(final Member member, final Recipe recipe);
}
31 changes: 31 additions & 0 deletions src/main/java/com/funeat/recipe/application/RecipeService.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -74,20 +79,23 @@ 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;

public RecipeService(final MemberRepository memberRepository, final ProductRepository productRepository,
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;
this.productRecipeRepository = productRecipeRepository;
this.recipeRepository = recipeRepository;
this.recipeImageRepository = recipeImageRepository;
this.recipeFavoriteRepository = recipeFavoriteRepository;
this.recipeBookmarkRepository = recipeBookmarkRepository;
this.commentRepository = commentRepository;
this.imageUploader = imageUploader;
}
Expand Down Expand Up @@ -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<Recipe> findRecipes = findAllByProductNameContaining(query, lastRecipeId);
final int resultSize = getResultSize(findRecipes);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.funeat.recipe.dto;

import jakarta.validation.constraints.NotNull;

public record RecipeBookmarkRequest (
@NotNull(message = "북마크를 확인해주세요")
Boolean bookmark
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.funeat.common.logging.Logging;
import com.funeat.recipe.application.RecipeService;
import com.funeat.recipe.dto.RankingRecipesResponse;
import com.funeat.recipe.dto.RecipeBookmarkRequest;
import com.funeat.recipe.dto.RecipeCommentCondition;
import com.funeat.recipe.dto.RecipeCommentCreateRequest;
import com.funeat.recipe.dto.RecipeCommentsResponse;
Expand Down Expand Up @@ -77,6 +78,16 @@ public ResponseEntity<Void> likeRecipe(@AuthenticationPrincipal final LoginInfo
return ResponseEntity.noContent().build();
}

@Logging
@PatchMapping(value = "/api/recipes/{recipeId}/bookmark")
public ResponseEntity<Void> bookmarkRecipe(@AuthenticationPrincipal final LoginInfo loginInfo,
@PathVariable final Long recipeId,
@RequestBody @Valid final RecipeBookmarkRequest request) {
recipeService.bookmarkRecipe(loginInfo.getId(), recipeId, request);

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

@GetMapping("/api/ranks/recipes")
public ResponseEntity<RankingRecipesResponse> getRankingRecipes(@AuthenticationPrincipal final LoginInfo loginInfo) {
final RankingRecipesResponse response = recipeService.getTop4Recipes(loginInfo.getId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.funeat.auth.dto.LoginInfo;
import com.funeat.auth.util.AuthenticationPrincipal;
import com.funeat.recipe.dto.RankingRecipesResponse;
import com.funeat.recipe.dto.RecipeBookmarkRequest;
import com.funeat.recipe.dto.RecipeCommentCondition;
import com.funeat.recipe.dto.RecipeCommentCreateRequest;
import com.funeat.recipe.dto.RecipeCommentsResponse;
Expand All @@ -14,7 +15,6 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
Expand All @@ -28,6 +28,8 @@
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@Tag(name = "07.Recipe", description = "꿀조합 관련 API 입니다.")
public interface RecipeController {

Expand Down Expand Up @@ -69,6 +71,16 @@ ResponseEntity<Void> likeRecipe(@AuthenticationPrincipal final LoginInfo loginIn
@PathVariable final Long recipeId,
@RequestBody final RecipeFavoriteRequest request);

@Operation(summary = "꿀조합 저장", description = "꿀조합을 저장 또는 취소를 한다.")
@ApiResponse(
responseCode = "204",
description = "꿀조합 저장(또는 저장 취소) 성공."
)
@PatchMapping
ResponseEntity<Void> bookmarkRecipe(@AuthenticationPrincipal final LoginInfo loginInfo,
@PathVariable final Long recipeId,
@RequestBody final RecipeBookmarkRequest request);

@Operation(summary = "꿀조합 랭킹 조회", description = "전체 꿀조합들 중에서 랭킹 TOP4를 조회한다.")
@ApiResponse(
responseCode = "200",
Expand Down
Loading

0 comments on commit 5546d43

Please sign in to comment.