From ca83d948ceedf82a707f70242aa99d9e44e6df11 Mon Sep 17 00:00:00 2001 From: hyun Date: Wed, 24 Jul 2024 14:55:25 +0900 Subject: [PATCH 1/5] :sparkles: implement recipe steps retrieval --- .../recipe/controller/RecipeController.java | 8 ++++- .../pengcook/recipe/domain/RecipeStep.java | 32 +++++++++++++++++++ .../recipe/dto/RecipeStepResponse.java | 16 ++++++++++ .../repository/RecipeStepRepository.java | 10 ++++++ .../recipe/service/RecipeService.java | 15 +++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/net/pengcook/recipe/domain/RecipeStep.java create mode 100644 backend/src/main/java/net/pengcook/recipe/dto/RecipeStepResponse.java create mode 100644 backend/src/main/java/net/pengcook/recipe/repository/RecipeStepRepository.java diff --git a/backend/src/main/java/net/pengcook/recipe/controller/RecipeController.java b/backend/src/main/java/net/pengcook/recipe/controller/RecipeController.java index 45d4cac5..d9423ec7 100644 --- a/backend/src/main/java/net/pengcook/recipe/controller/RecipeController.java +++ b/backend/src/main/java/net/pengcook/recipe/controller/RecipeController.java @@ -4,9 +4,10 @@ import lombok.RequiredArgsConstructor; import net.pengcook.recipe.dto.MainRecipeRequest; import net.pengcook.recipe.dto.MainRecipeResponse; +import net.pengcook.recipe.dto.RecipeStepResponse; import net.pengcook.recipe.service.RecipeService; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -21,4 +22,9 @@ public class RecipeController { public List readRecipes(@RequestBody MainRecipeRequest request) { return recipeService.readRecipes(request); } + + @GetMapping("/{id}/steps") + public List readRecipeSteps(@PathVariable long id) { + return recipeService.readRecipeSteps(id); + } } diff --git a/backend/src/main/java/net/pengcook/recipe/domain/RecipeStep.java b/backend/src/main/java/net/pengcook/recipe/domain/RecipeStep.java new file mode 100644 index 00000000..bb65e874 --- /dev/null +++ b/backend/src/main/java/net/pengcook/recipe/domain/RecipeStep.java @@ -0,0 +1,32 @@ +package net.pengcook.recipe.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.Getter; + +@Entity +@Getter +public class RecipeStep { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @ManyToOne + @JoinColumn(name = "recipe_id") + private Recipe recipe; + + private String image; + + private String description; + + private int sequence; + + public long recipeId() { + return recipe.getId(); + } +} diff --git a/backend/src/main/java/net/pengcook/recipe/dto/RecipeStepResponse.java b/backend/src/main/java/net/pengcook/recipe/dto/RecipeStepResponse.java new file mode 100644 index 00000000..14d664fd --- /dev/null +++ b/backend/src/main/java/net/pengcook/recipe/dto/RecipeStepResponse.java @@ -0,0 +1,16 @@ +package net.pengcook.recipe.dto; + +import net.pengcook.recipe.domain.RecipeStep; + +public record RecipeStepResponse(long id, long recipeId, String image, String description, int sequence) { + + public RecipeStepResponse(RecipeStep recipeStep) { + this( + recipeStep.getId(), + recipeStep.recipeId(), + recipeStep.getImage(), + recipeStep.getDescription(), + recipeStep.getSequence() + ); + } +} diff --git a/backend/src/main/java/net/pengcook/recipe/repository/RecipeStepRepository.java b/backend/src/main/java/net/pengcook/recipe/repository/RecipeStepRepository.java new file mode 100644 index 00000000..2ba40706 --- /dev/null +++ b/backend/src/main/java/net/pengcook/recipe/repository/RecipeStepRepository.java @@ -0,0 +1,10 @@ +package net.pengcook.recipe.repository; + +import java.util.List; +import net.pengcook.recipe.domain.RecipeStep; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RecipeStepRepository extends JpaRepository { + + List findAllByRecipeId(long id); +} diff --git a/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java b/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java index e301d23d..806f922f 100644 --- a/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java +++ b/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java @@ -5,13 +5,16 @@ import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import net.pengcook.recipe.domain.RecipeStep; import net.pengcook.recipe.dto.AuthorResponse; import net.pengcook.recipe.dto.CategoryResponse; import net.pengcook.recipe.dto.IngredientResponse; import net.pengcook.recipe.dto.MainRecipeRequest; import net.pengcook.recipe.dto.MainRecipeResponse; import net.pengcook.recipe.dto.RecipeDataResponse; +import net.pengcook.recipe.dto.RecipeStepResponse; import net.pengcook.recipe.repository.RecipeRepository; +import net.pengcook.recipe.repository.RecipeStepRepository; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -21,6 +24,7 @@ public class RecipeService { private final RecipeRepository recipeRepository; + private final RecipeStepRepository recipeStepRepository; public List readRecipes(MainRecipeRequest request) { Pageable pageable = PageRequest.of(request.pageNumber(), request.pageSize()); @@ -30,6 +34,17 @@ public List readRecipes(MainRecipeRequest request) { return convertToMainRecipeResponses(recipeDataResponses); } + public List readRecipeSteps(long id) { + List recipeSteps = recipeStepRepository.findAllByRecipeId(id); + return convertToRecipeStepResponses(recipeSteps); + } + + private List convertToRecipeStepResponses(List recipeSteps) { + return recipeSteps.stream() + .map(RecipeStepResponse::new) + .toList(); + } + public List convertToMainRecipeResponses(List recipeDataResponses) { Collection> groupedRecipeData = recipeDataResponses.stream() .collect(Collectors.groupingBy(RecipeDataResponse::recipeId)) From 5afd1d204be4fe7f1af635e93daf2339a4a378c1 Mon Sep 17 00:00:00 2001 From: hyun Date: Wed, 24 Jul 2024 15:11:05 +0900 Subject: [PATCH 2/5] :recycle: modify recipe retrieval to use query parameter --- .../net/pengcook/recipe/controller/RecipeController.java | 6 +++--- .../java/net/pengcook/recipe/dto/MainRecipeRequest.java | 4 ---- .../java/net/pengcook/recipe/service/RecipeService.java | 5 ++--- .../pengcook/recipe/controller/RecipeControllerTest.java | 6 +----- .../java/net/pengcook/recipe/service/RecipeServiceTest.java | 5 +---- 5 files changed, 7 insertions(+), 19 deletions(-) delete mode 100644 backend/src/main/java/net/pengcook/recipe/dto/MainRecipeRequest.java diff --git a/backend/src/main/java/net/pengcook/recipe/controller/RecipeController.java b/backend/src/main/java/net/pengcook/recipe/controller/RecipeController.java index d9423ec7..3a63a744 100644 --- a/backend/src/main/java/net/pengcook/recipe/controller/RecipeController.java +++ b/backend/src/main/java/net/pengcook/recipe/controller/RecipeController.java @@ -2,13 +2,13 @@ import java.util.List; import lombok.RequiredArgsConstructor; -import net.pengcook.recipe.dto.MainRecipeRequest; import net.pengcook.recipe.dto.MainRecipeResponse; import net.pengcook.recipe.dto.RecipeStepResponse; import net.pengcook.recipe.service.RecipeService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -19,8 +19,8 @@ public class RecipeController { private final RecipeService recipeService; @GetMapping - public List readRecipes(@RequestBody MainRecipeRequest request) { - return recipeService.readRecipes(request); + public List readRecipes(@RequestParam int pageNumber, @RequestParam int pageSize) { + return recipeService.readRecipes(pageNumber, pageSize); } @GetMapping("/{id}/steps") diff --git a/backend/src/main/java/net/pengcook/recipe/dto/MainRecipeRequest.java b/backend/src/main/java/net/pengcook/recipe/dto/MainRecipeRequest.java deleted file mode 100644 index 05974062..00000000 --- a/backend/src/main/java/net/pengcook/recipe/dto/MainRecipeRequest.java +++ /dev/null @@ -1,4 +0,0 @@ -package net.pengcook.recipe.dto; - -public record MainRecipeRequest(int pageNumber, int pageSize) { -} diff --git a/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java b/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java index 806f922f..0123a76f 100644 --- a/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java +++ b/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java @@ -9,7 +9,6 @@ import net.pengcook.recipe.dto.AuthorResponse; import net.pengcook.recipe.dto.CategoryResponse; import net.pengcook.recipe.dto.IngredientResponse; -import net.pengcook.recipe.dto.MainRecipeRequest; import net.pengcook.recipe.dto.MainRecipeResponse; import net.pengcook.recipe.dto.RecipeDataResponse; import net.pengcook.recipe.dto.RecipeStepResponse; @@ -26,8 +25,8 @@ public class RecipeService { private final RecipeRepository recipeRepository; private final RecipeStepRepository recipeStepRepository; - public List readRecipes(MainRecipeRequest request) { - Pageable pageable = PageRequest.of(request.pageNumber(), request.pageSize()); + public List readRecipes(int pageNumber, int pageSize) { + Pageable pageable = PageRequest.of(pageNumber, pageSize); List recipeIds = recipeRepository.findRecipeIds(pageable); List recipeDataResponses = recipeRepository.findRecipeData(recipeIds); diff --git a/backend/src/test/java/net/pengcook/recipe/controller/RecipeControllerTest.java b/backend/src/test/java/net/pengcook/recipe/controller/RecipeControllerTest.java index 4bf0042b..9aa2b7ee 100644 --- a/backend/src/test/java/net/pengcook/recipe/controller/RecipeControllerTest.java +++ b/backend/src/test/java/net/pengcook/recipe/controller/RecipeControllerTest.java @@ -4,7 +4,6 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; -import net.pengcook.recipe.dto.MainRecipeRequest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -28,13 +27,10 @@ void setUp() { @Test @DisplayName("레시피 개요 목록을 조회한다.") void readRecipes() { - MainRecipeRequest request = new MainRecipeRequest(0, 3); - RestAssured.given().log().all() .contentType(ContentType.JSON) - .body(request) .when() - .get("/api/recipes") + .get("/api/recipes?pageNumber=0&pageSize=3") .then().log().all() .body("size()", is(3)); } diff --git a/backend/src/test/java/net/pengcook/recipe/service/RecipeServiceTest.java b/backend/src/test/java/net/pengcook/recipe/service/RecipeServiceTest.java index ce1a2e88..1fd1bf13 100644 --- a/backend/src/test/java/net/pengcook/recipe/service/RecipeServiceTest.java +++ b/backend/src/test/java/net/pengcook/recipe/service/RecipeServiceTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.List; -import net.pengcook.recipe.dto.MainRecipeRequest; import net.pengcook.recipe.dto.MainRecipeResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; @@ -25,9 +24,7 @@ class RecipeServiceTest { @CsvSource(value = {"0,2,4", "1,2,2", "1,3,1"}) @DisplayName("요청받은 페이지의 레시피 개요 목록을 조회한다.") void readRecipes(int pageNumber, int pageSize, int expectedFirstRecipeId) { - MainRecipeRequest request = new MainRecipeRequest(pageNumber, pageSize); - - List mainRecipeResponses = recipeService.readRecipes(request); + List mainRecipeResponses = recipeService.readRecipes(pageNumber, pageSize); assertThat(mainRecipeResponses.getFirst().recipeId()).isEqualTo(expectedFirstRecipeId); } From 71318c67c84934411b67889f9d9938b3075aaa19 Mon Sep 17 00:00:00 2001 From: hyun Date: Wed, 24 Jul 2024 16:02:24 +0900 Subject: [PATCH 3/5] :recycle: add sorting feature to recipe steps --- .../net/pengcook/recipe/repository/RecipeStepRepository.java | 2 +- .../main/java/net/pengcook/recipe/service/RecipeService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/net/pengcook/recipe/repository/RecipeStepRepository.java b/backend/src/main/java/net/pengcook/recipe/repository/RecipeStepRepository.java index 2ba40706..da082f2d 100644 --- a/backend/src/main/java/net/pengcook/recipe/repository/RecipeStepRepository.java +++ b/backend/src/main/java/net/pengcook/recipe/repository/RecipeStepRepository.java @@ -6,5 +6,5 @@ public interface RecipeStepRepository extends JpaRepository { - List findAllByRecipeId(long id); + List findAllByRecipeIdOrderBySequence(long id); } diff --git a/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java b/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java index 0123a76f..8556e55f 100644 --- a/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java +++ b/backend/src/main/java/net/pengcook/recipe/service/RecipeService.java @@ -34,7 +34,7 @@ public List readRecipes(int pageNumber, int pageSize) { } public List readRecipeSteps(long id) { - List recipeSteps = recipeStepRepository.findAllByRecipeId(id); + List recipeSteps = recipeStepRepository.findAllByRecipeIdOrderBySequence(id); return convertToRecipeStepResponses(recipeSteps); } From bdd8a5110c8d2fba53a90c5bfcaf39c10de4e588 Mon Sep 17 00:00:00 2001 From: hyun Date: Wed, 24 Jul 2024 16:15:52 +0900 Subject: [PATCH 4/5] :white_check_mark: add recipe steps retrieval --- .../controller/RecipeControllerTest.java | 11 +++++++++++ .../recipe/service/RecipeServiceTest.java | 18 ++++++++++++++++++ backend/src/test/resources/data/recipe.sql | 8 ++++++++ 3 files changed, 37 insertions(+) diff --git a/backend/src/test/java/net/pengcook/recipe/controller/RecipeControllerTest.java b/backend/src/test/java/net/pengcook/recipe/controller/RecipeControllerTest.java index 9aa2b7ee..74acce12 100644 --- a/backend/src/test/java/net/pengcook/recipe/controller/RecipeControllerTest.java +++ b/backend/src/test/java/net/pengcook/recipe/controller/RecipeControllerTest.java @@ -34,4 +34,15 @@ void readRecipes() { .then().log().all() .body("size()", is(3)); } + + @Test + @DisplayName("레시피 상세 스텝을 조회한다.") + void readRecipeSteps() { + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .when() + .get("/api/recipes/1/steps") + .then().log().all() + .body("size()", is(3)); + } } diff --git a/backend/src/test/java/net/pengcook/recipe/service/RecipeServiceTest.java b/backend/src/test/java/net/pengcook/recipe/service/RecipeServiceTest.java index 1fd1bf13..9fe07511 100644 --- a/backend/src/test/java/net/pengcook/recipe/service/RecipeServiceTest.java +++ b/backend/src/test/java/net/pengcook/recipe/service/RecipeServiceTest.java @@ -2,9 +2,12 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.Arrays; import java.util.List; import net.pengcook.recipe.dto.MainRecipeResponse; +import net.pengcook.recipe.dto.RecipeStepResponse; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.springframework.beans.factory.annotation.Autowired; @@ -28,4 +31,19 @@ void readRecipes(int pageNumber, int pageSize, int expectedFirstRecipeId) { assertThat(mainRecipeResponses.getFirst().recipeId()).isEqualTo(expectedFirstRecipeId); } + + @Test + @DisplayName("특정 레시피의 스텝을 sequence 순서로 조회한다.") + void readRecipeSteps() { + long recipeId = 1L; + List expectedRecipeStepResponses = Arrays.asList( + new RecipeStepResponse(1L, 1, "레시피1 설명1 이미지", "레시피1 설명1", 1), + new RecipeStepResponse(3L, 1, "레시피1 설명2 이미지", "레시피1 설명2", 2), + new RecipeStepResponse(2L, 1, "레시피1 설명3 이미지", "레시피1 설명3", 3) + ); + + List recipeStepResponses = recipeService.readRecipeSteps(recipeId); + + assertThat(recipeStepResponses).isEqualTo(expectedRecipeStepResponses); + } } diff --git a/backend/src/test/resources/data/recipe.sql b/backend/src/test/resources/data/recipe.sql index 8d05ff42..6cbd2ad3 100644 --- a/backend/src/test/resources/data/recipe.sql +++ b/backend/src/test/resources/data/recipe.sql @@ -18,6 +18,9 @@ ALTER TABLE category_recipe ALTER COLUMN id RESTART; TRUNCATE TABLE ingredient_recipe; ALTER TABLE ingredient_recipe ALTER COLUMN id RESTART; +TRUNCATE TABLE recipe_step; +ALTER TABLE recipe_step ALTER COLUMN id RESTART; + SET REFERENTIAL_INTEGRITY TRUE; INSERT INTO users (email, username, nickname, image, birth, region) @@ -76,3 +79,8 @@ VALUES (1, 1, 'REQUIRED'), (3, 3, 'REQUIRED'), (7, 3, 'REQUIRED'), (2, 4, 'REQUIRED'); + +INSERT INTO recipe_step (recipe_id, image, description, sequence) +VALUES (1, '레시피1 설명1 이미지', '레시피1 설명1', 1), + (1, '레시피1 설명3 이미지', '레시피1 설명3', 3), + (1, '레시피1 설명2 이미지', '레시피1 설명2', 2); From f8553a361753be4f64a729b2592dac04e1c0bf2b Mon Sep 17 00:00:00 2001 From: hyun Date: Wed, 24 Jul 2024 16:26:14 +0900 Subject: [PATCH 5/5] :recycle: add access modifier to Lombok constructor --- backend/src/main/java/net/pengcook/recipe/domain/Recipe.java | 3 ++- .../src/main/java/net/pengcook/recipe/domain/RecipeStep.java | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/net/pengcook/recipe/domain/Recipe.java b/backend/src/main/java/net/pengcook/recipe/domain/Recipe.java index c8672f3d..46b9f53d 100644 --- a/backend/src/main/java/net/pengcook/recipe/domain/Recipe.java +++ b/backend/src/main/java/net/pengcook/recipe/domain/Recipe.java @@ -8,13 +8,14 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import java.time.LocalTime; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import net.pengcook.user.domain.User; @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Getter public class Recipe { diff --git a/backend/src/main/java/net/pengcook/recipe/domain/RecipeStep.java b/backend/src/main/java/net/pengcook/recipe/domain/RecipeStep.java index bb65e874..3468f297 100644 --- a/backend/src/main/java/net/pengcook/recipe/domain/RecipeStep.java +++ b/backend/src/main/java/net/pengcook/recipe/domain/RecipeStep.java @@ -6,9 +6,14 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; @Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter public class RecipeStep {