Skip to content

Commit

Permalink
[Server] feat : 추천 레시피 관련 추가 요구사항 처리 (#50) (#51)
Browse files Browse the repository at this point in the history
* fix : recipe의 id는 만개의 레시피에서 직접 지정한다.

* refact : 사용하지 않는 메서드 제거

* refact : recommand -> recommend

* feat : ai api와 연결 확인 및 기본 베이스 구현

* refact : aiBaseUrl은 프로퍼티에 저장해두고 사용한다.

* feat : AI와 연결하여 유저의 소유 재료 변경시 event를 발행해 recipe를 새로 추천 받아 DB에 저장한다. 및 리팩토링

* refact : 폴더 구조 리팩토링 및 테스트 코드 주석처리

* refact : 서비스 클래스에서 @RequiredConstructor를 사용하여 의존성을 주입받고, 메서드에 @transactional을 적용한다.

* feat : 레시피 카테고리 추가

* refact : IngredientChangedEvent는 ingredientService에서 발행하므로 위치 변경

* refact : import 수정

* feat : 추천 레시피 목록 조회시 페이지네이션을 수행한다.

* feat : 추천 레시피 카테고리를 변경하는 api 추가

* refact : 불필요한 transactional 제거

* fix : 레시피 테이블의 이름은 recipes이다.

* refact : EOL 추가
  • Loading branch information
Due-IT authored Oct 2, 2024
1 parent b2e13a9 commit 6498a3f
Show file tree
Hide file tree
Showing 34 changed files with 621 additions and 422 deletions.
Original file line number Diff line number Diff line change
@@ -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<Long> getRecommendedRecipesFromAI(RecipeCategory category, List<Ingredient> ingredientList) {
AiRecipeRecommendRequest request = AiRecipeRecommendRequest.of(ingredientList);
AiRecipeRecommendResponse response = restTemplate.postForObject(aiBaseUrl + "/recommend", request, AiRecipeRecommendResponse.class);

return response.recommended_recipes();
}
}
Original file line number Diff line number Diff line change
@@ -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<Long> ingredients
) {
public static AiRecipeRecommendRequest of(List<Ingredient> ingredients) {
List<Long> ingredientIds = ingredients.stream()
.map(Ingredient::getId)
.collect(Collectors.toList());

return new AiRecipeRecommendRequest(ingredientIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.sundaegukbap.banchango.ai.dto;

import java.util.List;

public record AiRecipeRecommendResponse(
List<Long> recommended_recipes
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Long> getBookmarkedRecipes(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new NoSuchElementException());
Expand All @@ -37,6 +36,7 @@ public List<Long> getBookmarkedRecipes(Long userId) {
return result;
}

@Transactional
public String clickBookmark(Long userId, Long recipeId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new NoSuchElementException("no user"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public RecipeBookmarkController(RecipeBookmarkService recipeBookmarkService) {

@GetMapping("/{userId}")
@Operation(summary = "북마크한 레시피 목록 조회", description = "레시피 북마크 리스트를 조회한다.")
public ResponseEntity<List> getRecommandRecipes(@PathVariable("userId") Long userId) {
public ResponseEntity<List> getRecommendRecipes(@PathVariable("userId") Long userId) {
List<Long> response = recipeBookmarkService.getBookmarkedRecipes(userId);
return new ResponseEntity<>(response, HttpStatus.OK);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@
@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand All @@ -30,6 +29,7 @@ public void createContainer(Long userId, ContainerInsertRequest request) {
containerRepository.save(container);
}

@Transactional
public List<ContainerDto> getAllContainers(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new NoSuchElementException("no user"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String,List> checkIngredientRelation(User user, Recipe recipe){
List<Ingredient> havingIngredients = getAllIngredientsWithUser(user);
List<Ingredient> requiringIngredients = getIngredientsWithRecipe(recipe);
Expand All @@ -50,16 +48,12 @@ public HashMap<String,List> checkIngredientRelation(User user, Recipe recipe){

private List<Ingredient> getAllIngredientsWithUser(User user){
List<Container> containers = containerRepository.findAllByUser(user);
List<ContainerIngredient> containerIngredientList = containerIngredientRepository.findByContainerIn(containers);
List<Ingredient> ingredients = containerIngredientList.stream()
.map(ContainerIngredient::getIngredient)
.collect(Collectors.toList());

Set<Ingredient> ingredients = new HashSet<>();
for(Container container : containers){
List<ContainerIngredient> containerIngredientList = containerIngredientRepository.findAllByContainer(container);
for(ContainerIngredient ci : containerIngredientList){
ingredients.add(ci.getIngredient());
}
}

return ingredients.stream().toList();
return ingredients;
}

private List<Ingredient> getIngredientsWithRecipe(Recipe recipe) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,67 +1,43 @@
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<Container> containerList = containerRepository.findAllByUserId(userId);
List<ContainerIngredient> containerIngredientList = containerIngredientRepository.findByContainerIn(containerList);

return ContainerIngredientDtos.of(containerIngredientList);
}

@Transactional
public ContainerIngredientDtos getContainerIngredients(Long containerId) {
List<ContainerIngredient> 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<ContainerIngredient> containerIngredients = containerIngredientRepository.findAllByContainerId(containerId);

Map<String, List<ContainerIngredient>> kindIngredientsMap = containerIngredients.stream()
.collect(Collectors.groupingBy(userHavingIngredient -> userHavingIngredient.getIngredient().getKind()));

List<CategoryIngredientResponse> categoryIngredientResponseList = new ArrayList<>();
kindIngredientsMap.forEach((kind, userHavingIngredientsList) -> {
List<IngredientDetailResponse> ingredientDetailResponseList = userHavingIngredientsList.stream()
.map(IngredientDetailResponse::of)
.collect(Collectors.toList());

IngredientDetailResponses ingredientDetailResponses = IngredientDetailResponses.of(ingredientDetailResponseList);

categoryIngredientResponseList.add(CategoryIngredientResponse.of(kind, ingredientDetailResponses));
});

return CategoryIngredientResponses.of(categoryIngredientResponseList);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand All @@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -24,4 +27,7 @@ public class Ingredient {
private String kind;
private String image;

public Ingredient(Long id) {
this.id = id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.sundaegukbap.banchango.ingredient.dto.event;

public record IngredientChangedEvent(
Long userId
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public ResponseEntity<String> insertIngredient(@PathVariable("userId") Long user
@DeleteMapping("/{containerIngredientId}")
@Operation(summary = "소유 재료 제거", description = "소유한 재료를 제거한다.")
public ResponseEntity<String> removeIngredient(@PathVariable("containerIngredientId") Long containerIngredientId) {
ingredientService.removeIngredient(containerIngredientId);
ingredientService.removeIngredient(1L,containerIngredientId);
return new ResponseEntity<>("success remove ingredient", HttpStatus.OK);
}

Expand Down
Loading

0 comments on commit 6498a3f

Please sign in to comment.