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.RestController;
import server.database.IngredientRepository;
import server.database.RecipeIngredientRepository;
import server.database.RecipeRepository;
@ -34,16 +35,16 @@ public class RecipeController {
private final RecipeRepository recipeRepository; // JPA repository used in this controller
private final SimpMessagingTemplate messagingTemplate;
private final RecipeIngredientRepository recipeIngredientRepository;
private final IngredientController ingredientController;
private final IngredientRepository ingredientRepository;
public RecipeController(RecipeRepository recipeRepository,
SimpMessagingTemplate messagingTemplate,
IngredientController ingredientController,
IngredientRepository ingredientRepository,
RecipeIngredientRepository recipeIngredientRepository) {
this.recipeRepository = recipeRepository;
this.messagingTemplate = messagingTemplate;
this.recipeIngredientRepository = recipeIngredientRepository;
this.ingredientController = ingredientController;
this.ingredientRepository = ingredientRepository;
}
/**
@ -80,7 +81,19 @@ public class RecipeController {
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>.
* Also creates the ingredient elements if they do not exist.
@ -94,11 +107,7 @@ public class RecipeController {
if (!recipeRepository.existsById(id)) {
return ResponseEntity.badRequest().build();
}
recipe.getIngredients().stream()
.map(recipeIngredient -> recipeIngredient.ingredient)
.forEach(ingredientController::createIngredient);
recipeIngredientRepository.saveAll(recipe.getIngredients());
Recipe saved = recipeRepository.save(recipe);
Recipe saved = saveRecipeAndDependencies(recipe);
messagingTemplate.convertAndSend(Topics.RECIPES, new UpdateRecipeMessage(saved));
return ResponseEntity.ok(saved);
@ -128,12 +137,7 @@ public class RecipeController {
if (recipeRepository.exists(Example.of(example))) {
return ResponseEntity.badRequest().build();
}
// FIXME reduce code duplication
recipe.getIngredients().stream()
.map(recipeIngredient -> recipeIngredient.ingredient)
.forEach(ingredientController::createIngredient);
recipeIngredientRepository.saveAll(recipe.getIngredients());
Recipe saved = recipeRepository.save(recipe);
Recipe saved = saveRecipeAndDependencies(recipe);
messagingTemplate.convertAndSend(Topics.RECIPES, new CreateRecipeMessage(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 java.util.List;
import java.util.Optional;
public interface IngredientRepository extends JpaRepository<Ingredient, Long> {
List<Ingredient> findAllByOrderByNameAsc();
Page<Ingredient> findAllByOrderByNameAsc(Pageable pageable);
Optional<Ingredient> findByName(String name);
}