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..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,12 +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.RequestBody; +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 @@ -18,7 +19,12 @@ 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") + public List readRecipeSteps(@PathVariable long id) { + return recipeService.readRecipeSteps(id); } } 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 new file mode 100644 index 00000000..3468f297 --- /dev/null +++ b/backend/src/main/java/net/pengcook/recipe/domain/RecipeStep.java @@ -0,0 +1,37 @@ +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.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@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/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/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..da082f2d --- /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 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 e301d23d..8556e55f 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,15 @@ 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,15 +23,27 @@ 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); return convertToMainRecipeResponses(recipeDataResponses); } + public List readRecipeSteps(long id) { + List recipeSteps = recipeStepRepository.findAllByRecipeIdOrderBySequence(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)) 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..74acce12 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,21 @@ void setUp() { @Test @DisplayName("레시피 개요 목록을 조회한다.") void readRecipes() { - MainRecipeRequest request = new MainRecipeRequest(0, 3); + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .when() + .get("/api/recipes?pageNumber=0&pageSize=3") + .then().log().all() + .body("size()", is(3)); + } + @Test + @DisplayName("레시피 상세 스텝을 조회한다.") + void readRecipeSteps() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .body(request) .when() - .get("/api/recipes") + .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 ce1a2e88..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,10 +2,12 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.Arrays; import java.util.List; -import net.pengcook.recipe.dto.MainRecipeRequest; 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; @@ -25,10 +27,23 @@ 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); } + + @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);