diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ai/application/AiRecipeRecommendClient.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ai/application/AiRecipeRecommendClient.java new file mode 100644 index 0000000..c101c01 --- /dev/null +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ai/application/AiRecipeRecommendClient.java @@ -0,0 +1,31 @@ +package com.sundaegukbap.banchango.ai.application; + +import com.sundaegukbap.banchango.ai.dto.AiRecipeRecommendRequest; +import com.sundaegukbap.banchango.ai.dto.AiRecipeRecommendResponse; +import com.sundaegukbap.banchango.ingredient.domain.Ingredient; +import com.sundaegukbap.banchango.recipe.domain.RecipeCategory; +import jakarta.transaction.Transactional; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +@Component +public class AiRecipeRecommendClient { + @Value("${api.aiBaseUrl}") + private String aiBaseUrl; + private final RestTemplate restTemplate; + + public AiRecipeRecommendClient(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Transactional + public List getRecommendedRecipesFromAI(RecipeCategory category, List ingredientList) { + AiRecipeRecommendRequest request = AiRecipeRecommendRequest.of(ingredientList); + AiRecipeRecommendResponse response = restTemplate.postForObject(aiBaseUrl + "/recommend", request, AiRecipeRecommendResponse.class); + + return response.recommended_recipes(); + } +} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ai/dto/AiRecipeRecommendRequest.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ai/dto/AiRecipeRecommendRequest.java new file mode 100644 index 0000000..ab1056b --- /dev/null +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ai/dto/AiRecipeRecommendRequest.java @@ -0,0 +1,18 @@ +package com.sundaegukbap.banchango.ai.dto; + +import com.sundaegukbap.banchango.ingredient.domain.Ingredient; + +import java.util.List; +import java.util.stream.Collectors; + +public record AiRecipeRecommendRequest( + List ingredients +) { + public static AiRecipeRecommendRequest of(List ingredients) { + List ingredientIds = ingredients.stream() + .map(Ingredient::getId) + .collect(Collectors.toList()); + + return new AiRecipeRecommendRequest(ingredientIds); + } +} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ai/dto/AiRecipeRecommendResponse.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ai/dto/AiRecipeRecommendResponse.java new file mode 100644 index 0000000..e4ac973 --- /dev/null +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ai/dto/AiRecipeRecommendResponse.java @@ -0,0 +1,8 @@ +package com.sundaegukbap.banchango.ai.dto; + +import java.util.List; + +public record AiRecipeRecommendResponse( + List recommended_recipes +) { +} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/bookmark/application/RecipeBookmarkService.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/bookmark/application/RecipeBookmarkService.java index 986036c..c1b7a3e 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/bookmark/application/RecipeBookmarkService.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/bookmark/application/RecipeBookmarkService.java @@ -6,24 +6,23 @@ import com.sundaegukbap.banchango.recipe.repository.RecipeRepository; import com.sundaegukbap.banchango.user.domain.User; import com.sundaegukbap.banchango.user.repository.UserRepository; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; @Service -@Transactional @RequiredArgsConstructor public class RecipeBookmarkService { private final RecipeBookmarkRepository recipeBookmarkRepository; private final UserRepository userRepository; private final RecipeRepository recipeRepository; + @Transactional public List getBookmarkedRecipes(Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new NoSuchElementException()); @@ -37,6 +36,7 @@ public List getBookmarkedRecipes(Long userId) { return result; } + @Transactional public String clickBookmark(Long userId, Long recipeId) { User user = userRepository.findById(userId) .orElseThrow(() -> new NoSuchElementException("no user")); diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/bookmark/presentation/RecipeBookmarkController.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/bookmark/presentation/RecipeBookmarkController.java index c50ac89..163cee1 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/bookmark/presentation/RecipeBookmarkController.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/bookmark/presentation/RecipeBookmarkController.java @@ -21,7 +21,7 @@ public RecipeBookmarkController(RecipeBookmarkService recipeBookmarkService) { @GetMapping("/{userId}") @Operation(summary = "북마크한 레시피 목록 조회", description = "레시피 북마크 리스트를 조회한다.") - public ResponseEntity getRecommandRecipes(@PathVariable("userId") Long userId) { + public ResponseEntity getRecommendRecipes(@PathVariable("userId") Long userId) { List response = recipeBookmarkService.getBookmarkedRecipes(userId); return new ResponseEntity<>(response, HttpStatus.OK); } diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/config/AppConfig.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/config/AppConfig.java new file mode 100644 index 0000000..48fd0ed --- /dev/null +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/config/AppConfig.java @@ -0,0 +1,14 @@ +package com.sundaegukbap.banchango.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class AppConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/config/JpaAuditingConfig.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/config/JpaAuditingConfig.java index 0511375..b12ede0 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/config/JpaAuditingConfig.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/config/JpaAuditingConfig.java @@ -6,5 +6,4 @@ @Configuration @EnableJpaAuditing public class JpaAuditingConfig { - -} \ No newline at end of file +} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/container/application/ContainerService.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/container/application/ContainerService.java index 6fc6cbd..e9e51c7 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/container/application/ContainerService.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/container/application/ContainerService.java @@ -6,21 +6,20 @@ import com.sundaegukbap.banchango.container.repository.ContainerRepository; import com.sundaegukbap.banchango.user.domain.User; import com.sundaegukbap.banchango.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.List; import java.util.NoSuchElementException; @Service +@RequiredArgsConstructor public class ContainerService { private final ContainerRepository containerRepository; private final UserRepository userRepository; - public ContainerService(ContainerRepository containerRepository, UserRepository userRepository) { - this.containerRepository = containerRepository; - this.userRepository = userRepository; - } - + @Transactional public void createContainer(Long userId, ContainerInsertRequest request) { User user = userRepository.findById(userId) .orElseThrow(() -> new NoSuchElementException("no user")); @@ -30,6 +29,7 @@ public void createContainer(Long userId, ContainerInsertRequest request) { containerRepository.save(container); } + @Transactional public List getAllContainers(Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new NoSuchElementException("no user")); diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientMatcher.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientMatcher.java index ca05bde..81e2dee 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientMatcher.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientMatcher.java @@ -2,30 +2,28 @@ import com.sundaegukbap.banchango.container.domain.Container; import com.sundaegukbap.banchango.container.repository.ContainerRepository; +import com.sundaegukbap.banchango.ingredient.domain.ContainerIngredient; import com.sundaegukbap.banchango.ingredient.domain.Ingredient; import com.sundaegukbap.banchango.ingredient.domain.RecipeRequiringIngredient; -import com.sundaegukbap.banchango.ingredient.domain.ContainerIngredient; -import com.sundaegukbap.banchango.ingredient.repository.RecipeRequiringIngredientRepository; import com.sundaegukbap.banchango.ingredient.repository.ContainerIngredientRepository; +import com.sundaegukbap.banchango.ingredient.repository.RecipeRequiringIngredientRepository; import com.sundaegukbap.banchango.recipe.domain.Recipe; import com.sundaegukbap.banchango.user.domain.User; +import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.stream.Collectors; @Component +@AllArgsConstructor public class IngredientMatcher { private ContainerRepository containerRepository; private ContainerIngredientRepository containerIngredientRepository; private RecipeRequiringIngredientRepository recipeRequiringIngredientRepository; - public IngredientMatcher(ContainerRepository containerRepository, ContainerIngredientRepository containerIngredientRepository, RecipeRequiringIngredientRepository recipeRequiringIngredientRepository) { - this.containerRepository = containerRepository; - this.containerIngredientRepository = containerIngredientRepository; - this.recipeRequiringIngredientRepository = recipeRequiringIngredientRepository; - } - public HashMap checkIngredientRelation(User user, Recipe recipe){ List havingIngredients = getAllIngredientsWithUser(user); List requiringIngredients = getIngredientsWithRecipe(recipe); @@ -50,16 +48,12 @@ public HashMap checkIngredientRelation(User user, Recipe recipe){ private List getAllIngredientsWithUser(User user){ List containers = containerRepository.findAllByUser(user); + List containerIngredientList = containerIngredientRepository.findByContainerIn(containers); + List ingredients = containerIngredientList.stream() + .map(ContainerIngredient::getIngredient) + .collect(Collectors.toList()); - Set ingredients = new HashSet<>(); - for(Container container : containers){ - List containerIngredientList = containerIngredientRepository.findAllByContainer(container); - for(ContainerIngredient ci : containerIngredientList){ - ingredients.add(ci.getIngredient()); - } - } - - return ingredients.stream().toList(); + return ingredients; } private List getIngredientsWithRecipe(Recipe recipe) { diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientQueryService.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientQueryService.java index 9b63335..9f7431e 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientQueryService.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientQueryService.java @@ -1,29 +1,25 @@ package com.sundaegukbap.banchango.ingredient.application; + import com.sundaegukbap.banchango.container.domain.Container; import com.sundaegukbap.banchango.container.repository.ContainerRepository; import com.sundaegukbap.banchango.ingredient.domain.ContainerIngredient; -import com.sundaegukbap.banchango.ingredient.dto.CategoryIngredientResponse; -import com.sundaegukbap.banchango.ingredient.dto.CategoryIngredientResponses; -import com.sundaegukbap.banchango.ingredient.dto.IngredientDetailResponse; -import com.sundaegukbap.banchango.ingredient.dto.IngredientDetailResponses; import com.sundaegukbap.banchango.ingredient.dto.dto.ContainerIngredientDto; import com.sundaegukbap.banchango.ingredient.dto.dto.ContainerIngredientDtos; import com.sundaegukbap.banchango.ingredient.repository.ContainerIngredientRepository; +import jakarta.transaction.Transactional; +import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; -import java.util.*; -import java.util.stream.Collectors; +import java.util.List; +import java.util.NoSuchElementException; @Service +@AllArgsConstructor public class IngredientQueryService { private final ContainerRepository containerRepository; private final ContainerIngredientRepository containerIngredientRepository; - public IngredientQueryService(ContainerRepository containerRepository, ContainerIngredientRepository containerIngredientRepository) { - this.containerRepository = containerRepository; - this.containerIngredientRepository = containerIngredientRepository; - } - + @Transactional public ContainerIngredientDtos getUserIngredients(Long userId) { List containerList = containerRepository.findAllByUserId(userId); List containerIngredientList = containerIngredientRepository.findByContainerIn(containerList); @@ -31,37 +27,17 @@ public ContainerIngredientDtos getUserIngredients(Long userId) { return ContainerIngredientDtos.of(containerIngredientList); } + @Transactional public ContainerIngredientDtos getContainerIngredients(Long containerId) { List containerIngredientList = containerIngredientRepository.findAllByContainerId(containerId); return ContainerIngredientDtos.of(containerIngredientList); } + @Transactional public ContainerIngredientDto getIngredientInfo(Long containerIngredientId) { ContainerIngredient containerIngredient = containerIngredientRepository.findById(containerIngredientId) .orElseThrow(() -> new NoSuchElementException("no ingredient in container")); return ContainerIngredientDto.of(containerIngredient); } - - public CategoryIngredientResponses getCategoryIngredientResponses(Long containerId) { - List containerIngredients = containerIngredientRepository.findAllByContainerId(containerId); - - Map> kindIngredientsMap = containerIngredients.stream() - .collect(Collectors.groupingBy(userHavingIngredient -> userHavingIngredient.getIngredient().getKind())); - - List categoryIngredientResponseList = new ArrayList<>(); - kindIngredientsMap.forEach((kind, userHavingIngredientsList) -> { - List ingredientDetailResponseList = userHavingIngredientsList.stream() - .map(IngredientDetailResponse::of) - .collect(Collectors.toList()); - - IngredientDetailResponses ingredientDetailResponses = IngredientDetailResponses.of(ingredientDetailResponseList); - - categoryIngredientResponseList.add(CategoryIngredientResponse.of(kind, ingredientDetailResponses)); - }); - - return CategoryIngredientResponses.of(categoryIngredientResponseList); - } - - } diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientService.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientService.java index 51790b1..03d64ba 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientService.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/application/IngredientService.java @@ -2,30 +2,28 @@ import com.sundaegukbap.banchango.container.domain.Container; import com.sundaegukbap.banchango.container.repository.ContainerRepository; -import com.sundaegukbap.banchango.ingredient.domain.Ingredient; import com.sundaegukbap.banchango.ingredient.domain.ContainerIngredient; +import com.sundaegukbap.banchango.ingredient.domain.Ingredient; import com.sundaegukbap.banchango.ingredient.dto.IngredientInsertRequest; -import com.sundaegukbap.banchango.ingredient.repository.IngredientRepository; import com.sundaegukbap.banchango.ingredient.repository.ContainerIngredientRepository; -import com.sundaegukbap.banchango.user.repository.UserRepository; +import com.sundaegukbap.banchango.ingredient.repository.IngredientRepository; +import com.sundaegukbap.banchango.ingredient.dto.event.IngredientChangedEvent; +import jakarta.transaction.Transactional; +import lombok.AllArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import java.util.NoSuchElementException; @Service +@AllArgsConstructor public class IngredientService { private final ContainerIngredientRepository containerIngredientRepository; - private final UserRepository userRepository; private final IngredientRepository ingredientRepository; private final ContainerRepository containerRepository; + private final ApplicationEventPublisher applicationEventPublisher; - public IngredientService(ContainerIngredientRepository containerIngredientRepository, UserRepository userRepository, IngredientRepository ingredientRepository, ContainerRepository containerRepository) { - this.containerIngredientRepository = containerIngredientRepository; - this.userRepository = userRepository; - this.ingredientRepository = ingredientRepository; - this.containerRepository = containerRepository; - } - + @Transactional public void insertIngredient(Long userId, IngredientInsertRequest request) { Container container = containerRepository.findById(request.containerId()) .orElseThrow(() -> new NoSuchElementException("no container")); @@ -34,12 +32,17 @@ public void insertIngredient(Long userId, IngredientInsertRequest request) { ContainerIngredient containerIngredient = request.toEntity(container, ingredient); containerIngredientRepository.save(containerIngredient); + + applicationEventPublisher.publishEvent(new IngredientChangedEvent(userId)); } - public void removeIngredient(Long containerIngredientId) { + @Transactional + public void removeIngredient(Long userId, Long containerIngredientId) { ContainerIngredient containerIngredient = containerIngredientRepository.findById(containerIngredientId) .orElseThrow(() -> new NoSuchElementException("no ingredient in container")); containerIngredientRepository.delete(containerIngredient); + + applicationEventPublisher.publishEvent(new IngredientChangedEvent(userId)); } } diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/domain/Ingredient.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/domain/Ingredient.java index ce076f2..d756f3b 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/domain/Ingredient.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/domain/Ingredient.java @@ -1,10 +1,13 @@ package com.sundaegukbap.banchango.ingredient.domain; -import jakarta.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -24,4 +27,7 @@ public class Ingredient { private String kind; private String image; + public Ingredient(Long id) { + this.id = id; + } } diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/dto/event/IngredientChangedEvent.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/dto/event/IngredientChangedEvent.java new file mode 100644 index 0000000..e2c1d60 --- /dev/null +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/dto/event/IngredientChangedEvent.java @@ -0,0 +1,6 @@ +package com.sundaegukbap.banchango.ingredient.dto.event; + +public record IngredientChangedEvent( + Long userId +) { +} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/presentation/IngredientController.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/presentation/IngredientController.java index c523609..a44b816 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/presentation/IngredientController.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/ingredient/presentation/IngredientController.java @@ -34,7 +34,7 @@ public ResponseEntity insertIngredient(@PathVariable("userId") Long user @DeleteMapping("/{containerIngredientId}") @Operation(summary = "소유 재료 제거", description = "소유한 재료를 제거한다.") public ResponseEntity removeIngredient(@PathVariable("containerIngredientId") Long containerIngredientId) { - ingredientService.removeIngredient(containerIngredientId); + ingredientService.removeIngredient(1L,containerIngredientId); return new ResponseEntity<>("success remove ingredient", HttpStatus.OK); } diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/application/RecipeQueryService.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/application/RecipeQueryService.java new file mode 100644 index 0000000..5072323 --- /dev/null +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/application/RecipeQueryService.java @@ -0,0 +1,74 @@ +package com.sundaegukbap.banchango.recipe.application; + +import com.sundaegukbap.banchango.ingredient.application.IngredientMatcher; +import com.sundaegukbap.banchango.ingredient.domain.Ingredient; +import com.sundaegukbap.banchango.recipe.domain.Recipe; +import com.sundaegukbap.banchango.recipe.domain.UserRecommendedRecipe; +import com.sundaegukbap.banchango.recipe.dto.response.RecommendedRecipeResponse; +import com.sundaegukbap.banchango.recipe.dto.response.RecommendedRecipeResponses; +import com.sundaegukbap.banchango.recipe.repository.RecipeRepository; +import com.sundaegukbap.banchango.recipe.repository.RecommendedRecipeRepository; +import com.sundaegukbap.banchango.user.domain.User; +import com.sundaegukbap.banchango.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class RecipeQueryService { + private final RecipeRepository recipeRepository; + private final UserRepository userRepository; + private final RecommendedRecipeRepository recommendedRecipeRepository; + private final IngredientMatcher ingredientMatcher; + private final ApplicationEventPublisher applicationEventPublisher; + + @Transactional + public RecommendedRecipeResponse getRecipeDetail(Long userId, Long recipeId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NoSuchElementException("no user")); + Recipe recipe = recipeRepository.findById(recipeId) + .orElseThrow(() -> new NoSuchElementException("no recipe")); + + return resolveRecipeWithUser(user, recipe); + } + + @Transactional + public RecommendedRecipeResponses getRecommendedRecipes(int pageIndex, int pageSize, Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NoSuchElementException("no user")); + + int firstDataIndex = pageIndex * pageSize; + List recipes; + if(firstDataIndex >= 50){ + recipes = recipeRepository.findRecipesByRandom(pageSize); + } else { + PageRequest pageRequest = PageRequest.of(pageIndex, pageSize); + List userRecommendedRecipeList = recommendedRecipeRepository.findAllByUser(pageRequest, user).getContent(); + recipes = userRecommendedRecipeList.stream() + .map(r -> r.getRecipe()) + .collect(Collectors.toList()); + } + + List recommendedRecipeResponseList = recipes.stream() + .map(recipe -> resolveRecipeWithUser(user, recipe)) + .collect(Collectors.toList()); + + return RecommendedRecipeResponses.of(recommendedRecipeResponseList); + } + + public RecommendedRecipeResponse resolveRecipeWithUser(User user, Recipe recipe) { + HashMap ingredientRelation = ingredientMatcher.checkIngredientRelation(user, recipe); + List have = ingredientRelation.get("have"); + List need = ingredientRelation.get("need"); + + return RecommendedRecipeResponse.of(recipe, have, need); + } +} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/application/RecipeService.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/application/RecipeService.java index 3a1a2b9..a976ec7 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/application/RecipeService.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/application/RecipeService.java @@ -1,72 +1,67 @@ package com.sundaegukbap.banchango.recipe.application; -import com.sundaegukbap.banchango.bookmark.repository.RecipeBookmarkRepository; -import com.sundaegukbap.banchango.ingredient.application.IngredientMatcher; +import com.sundaegukbap.banchango.ai.application.AiRecipeRecommendClient; +import com.sundaegukbap.banchango.container.domain.Container; +import com.sundaegukbap.banchango.container.repository.ContainerRepository; +import com.sundaegukbap.banchango.ingredient.domain.ContainerIngredient; import com.sundaegukbap.banchango.ingredient.domain.Ingredient; +import com.sundaegukbap.banchango.ingredient.dto.event.IngredientChangedEvent; +import com.sundaegukbap.banchango.ingredient.repository.ContainerIngredientRepository; import com.sundaegukbap.banchango.recipe.domain.Recipe; -import com.sundaegukbap.banchango.recipe.domain.UserRecommandedRecipe; -import com.sundaegukbap.banchango.recipe.dto.RecommandedRecipeResponse; -import com.sundaegukbap.banchango.recipe.dto.RecommandedRecipeResponses; +import com.sundaegukbap.banchango.recipe.domain.RecipeCategory; +import com.sundaegukbap.banchango.recipe.domain.UserRecommendedRecipe; import com.sundaegukbap.banchango.recipe.repository.RecipeRepository; -import com.sundaegukbap.banchango.recipe.repository.RecommandedRecipeRepository; +import com.sundaegukbap.banchango.recipe.repository.RecommendedRecipeRepository; import com.sundaegukbap.banchango.user.domain.User; import com.sundaegukbap.banchango.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.NoSuchElementException; import java.util.stream.Collectors; -//recipe와 관련된 조작을 수행한다. @Service +@RequiredArgsConstructor public class RecipeService { private final RecipeRepository recipeRepository; private final UserRepository userRepository; - private final RecipeBookmarkRepository recipeBookmarkRepository; - private final RecommandedRecipeRepository recommandedRecipeRepository; - private final IngredientMatcher ingredientMatcher; + private final RecommendedRecipeRepository recommendedRecipeRepository; + private final ContainerIngredientRepository containerIngredientRepository; + private final ContainerRepository containerRepository; + private final AiRecipeRecommendClient aiRecipeRecommendClient; - public RecipeService(RecipeRepository recipeRepository, UserRepository userRepository, RecipeBookmarkRepository recipeBookmarkRepository, RecommandedRecipeRepository recommandedRecipeRepository, IngredientMatcher ingredientMatcher) { - this.recipeRepository = recipeRepository; - this.userRepository = userRepository; - this.recipeBookmarkRepository = recipeBookmarkRepository; - this.recommandedRecipeRepository = recommandedRecipeRepository; - this.ingredientMatcher = ingredientMatcher; + @EventListener + public void refreshRecommendedRecipes(IngredientChangedEvent event) { + refreshRecommendedRecipes(event.userId(), RecipeCategory.전체); } - public RecommandedRecipeResponse getRecipeDetail(Long userId, Long recipeId){ - User user = userRepository.findById(userId) - .orElseThrow(() -> new NoSuchElementException("no user")); - Recipe recipe = recipeRepository.findById(recipeId) - .orElseThrow(() -> new NoSuchElementException("no recipe")); - - return resolveRecipeWithUser(user, recipe); + public void changeRecipeCategory(Long userId, RecipeCategory recipeCategory) { + refreshRecommendedRecipes(userId, recipeCategory); } - public RecommandedRecipeResponses getRecommandedRecipes(Long userId) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new NoSuchElementException("no user")); - - List userRecommandedRecipeList = recommandedRecipeRepository.findAllByUser(user); - List recipes = userRecommandedRecipeList.stream() - .map(r -> r.getRecipe()) + @Transactional + public void refreshRecommendedRecipes(Long userId, RecipeCategory recipeCategory) { + List containers = containerRepository.findAllByUserId(userId); + List containerIngredients = containerIngredientRepository.findByContainerIn(containers); + List ingredients = containerIngredients.stream() + .map(ContainerIngredient::getIngredient) .collect(Collectors.toList()); - //레시피를 순회하면서 사용자와의 관계 파악해서 RecommandedRecipeResponse추가 - List recommandedRecipeResponseList = recipes.stream() - .map(recipe -> resolveRecipeWithUser(user, recipe)) - .collect(Collectors.toList()); + recommendedRecipeRepository.deleteAllByUserId(userId); - return RecommandedRecipeResponses.of(recommandedRecipeResponseList); - } - - public RecommandedRecipeResponse resolveRecipeWithUser(User user, Recipe recipe) { - HashMap ingredientRelation = ingredientMatcher.checkIngredientRelation(user, recipe); - List have = ingredientRelation.get("have"); - List need = ingredientRelation.get("need"); - - return RecommandedRecipeResponse.of(recipe, have, need); + User user = userRepository.findById(userId) + .orElseThrow(() -> new NoSuchElementException("no user")); + List recommendedRecipeIds = aiRecipeRecommendClient.getRecommendedRecipesFromAI(recipeCategory, ingredients); + List recipes = recipeRepository.findAllById(recommendedRecipeIds); + List recommendedRecipes = recipes.stream() + .map(recipe -> UserRecommendedRecipe.builder() + .user(user) + .recipe(recipe) + .build()) + .collect(Collectors.toList()); + recommendedRecipeRepository.saveAll(recommendedRecipes); } } diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/Recipe.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/Recipe.java index d241048..93ccc56 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/Recipe.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/Recipe.java @@ -4,8 +4,6 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; @@ -20,7 +18,6 @@ @Table(name="recipes") public class Recipe { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotNull private String name; @@ -37,13 +34,13 @@ public class Recipe { private int cookingTime; @NotNull @Enumerated(EnumType.STRING) - private Difficulty difficulty; + private RecipeDifficulty recipeDifficulty; private String bySort; private String byIngredient; - private String bySituation; + private RecipeCategory recipeCategory; @Builder - public Recipe(Long id, String name, String bestName, String introduction, String image1, String image2, String link, int servings, int cookingTime, Difficulty difficulty, String bySort, String byIngredient, String bySituation) { + public Recipe(Long id, String name, String bestName, String introduction, String image1, String image2, String link, int servings, int cookingTime, RecipeDifficulty recipeDifficulty, String bySort, String byIngredient, RecipeCategory recipeCategory) { this.id = id; this.name = name; this.bestName = bestName; @@ -52,10 +49,10 @@ public Recipe(Long id, String name, String bestName, String introduction, String this.image2 = image2; this.servings = servings; this.cookingTime = cookingTime; - this.difficulty = difficulty; + this.recipeDifficulty = recipeDifficulty; this.bySort = bySort; this.byIngredient = byIngredient; - this.bySituation = bySituation; + this.recipeCategory = recipeCategory; } public String getLink(){ diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/RecipeCategory.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/RecipeCategory.java new file mode 100644 index 0000000..d75e70b --- /dev/null +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/RecipeCategory.java @@ -0,0 +1,18 @@ +package com.sundaegukbap.banchango.recipe.domain; + +public enum RecipeCategory { + 전체, + 간식, + 기타, + 다이어트, + 도시락, + 명절, + 손님접대, + 술안주, + 야식, + 이유식, + 일상, + 초스피드, + 푸드스타일링, + 해장 +} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/Difficulty.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/RecipeDifficulty.java similarity index 76% rename from Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/Difficulty.java rename to Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/RecipeDifficulty.java index 6e18c61..e111e5b 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/Difficulty.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/RecipeDifficulty.java @@ -1,6 +1,6 @@ package com.sundaegukbap.banchango.recipe.domain; -public enum Difficulty { +public enum RecipeDifficulty { 아무나, 초급, 중급, diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/UserRecommandedRecipe.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/UserRecommendedRecipe.java similarity index 70% rename from Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/UserRecommandedRecipe.java rename to Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/UserRecommendedRecipe.java index 5b6c4e3..bcea2d5 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/UserRecommandedRecipe.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/domain/UserRecommendedRecipe.java @@ -2,16 +2,16 @@ import com.sundaegukbap.banchango.user.domain.User; import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name="user_recommanded_recipes") -public class UserRecommandedRecipe { +@Table(name="user_recommended_recipes") +public class UserRecommendedRecipe { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -21,4 +21,10 @@ public class UserRecommandedRecipe { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="recipe_id") private Recipe recipe; + + @Builder + public UserRecommendedRecipe(User user, Recipe recipe) { + this.user = user; + this.recipe = recipe; + } } diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/RecommandedRecipeResponses.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/RecommandedRecipeResponses.java deleted file mode 100644 index 8e3772f..0000000 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/RecommandedRecipeResponses.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.sundaegukbap.banchango.recipe.dto; - -import java.util.List; - -public record RecommandedRecipeResponses( - List recommandedRecipeResponses) { - public static RecommandedRecipeResponses of(List recommandedRecipeResponseList){ - return new RecommandedRecipeResponses(recommandedRecipeResponseList); - } -} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/dto/RecipeDto.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/dto/RecipeDto.java index 7082593..7b9ee3a 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/dto/RecipeDto.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/dto/RecipeDto.java @@ -1,6 +1,6 @@ package com.sundaegukbap.banchango.recipe.dto.dto; -import com.sundaegukbap.banchango.recipe.domain.Difficulty; +import com.sundaegukbap.banchango.recipe.domain.RecipeDifficulty; import com.sundaegukbap.banchango.recipe.domain.Recipe; public record RecipeDto( @@ -11,7 +11,7 @@ public record RecipeDto( String link, int servings, int cookingTime, - Difficulty difficulty + RecipeDifficulty recipeDifficulty ) { public static RecipeDto of(Recipe recipe){ return new RecipeDto( @@ -22,7 +22,7 @@ public static RecipeDto of(Recipe recipe){ recipe.getLink(), recipe.getServings(), recipe.getCookingTime(), - recipe.getDifficulty() + recipe.getRecipeDifficulty() ); } } diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/RecipeUserRelation.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/dto/RecipeUserRelation.java similarity index 88% rename from Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/RecipeUserRelation.java rename to Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/dto/RecipeUserRelation.java index 9a13a50..b2aae6d 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/RecipeUserRelation.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/dto/RecipeUserRelation.java @@ -1,4 +1,4 @@ -package com.sundaegukbap.banchango.recipe.dto; +package com.sundaegukbap.banchango.recipe.dto.dto; import java.util.List; diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/event/RecommendedRecipeCategoryChangedEvent.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/event/RecommendedRecipeCategoryChangedEvent.java new file mode 100644 index 0000000..32ae4ae --- /dev/null +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/event/RecommendedRecipeCategoryChangedEvent.java @@ -0,0 +1,9 @@ +package com.sundaegukbap.banchango.recipe.dto.event; + +import com.sundaegukbap.banchango.recipe.domain.RecipeCategory; + +public record RecommendedRecipeCategoryChangedEvent( + Long userId, + RecipeCategory recipeCategory +) { +} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/RecommandedRecipeResponse.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/response/RecommendedRecipeResponse.java similarity index 72% rename from Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/RecommandedRecipeResponse.java rename to Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/response/RecommendedRecipeResponse.java index b35fdd0..215a61b 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/RecommandedRecipeResponse.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/response/RecommendedRecipeResponse.java @@ -1,4 +1,4 @@ -package com.sundaegukbap.banchango.recipe.dto; +package com.sundaegukbap.banchango.recipe.dto.response; import com.sundaegukbap.banchango.ingredient.domain.Ingredient; import com.sundaegukbap.banchango.ingredient.dto.dto.IngredientDtos; @@ -7,12 +7,12 @@ import java.util.List; -public record RecommandedRecipeResponse( +public record RecommendedRecipeResponse( RecipeDto recipe, IngredientDtos have, IngredientDtos need) { - public static RecommandedRecipeResponse of(Recipe recipe, List have, List need){ - return new RecommandedRecipeResponse( + public static RecommendedRecipeResponse of(Recipe recipe, List have, List need){ + return new RecommendedRecipeResponse( RecipeDto.of(recipe), IngredientDtos.of(have), IngredientDtos.of(need) diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/response/RecommendedRecipeResponses.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/response/RecommendedRecipeResponses.java new file mode 100644 index 0000000..ed3ebae --- /dev/null +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/dto/response/RecommendedRecipeResponses.java @@ -0,0 +1,10 @@ +package com.sundaegukbap.banchango.recipe.dto.response; + +import java.util.List; + +public record RecommendedRecipeResponses( + List recommendedRecipeRespons) { + public static RecommendedRecipeResponses of(List recommendedRecipeResponseList){ + return new RecommendedRecipeResponses(recommendedRecipeResponseList); + } +} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/presentation/RecipeController.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/presentation/RecipeController.java index 1c04172..914d90e 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/presentation/RecipeController.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/presentation/RecipeController.java @@ -1,40 +1,51 @@ package com.sundaegukbap.banchango.recipe.presentation; +import com.sundaegukbap.banchango.recipe.application.RecipeQueryService; import com.sundaegukbap.banchango.recipe.application.RecipeService; -import com.sundaegukbap.banchango.recipe.dto.RecommandedRecipeResponse; -import com.sundaegukbap.banchango.recipe.dto.RecommandedRecipeResponses; +import com.sundaegukbap.banchango.recipe.domain.RecipeCategory; +import com.sundaegukbap.banchango.recipe.dto.response.RecommendedRecipeResponse; +import com.sundaegukbap.banchango.recipe.dto.response.RecommendedRecipeResponses; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RestController @RequestMapping("/api/recipe") -@Tag(name = "레시피 관련 컨트롤러") +@AllArgsConstructor +@Tag(name = "레시피 조회 관련 컨트롤러") public class RecipeController { + private final RecipeQueryService recipeQueryService; private final RecipeService recipeService; - public RecipeController(RecipeService recipeService) { - this.recipeService = recipeService; - } - - @GetMapping("/recommand/{userId}") - @Operation(summary = "추천 레시피 목록 조회", description = "추천 레시피 목록을 조회한다.") - public ResponseEntity getRecommandedRecipes(@PathVariable("userId") Long userId) { - RecommandedRecipeResponses response = recipeService.getRecommandedRecipes(userId); + @GetMapping("/recommend/{userId}") + @Operation(summary = "추천 레시피 목록 조회", description = "추천 레시피 목록을 조회합니다.") + public ResponseEntity getRecommendedRecipes(@PathVariable("userId") Long userId, + @RequestParam(defaultValue = "0") int pageIndex, + @RequestParam(defaultValue = "10") int pageSize) { + RecommendedRecipeResponses response = recipeQueryService.getRecommendedRecipes(pageIndex, pageSize, userId); return new ResponseEntity<>(response, HttpStatus.OK); } @GetMapping("/{userId}/{recipeId}") @Operation(summary = "특정 레시피 상세 조회", description = "레시피를 조회한다.") - public ResponseEntity getRecipeDetail(@PathVariable("userId") Long userId, @PathVariable("recipeId") Long recipeId) { - RecommandedRecipeResponse response = recipeService.getRecipeDetail(userId,recipeId); + public ResponseEntity getRecipeDetail(@PathVariable("userId") Long userId, @PathVariable("recipeId") Long recipeId) { + RecommendedRecipeResponse response = recipeQueryService.getRecipeDetail(userId, recipeId); return new ResponseEntity<>(response, HttpStatus.OK); } + + @PostMapping("/{userId}") + @Operation(summary = "추천 레시피 카테고리 변경", description = "추천 레시피 카테고리를 변경하고 새로운 추천 레시피를 받아옵니다.") + public ResponseEntity changeRecipeCategory(@PathVariable("userId") Long userId, + @RequestParam(defaultValue = "전체") RecipeCategory recipeCategory) { + recipeService.changeRecipeCategory(userId, recipeCategory); + return new ResponseEntity<>("카테고리에 맞게 추천 레시피목록이 변경되었습니다.", HttpStatus.OK); + } } diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/repository/RecipeRepository.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/repository/RecipeRepository.java index 2ae3000..63dc9e0 100644 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/repository/RecipeRepository.java +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/repository/RecipeRepository.java @@ -2,8 +2,14 @@ import com.sundaegukbap.banchango.recipe.domain.Recipe; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface RecipeRepository extends JpaRepository { + @Query(value = "SELECT * FROM recipes ORDER BY RAND() LIMIT :size", nativeQuery = true) + List findRecipesByRandom(@Param("size") int size); } diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/repository/RecommandedRecipeRepository.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/repository/RecommandedRecipeRepository.java deleted file mode 100644 index 1f1e1c5..0000000 --- a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/repository/RecommandedRecipeRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.sundaegukbap.banchango.recipe.repository; - -import com.sundaegukbap.banchango.recipe.domain.UserRecommandedRecipe; -import com.sundaegukbap.banchango.user.domain.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface RecommandedRecipeRepository extends JpaRepository { - List findAllByUser(User user); -} diff --git a/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/repository/RecommendedRecipeRepository.java b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/repository/RecommendedRecipeRepository.java new file mode 100644 index 0000000..201a3fc --- /dev/null +++ b/Server/banchango/src/main/java/com/sundaegukbap/banchango/recipe/repository/RecommendedRecipeRepository.java @@ -0,0 +1,14 @@ +package com.sundaegukbap.banchango.recipe.repository; + +import com.sundaegukbap.banchango.recipe.domain.UserRecommendedRecipe; +import com.sundaegukbap.banchango.user.domain.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface RecommendedRecipeRepository extends JpaRepository { + Page findAllByUser(Pageable pageable, User user); + void deleteAllByUserId(Long userId); +} diff --git a/Server/banchango/src/main/resources/application-local.properties b/Server/banchango/src/main/resources/application-local.properties index e15c230..6a031e7 100644 --- a/Server/banchango/src/main/resources/application-local.properties +++ b/Server/banchango/src/main/resources/application-local.properties @@ -9,4 +9,4 @@ spring.jpa.hibernate.ddl-auto=update # spring boot 2.5.x to use script spring.sql.init.mode=always # script use after hibernate initilalization -spring.jpa.defer-datasource-initialization=true \ No newline at end of file +spring.jpa.defer-datasource-initialization=true diff --git a/Server/banchango/src/main/resources/application.properties b/Server/banchango/src/main/resources/application.properties index d6711d0..37e29c0 100644 --- a/Server/banchango/src/main/resources/application.properties +++ b/Server/banchango/src/main/resources/application.properties @@ -1,6 +1,8 @@ spring.application.name=banchango -spring.profiles.active=prod +spring.profiles.active=local -app.cors.allowedOrigins=http://localhost:3000,http://localhost:8080 -app.oauth2.authorizedRedirectUris=http://localhost:3000/oauth2/redirect,myandroidapp://oauth2/redirect,myiosapp://oauth2/redirect +app.cors.allowedOrigins=http://34.222.135.30:8000,http://localhost:3000,http://localhost:8080 +app.oauth2.authorizedRedirectUris=http://34.222.135.30:8000,http://localhost:3000/oauth2/redirect,myandroidapp://oauth2/redirect,myiosapp://oauth2/redirect + +api.aiBaseUrl=http://34.222.135.30:8000 diff --git a/Server/banchango/src/test/java/com/sundaegukbap/banchango/recipe/application/RecipeServiceTest.java b/Server/banchango/src/test/java/com/sundaegukbap/banchango/recipe/application/RecipeServiceTest.java index 415aec3..5ffcf72 100644 --- a/Server/banchango/src/test/java/com/sundaegukbap/banchango/recipe/application/RecipeServiceTest.java +++ b/Server/banchango/src/test/java/com/sundaegukbap/banchango/recipe/application/RecipeServiceTest.java @@ -1,126 +1,140 @@ -package com.sundaegukbap.banchango.recipe.application; - -import com.sundaegukbap.banchango.bookmark.domain.RecipeBookmark; -import com.sundaegukbap.banchango.bookmark.repository.RecipeBookmarkRepository; -import com.sundaegukbap.banchango.ingredient.application.IngredientMatcher; -import com.sundaegukbap.banchango.recipe.domain.Difficulty; -import com.sundaegukbap.banchango.recipe.domain.Recipe; -import com.sundaegukbap.banchango.recipe.dto.RecommandedRecipeResponse; -import com.sundaegukbap.banchango.recipe.repository.RecipeRepository; -import com.sundaegukbap.banchango.user.domain.User; -import com.sundaegukbap.banchango.user.repository.UserRepository; -import org.junit.jupiter.api.*; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Spy; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.when; - -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -@ExtendWith(MockitoExtension.class) -public class RecipeServiceTest { - @Mock - private RecipeRepository recipeRepository; - @Mock - private UserRepository userRepository; - @Mock - private IngredientMatcher ingredientMatcher; - @Mock - private RecipeBookmarkRepository recipeBookmarkRepository; - @InjectMocks - @Spy - private RecipeService recipeService; - - User user; - Recipe recipe; - RecipeBookmark recipeBookmark; - List have,need; - HashMap ingredientRelation; - RecommandedRecipeResponse recommandedRecipeResponse; - - @BeforeEach - void setUp(){ - user = User.builder() - .id(1L) - .build(); - - recipe = Recipe.builder() - .id(1L) - .name("간장계란밥") - .introduction("맛있습니다.") - .image1("image.png") - .link("link") - .servings(4) - .cookingTime(30) - .difficulty(Difficulty.초급) - .build(); - - recipeBookmark = RecipeBookmark.builder() - .id(1L) - .user(user) - .recipe(recipe) - .build(); - - have = new ArrayList<>(List.of( - "김치" - )); - - need = new ArrayList<>(List.of( - "밥" - )); - - ingredientRelation = new HashMap<>(Map.of( - "have", have, - "need", need - )); - - recommandedRecipeResponse = RecommandedRecipeResponse.of( - recipe, - have, - need - ); - } - - @Nested - @DisplayName("recipeServiceQueryService") - class 레시피_조회 { - @Test - @DisplayName("recipeService.getRecipe()") - void 단일_레시피_상세정보_조회() { - //given - when(userRepository.findById(1L)).thenReturn(Optional.of(user)); - when(recipeRepository.findById(1L)).thenReturn(Optional.of(recipe)); - when(ingredientMatcher.checkIngredientRelation(user,recipe)).thenReturn(ingredientRelation); - RecommandedRecipeResponse expected = recommandedRecipeResponse; - - //when - RecommandedRecipeResponse result = recipeService.getRecipe(1L, 1L); - - //then - assertThat(result).isEqualTo(expected); - } - - @Test - @DisplayName("recipeService.getRecommandedRecipes()") - void 추천_레시피_목록_조회() { - //given - when(userRepository.findById(1L)).thenReturn(Optional.of(user)); - when(recipeBookmarkRepository.findAllByUser(user)).thenReturn(Arrays.asList(recipeBookmark)); - doReturn(recommandedRecipeResponse).when(recipeService).getRecipe(user.getId(),recipe.getId()); - List expected = Arrays.asList(recommandedRecipeResponse); - - //when - List result = recipeService.getRecommandedRecipes(user.getId()); - - //then - assertThat(result).isEqualTo(expected); - } - } -} +//package com.sundaegukbap.banchango.recipe.application; +// +//import com.sundaegukbap.banchango.bookmark.domain.RecipeBookmark; +//import com.sundaegukbap.banchango.bookmark.repository.RecipeBookmarkRepository; +//import com.sundaegukbap.banchango.ingredient.application.IngredientMatcher; +//import com.sundaegukbap.banchango.recipe.domain.Recipe; +//import com.sundaegukbap.banchango.recipe.domain.RecipeDifficulty; +//import com.sundaegukbap.banchango.recipe.dto.response.RecommendedRecipeResponse; +//import com.sundaegukbap.banchango.recipe.dto.response.RecommendedRecipeResponses; +//import com.sundaegukbap.banchango.recipe.repository.RecipeRepository; +//import com.sundaegukbap.banchango.user.domain.User; +//import com.sundaegukbap.banchango.user.repository.UserRepository; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.DisplayName; +//import org.junit.jupiter.api.DisplayNameGeneration; +//import org.junit.jupiter.api.DisplayNameGenerator; +//import org.junit.jupiter.api.Nested; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mockito.InjectMocks; +//import org.mockito.Mock; +//import org.mockito.Spy; +//import org.mockito.junit.jupiter.MockitoExtension; +// +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +//import java.util.Optional; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.mockito.Mockito.doReturn; +//import static org.mockito.Mockito.when; +// +//@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +//@SuppressWarnings("NonAsciiCharacters") +//@ExtendWith(MockitoExtension.class) +//public class RecipeServiceTest { +// @Mock +// private RecipeRepository recipeRepository; +// @Mock +// private UserRepository userRepository; +// @Mock +// private IngredientMatcher ingredientMatcher; +// @Mock +// private RecipeBookmarkRepository recipeBookmarkRepository; +// @InjectMocks +// @Spy +// private RecipeService recipeService; +// @InjectMocks +// @Spy +// private RecipeQueryService recipeQueryService; +// +// User user; +// Recipe recipe; +// RecipeBookmark recipeBookmark; +// List have,need; +// HashMap ingredientRelation; +// RecommendedRecipeResponse recommendedRecipeResponse; +// +// @BeforeEach +// void setUp(){ +// user = User.builder() +// .id(1L) +// .build(); +// +// recipe = Recipe.builder() +// .id(1L) +// .name("간장계란밥") +// .introduction("맛있습니다.") +// .image1("image.png") +// .link("link") +// .servings(4) +// .cookingTime(30) +// .recipeDifficulty(RecipeDifficulty.초급) +// .build(); +// +// recipeBookmark = RecipeBookmark.builder() +// .id(1L) +// .user(user) +// .recipe(recipe) +// .build(); +// +// have = new ArrayList<>(List.of( +// "김치" +// )); +// +// need = new ArrayList<>(List.of( +// "밥" +// )); +// +// ingredientRelation = new HashMap<>(Map.of( +// "have", have, +// "need", need +// )); +// +// recommendedRecipeResponse = RecommendedRecipeResponse.of( +// recipe, +// null, +// null +// ); +// } +// +// @Nested +// @DisplayName("recipeServiceQueryService") +// class 레시피_조회 { +// @Test +// @DisplayName("recipeService.getRecipe()") +// void 단일_레시피_상세정보_조회() { +// //given +// when(userRepository.findById(1L)).thenReturn(Optional.of(user)); +// when(recipeRepository.findById(1L)).thenReturn(Optional.of(recipe)); +// when(ingredientMatcher.checkIngredientRelation(user,recipe)).thenReturn(ingredientRelation); +// RecommendedRecipeResponse expected = recommendedRecipeResponse; +// +// //when +// RecommendedRecipeResponse result = recipeQueryService.getRecipeDetail(1L, 1L); +// +// //then +// assertThat(result).isEqualTo(expected); +// } +// +// @Test +// @DisplayName("recipeService.getRecommendedRecipes()") +// void 추천_레시피_목록_조회() { +// //given +// when(userRepository.findById(1L)).thenReturn(Optional.of(user)); +// when(recipeBookmarkRepository.findAllByUser(user)).thenReturn(Arrays.asList(recipeBookmark)); +// doReturn(recommendedRecipeResponse).when(recipeQueryService).getRecipeDetail(user.getId(),recipe.getId()); +// List expected = Arrays.asList(recommendedRecipeResponse); +// +// //when +// RecommendedRecipeResponses result = recipeQueryService.getRecommendedRecipes(user.getId()); +// +// //then +// assertThat(result).isEqualTo(expected); +// } +// } +//} diff --git a/Server/banchango/src/test/java/com/sundaegukbap/banchango/recipe/presentation/RecipeControllerTest.java b/Server/banchango/src/test/java/com/sundaegukbap/banchango/recipe/presentation/RecipeControllerTest.java index 8790c62..b2973b9 100644 --- a/Server/banchango/src/test/java/com/sundaegukbap/banchango/recipe/presentation/RecipeControllerTest.java +++ b/Server/banchango/src/test/java/com/sundaegukbap/banchango/recipe/presentation/RecipeControllerTest.java @@ -1,112 +1,123 @@ -package com.sundaegukbap.banchango.recipe.presentation; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.sundaegukbap.banchango.recipe.application.RecipeService; -import com.sundaegukbap.banchango.recipe.domain.Difficulty; -import com.sundaegukbap.banchango.recipe.dto.RecommandedRecipeResponse; -import com.sundaegukbap.banchango.support.CustomWebMvcTest; -import org.junit.jupiter.api.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.given; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@CustomWebMvcTest -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -public class RecipeControllerTest { - @Autowired - MockMvc mockMvc; - - @Autowired - ObjectMapper objectMapper; - - @MockBean - RecipeService recipeService; - - RecommandedRecipeResponse recommandedRecipeResponse; - @BeforeEach - void setUp() { - recommandedRecipeResponse = - new RecommandedRecipeResponse( - 1L, - "간장계란볶음밥", - "달짝지근함", - "test.png", - "test.link", - new ArrayList<>( - List.of("간장", "밥") - ), - new ArrayList<>( - List.of("계란", "당근") - ), - 1, - 30, - Difficulty.아무나 - ); - } - @Nested - @DisplayName("/api/recipe/") - class 레시피_조회 { - @Test - @DisplayName("/recommand/{userId}") - void 추천_레시피_조회() throws Exception { - //given - List expected = new ArrayList<>( - List.of(recommandedRecipeResponse) - ); - - given(recipeService.getRecommandedRecipes(anyLong())) - .willReturn(expected); - - // when - String content = mockMvc.perform(get("/api/recipe/recommand/{userId}", 1L) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(print()) - .andReturn() - .getResponse() - .getContentAsString(StandardCharsets.UTF_8); - List actual = objectMapper.readValue(content, new TypeReference>() { - }); - - //then - assertThat(actual).isEqualTo(expected); - } - - @Test - @DisplayName("/{userId}/{recipeId}") - void 상세_레시피_조회() throws Exception { - //given - RecommandedRecipeResponse expected = recommandedRecipeResponse; - - given(recipeService.getRecipe(anyLong(), anyLong())) - .willReturn(expected); - - // when - String content = mockMvc.perform(get("/api/recipe/{userId}/{recipeId}", 1L, 1L) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(print()) - .andReturn() - .getResponse() - .getContentAsString(StandardCharsets.UTF_8); - RecommandedRecipeResponse actual = objectMapper.readValue(content, new TypeReference() {}); - - //then - assertThat(actual).isEqualTo(expected); - } - } -} +//package com.sundaegukbap.banchango.recipe.presentation; +// +//import com.fasterxml.jackson.core.type.TypeReference; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import com.sundaegukbap.banchango.recipe.application.RecipeQueryService; +//import com.sundaegukbap.banchango.recipe.application.RecipeService; +//import com.sundaegukbap.banchango.recipe.domain.RecipeDifficulty; +//import com.sundaegukbap.banchango.recipe.dto.response.RecommendedRecipeResponse; +//import com.sundaegukbap.banchango.recipe.dto.response.RecommendedRecipeResponses; +//import com.sundaegukbap.banchango.support.CustomWebMvcTest; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.DisplayName; +//import org.junit.jupiter.api.DisplayNameGeneration; +//import org.junit.jupiter.api.DisplayNameGenerator; +//import org.junit.jupiter.api.Nested; +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.mock.mockito.MockBean; +//import org.springframework.http.MediaType; +//import org.springframework.test.web.servlet.MockMvc; +// +//import java.nio.charset.StandardCharsets; +//import java.util.ArrayList; +//import java.util.List; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.mockito.ArgumentMatchers.anyLong; +//import static org.mockito.BDDMockito.given; +//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +// +//@CustomWebMvcTest +//@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +//@SuppressWarnings("NonAsciiCharacters") +//public class RecipeControllerTest { +// @Autowired +// MockMvc mockMvc; +// +// @Autowired +// ObjectMapper objectMapper; +// +// @MockBean +// RecipeService recipeService; +// +// @MockBean +// RecipeQueryService recipeQueryService; +// +// RecommendedRecipeResponse recommendedRecipeResponse; +// +// @BeforeEach +// void setUp() { +// recommendedRecipeResponse = +// new RecommendedRecipeResponse( +// 1L, +// "간장계란볶음밥", +// "달짝지근함", +// "test.png", +// "test.link", +// new ArrayList<>( +// List.of("간장", "밥") +// ), +// new ArrayList<>( +// List.of("계란", "당근") +// ), +// 1, +// 30, +// RecipeDifficulty.아무나 +// ); +// } +// +// @Nested +// @DisplayName("/api/recipe/") +// class 레시피_조회 { +// @Test +// @DisplayName("/recommend/{userId}") +// void 추천_레시피_조회() throws Exception { +// //given +// RecommendedRecipeResponses expected = RecommendedRecipeResponses.of(recommendedRecipeResponse); +// +// given(recipeQueryService.getRecommendedRecipes(anyLong())) +// .willReturn(expected); +// +// // when +// String content = mockMvc.perform(get("/api/recipe/recommend/{userId}", 1L) +// .contentType(MediaType.APPLICATION_JSON)) +// .andExpect(status().isOk()) +// .andDo(print()) +// .andReturn() +// .getResponse() +// .getContentAsString(StandardCharsets.UTF_8); +// List actual = objectMapper.readValue(content, new TypeReference>() { +// }); +// +// //then +// assertThat(actual).isEqualTo(expected); +// } +// +// @Test +// @DisplayName("/{userId}/{recipeId}") +// void 상세_레시피_조회() throws Exception { +// //given +// RecommendedRecipeResponse expected = recommendedRecipeResponse; +// +// given(recipeService.getRecipe(anyLong(), anyLong())) +// .willReturn(expected); +// +// // when +// String content = mockMvc.perform(get("/api/recipe/{userId}/{recipeId}", 1L, 1L) +// .contentType(MediaType.APPLICATION_JSON)) +// .andExpect(status().isOk()) +// .andDo(print()) +// .andReturn() +// .getResponse() +// .getContentAsString(StandardCharsets.UTF_8); +// RecommendedRecipeResponse actual = objectMapper.readValue(content, new TypeReference() { +// }); +// +// //then +// assertThat(actual).isEqualTo(expected); +// } +// } +//}