fix(server): fix duplicate Ingredient present in each recipe

An issue where multiple instances of a uniquely named Ingredient are
stored in the database. This is patched by checking server-side whether
an ingredient exists before persisting it. This now correctly implements
the Many-to-Many reference.
This commit is contained in:
Zhongheng Liu 2025-12-29 12:55:00 +01:00
commit d497ed108e
2 changed files with 21 additions and 15 deletions

View file

@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import server.database.IngredientRepository;
import server.database.RecipeIngredientRepository; import server.database.RecipeIngredientRepository;
import server.database.RecipeRepository; import server.database.RecipeRepository;
@ -34,16 +35,16 @@ public class RecipeController {
private final RecipeRepository recipeRepository; // JPA repository used in this controller private final RecipeRepository recipeRepository; // JPA repository used in this controller
private final SimpMessagingTemplate messagingTemplate; private final SimpMessagingTemplate messagingTemplate;
private final RecipeIngredientRepository recipeIngredientRepository; private final RecipeIngredientRepository recipeIngredientRepository;
private final IngredientController ingredientController; private final IngredientRepository ingredientRepository;
public RecipeController(RecipeRepository recipeRepository, public RecipeController(RecipeRepository recipeRepository,
SimpMessagingTemplate messagingTemplate, SimpMessagingTemplate messagingTemplate,
IngredientController ingredientController, IngredientRepository ingredientRepository,
RecipeIngredientRepository recipeIngredientRepository) { RecipeIngredientRepository recipeIngredientRepository) {
this.recipeRepository = recipeRepository; this.recipeRepository = recipeRepository;
this.messagingTemplate = messagingTemplate; this.messagingTemplate = messagingTemplate;
this.recipeIngredientRepository = recipeIngredientRepository; this.recipeIngredientRepository = recipeIngredientRepository;
this.ingredientController = ingredientController; this.ingredientRepository = ingredientRepository;
} }
/** /**
@ -80,7 +81,19 @@ public class RecipeController {
return ResponseEntity.ok(recipeRepository.findAll()); return ResponseEntity.ok(recipeRepository.findAll());
} }
private Recipe saveRecipeAndDependencies(Recipe recipe) {
recipe.getIngredients()
.forEach(recipeIngredient ->
recipeIngredient.setIngredient(
ingredientRepository
.findByName(recipeIngredient.getIngredient().name)
.orElseGet(() -> ingredientRepository.save(recipeIngredient.getIngredient())
))
);
recipeIngredientRepository.saveAll(recipe.getIngredients());
Recipe saved = recipeRepository.save(recipe);
return saved;
}
/** /**
* Mapping for <code>POST /recipe/{id}</code>. * Mapping for <code>POST /recipe/{id}</code>.
* Also creates the ingredient elements if they do not exist. * Also creates the ingredient elements if they do not exist.
@ -94,11 +107,7 @@ public class RecipeController {
if (!recipeRepository.existsById(id)) { if (!recipeRepository.existsById(id)) {
return ResponseEntity.badRequest().build(); return ResponseEntity.badRequest().build();
} }
recipe.getIngredients().stream() Recipe saved = saveRecipeAndDependencies(recipe);
.map(recipeIngredient -> recipeIngredient.ingredient)
.forEach(ingredientController::createIngredient);
recipeIngredientRepository.saveAll(recipe.getIngredients());
Recipe saved = recipeRepository.save(recipe);
messagingTemplate.convertAndSend(Topics.RECIPES, new UpdateRecipeMessage(saved)); messagingTemplate.convertAndSend(Topics.RECIPES, new UpdateRecipeMessage(saved));
return ResponseEntity.ok(saved); return ResponseEntity.ok(saved);
@ -128,12 +137,7 @@ public class RecipeController {
if (recipeRepository.exists(Example.of(example))) { if (recipeRepository.exists(Example.of(example))) {
return ResponseEntity.badRequest().build(); return ResponseEntity.badRequest().build();
} }
// FIXME reduce code duplication Recipe saved = saveRecipeAndDependencies(recipe);
recipe.getIngredients().stream()
.map(recipeIngredient -> recipeIngredient.ingredient)
.forEach(ingredientController::createIngredient);
recipeIngredientRepository.saveAll(recipe.getIngredients());
Recipe saved = recipeRepository.save(recipe);
messagingTemplate.convertAndSend(Topics.RECIPES, new CreateRecipeMessage(saved)); messagingTemplate.convertAndSend(Topics.RECIPES, new CreateRecipeMessage(saved));
return ResponseEntity.ok(saved); return ResponseEntity.ok(saved);

View file

@ -6,9 +6,11 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface IngredientRepository extends JpaRepository<Ingredient, Long> { public interface IngredientRepository extends JpaRepository<Ingredient, Long> {
List<Ingredient> findAllByOrderByNameAsc(); List<Ingredient> findAllByOrderByNameAsc();
Page<Ingredient> findAllByOrderByNameAsc(Pageable pageable); Page<Ingredient> findAllByOrderByNameAsc(Pageable pageable);
Optional<Ingredient> findByName(String name);
} }