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

[BE] feat: 꿀조합 작성 기능 구현 #349

Merged
merged 15 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from 11 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 @@ -22,4 +22,12 @@ public class ProductRecipe {
@ManyToOne
@JoinColumn(name = "recipe_id")
private Recipe recipe;

protected ProductRecipe() {
}

public ProductRecipe(final Product product, final Recipe recipe) {
this.product = product;
this.recipe = recipe;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.funeat.recipe.application;

import com.funeat.member.domain.Member;
import com.funeat.member.persistence.MemberRepository;
import com.funeat.product.domain.ProductRecipe;
import com.funeat.product.persistence.ProductRecipeRepository;
import com.funeat.product.persistence.ProductRepository;
import com.funeat.recipe.domain.Recipe;
import com.funeat.recipe.domain.RecipeImage;
import com.funeat.recipe.dto.RecipeCreateRequest;
import com.funeat.recipe.persistence.RecipeImageRepository;
import com.funeat.recipe.persistence.RecipeRepository;
import com.funeat.review.application.ImageService;
import java.util.List;
import java.util.Objects;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Service
@Transactional(readOnly = true)
public class RecipeService {

private final MemberRepository memberRepository;
private final ProductRepository productRepository;
private final ProductRecipeRepository productRecipeRepository;
private final RecipeRepository recipeRepository;
private final RecipeImageRepository recipeImageRepository;

private final ImageService imageService;
hanueleee marked this conversation as resolved.
Show resolved Hide resolved

public RecipeService(final MemberRepository memberRepository, final ProductRepository productRepository,
final ProductRecipeRepository productRecipeRepository, final RecipeRepository recipeRepository,
final RecipeImageRepository recipeImageRepository,
final ImageService imageService) {
this.memberRepository = memberRepository;
this.productRepository = productRepository;
this.productRecipeRepository = productRecipeRepository;
this.recipeRepository = recipeRepository;
this.recipeImageRepository = recipeImageRepository;
this.imageService = imageService;
}

@Transactional
public Long create(final Long memberId, final List<MultipartFile> images, final RecipeCreateRequest request) {
final Member member = memberRepository.findById(memberId)
.orElseThrow(IllegalArgumentException::new);

final Recipe savedRecipe = recipeRepository.save(new Recipe(request.getName(), request.getContent(), member));
request.getProductIds()
.stream()
.map(it -> productRepository.findById(it)
.orElseThrow(IllegalArgumentException::new))
.forEach(it -> productRecipeRepository.save(
new ProductRecipe(it, savedRecipe)
));
hanueleee marked this conversation as resolved.
Show resolved Hide resolved

if (Objects.nonNull(images)) {
images.stream()
.peek(it -> recipeImageRepository.save(new RecipeImage(it.getOriginalFilename(), savedRecipe)))
.forEach(imageService::upload);
}

return savedRecipe.getId();
}
}
33 changes: 22 additions & 11 deletions backend/src/main/java/com/funeat/recipe/domain/Recipe.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package com.funeat.recipe.domain;

import com.funeat.member.domain.Member;
import com.funeat.member.domain.bookmark.RecipeBookmark;
import com.funeat.member.domain.favorite.RecipeFavorite;
import com.funeat.product.domain.ProductRecipe;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;

@Entity
public class Recipe {
Expand All @@ -28,12 +23,28 @@ public class Recipe {
@JoinColumn(name = "member_id")
private Member member;

@OneToMany(mappedBy = "recipe")
private List<ProductRecipe> productRecipes;
protected Recipe() {
}

@OneToMany(mappedBy = "recipe")
private List<RecipeFavorite> recipeFavorites;
public Recipe(final String name, final String content, final Member member) {
this.name = name;
this.content = content;
this.member = member;
}

@OneToMany(mappedBy = "recipe")
private List<RecipeBookmark> recipeBookmarks;
public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getContent() {
return content;
}

public Member getMember() {
return member;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@ public class RecipeImage {
@ManyToOne
@JoinColumn(name = "recipe_id")
private Recipe recipe;

protected RecipeImage() {
}

public RecipeImage(final String image, final Recipe recipe) {
this.image = image;
this.recipe = recipe;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.funeat.recipe.dto;

import java.util.List;

public class RecipeCreateRequest {

private final String name;
private final List<Long> productIds;
private final String content;

public RecipeCreateRequest(final String name, final List<Long> productIds, final String content) {
this.name = name;
this.productIds = productIds;
this.content = content;
}

public String getName() {
return name;
}

public List<Long> getProductIds() {
return productIds;
}

public String getContent() {
return content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.funeat.recipe.presentation;

import com.funeat.auth.dto.LoginInfo;
import com.funeat.auth.util.AuthenticationPrincipal;
import com.funeat.recipe.application.RecipeService;
import com.funeat.recipe.dto.RecipeCreateRequest;
import java.net.URI;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
public class RecipeApiController implements RecipeController{

private final RecipeService recipeService;

public RecipeApiController(final RecipeService recipeService) {
this.recipeService = recipeService;
}

@PostMapping(value = "/api/recipes", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Void> writeRecipe(@AuthenticationPrincipal final LoginInfo loginInfo,
@RequestPart(required = false) final List<MultipartFile> images,
@RequestPart final RecipeCreateRequest recipeRequest) {
final Long recipeId = recipeService.create(loginInfo.getId(), images, recipeRequest);

return ResponseEntity.created(URI.create("/api/recipes/" + recipeId)).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.funeat.recipe.presentation;

import com.funeat.auth.dto.LoginInfo;
import com.funeat.auth.util.AuthenticationPrincipal;
import com.funeat.recipe.dto.RecipeCreateRequest;
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.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

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

@Operation(summary = "꿀조합 추가", description = "꿀조합을 작성한다.")
@ApiResponse(
responseCode = "201",
description = "꿀조합 작성 성공."
)
@PostMapping
ResponseEntity<Void> writeRecipe(@AuthenticationPrincipal LoginInfo loginInfo,
@RequestPart List<MultipartFile> images,
@RequestPart RecipeCreateRequest recipeRequest);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,22 @@ public class ReviewService {
private final ReviewTagRepository reviewTagRepository;
private final MemberRepository memberRepository;
private final ProductRepository productRepository;
private final ImageService imageService;

private final ReviewFavoriteRepository reviewFavoriteRepository;

private final ImageService imageService;

public ReviewService(final ReviewRepository reviewRepository, final TagRepository tagRepository,
final ReviewTagRepository reviewTagRepository, final MemberRepository memberRepository,
final ProductRepository productRepository, final ImageService imageService,
final ReviewFavoriteRepository reviewFavoriteRepository) {
final ProductRepository productRepository,
final ReviewFavoriteRepository reviewFavoriteRepository,
final ImageService imageService) {
this.reviewRepository = reviewRepository;
this.tagRepository = tagRepository;
this.reviewTagRepository = reviewTagRepository;
this.memberRepository = memberRepository;
this.productRepository = productRepository;
this.imageService = imageService;
this.reviewFavoriteRepository = reviewFavoriteRepository;
this.imageService = imageService;
hanueleee marked this conversation as resolved.
Show resolved Hide resolved
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ class FuneatApplicationTests {
@Test
void contextLoads() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.funeat.acceptance.recipe;

import static com.funeat.acceptance.common.CommonSteps.STATUS_CODE를_검증한다;
import static com.funeat.acceptance.common.CommonSteps.정상_생성;
import static com.funeat.acceptance.product.ProductSteps.간편식사;
import static com.funeat.acceptance.recipe.RecipeSteps.레시피_추가_요청;
import static com.funeat.acceptance.recipe.RecipeSteps.여러_사진_요청;

import com.funeat.acceptance.common.AcceptanceTest;
import com.funeat.acceptance.common.LoginSteps;
import com.funeat.product.domain.Category;
import com.funeat.product.domain.Product;
import com.funeat.recipe.dto.RecipeCreateRequest;
import io.restassured.specification.MultiPartSpecification;
import java.util.List;
import org.junit.jupiter.api.Test;

public class RecipeAcceptanceTest extends AcceptanceTest {
hanueleee marked this conversation as resolved.
Show resolved Hide resolved

@Test
void 레시피를_작성한다() {
// given
카테고리_추가_요청(간편식사);
final var product1 = new Product("삼각김밥1", 1000L, "image.png", "맛있는 삼각김밥1", 간편식사);
final var product2 = new Product("삼각김밥2", 2000L, "image.png", "맛있는 삼각김밥2", 간편식사);
final var product3 = new Product("삼각김밥3", 1500L, "image.png", "맛있는 삼각김밥3", 간편식사);
복수_상품_추가_요청(List.of(product1, product2, product3));
hanueleee marked this conversation as resolved.
Show resolved Hide resolved
final var loginCookie = LoginSteps.로그인_쿠키를_얻는다();

// when
final var request = new RecipeCreateRequest("제일로 맛있는 레시피",
List.of(product1.getId(), product2.getId(), product3.getId()),
"우선 밥을 넣어요. 그리고 밥을 또 넣어요. 그리고 밥을 또 넣으면.. 끝!!");
final var images = 여러_사진_요청(3);
final var response = 레시피_추가_요청(request, images, loginCookie);

// then
STATUS_CODE를_검증한다(response, 정상_생성);
}

private Long 카테고리_추가_요청(final Category category) {
return categoryRepository.save(category).getId();
}

private void 복수_상품_추가_요청(final List<Product> products) {
productRepository.saveAll(products);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.funeat.acceptance.recipe;

import static io.restassured.RestAssured.given;

import com.funeat.recipe.dto.RecipeCreateRequest;
import io.restassured.builder.MultiPartSpecBuilder;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import io.restassured.specification.MultiPartSpecification;
import io.restassured.specification.RequestSpecification;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class RecipeSteps {

public static ExtractableResponse<Response> 레시피_추가_요청(final RecipeCreateRequest recipeRequest,
final List<MultiPartSpecification> imageList,
final String loginCookie) {
final var request = given()
.cookie("JSESSIONID", loginCookie);
imageList.forEach(request::multiPart);
return request
.multiPart("recipeRequest", recipeRequest, "application/json")
.when()
.post("/api/recipes")
.then()
.extract();
}

public static List<MultiPartSpecification> 여러_사진_요청(int count) {
return IntStream.range(0, count)
.mapToObj(i -> new MultiPartSpecBuilder("image".getBytes())
.fileName("testImage.png")
.controlName("images")
.mimeType("image/png")
.build())
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.funeat.review.application;
package com.funeat.common;

import com.funeat.review.application.ImageService;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
Expand Down
Loading
Loading