recipe ingredient relations and API calls established

This commit is contained in:
durn
2026-03-03 01:14:02 -07:00
parent af514d408d
commit fa18b3e223
12 changed files with 510 additions and 16 deletions
@@ -1,5 +1,70 @@
package com.example.demo.controller; package com.example.demo.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.dto.RecipeDto;
import com.example.demo.entity.Recipe;
import com.example.demo.entity.User;
import com.example.demo.service.RecipeService;
@RestController
@RequestMapping("/api/recipes")
public class RecipeController { public class RecipeController {
private RecipeService recipeService;
public RecipeController(RecipeService recipeService) {
super();
this.recipeService = recipeService;
}
//build create recipe REST API
@PostMapping
public ResponseEntity<RecipeDto> saveUser(@RequestBody Recipe recipe){
RecipeDto recipeDto = recipeService.convertToDto(recipe);
return new ResponseEntity<RecipeDto>(recipeService.saveRecipe(recipeDto), HttpStatus.CREATED);
}
//build get all recipes REST API
@GetMapping
public List<RecipeDto> getAllRecipes(){
return recipeService.getAllRecipes();
}
//build get recipe by id REST API
// http://localhost:8080/api/recipes/(id number goes here)
@GetMapping("{id}")
public ResponseEntity<RecipeDto> getRecipeById(@PathVariable("id") Integer recipeId){
return new ResponseEntity<RecipeDto>(recipeService.getRecipeById(recipeId), HttpStatus.OK);
}
//build update recipe REST API
//http://localhost:8080/api/recipes/(id number goes here)
@PutMapping("{id}")
public ResponseEntity<RecipeDto> updateUser(@PathVariable("id") Integer recipeId, @RequestBody Recipe recipe){
RecipeDto recipeDto = recipeService.convertToDto(recipe);
return new ResponseEntity<RecipeDto>(recipeService.updateRecipe(recipeDto, recipeId), HttpStatus.OK);
}
//build delete recipe REST API
//http://localhost:8080/api/recipes/(id number goes here)
@DeleteMapping("{id}")
public ResponseEntity<String> deleteUser(@PathVariable("id") Integer recipeId){
recipeService.deleteRecipe(recipeId);
return new ResponseEntity<String>("Recipe deleted succesfully!", HttpStatus.OK);
}
} }
@@ -0,0 +1,88 @@
package com.example.demo.dto;
import java.util.List;
import com.example.demo.entity.Recipe;
public class RecipeDto {
private String title;
private String description;
private Integer prepTimeMinutes;
private Integer cookTimeMinutes;
private Integer servings;
private Integer userId;
private String status;
private List<RecipeIngredientDto> ingredients;
public RecipeDto() {
super();
}
public RecipeDto(String title, String description, Integer prepTimeMinutes, Integer cookTimeMinutes,
Integer servings, Integer userId, String status, List<RecipeIngredientDto> ingredients) {
super();
this.title = title;
this.description = description;
this.prepTimeMinutes = prepTimeMinutes;
this.cookTimeMinutes = cookTimeMinutes;
this.servings = servings;
this.userId = userId;
this.status = status;
this.ingredients = ingredients;
}
// getters and setters
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getPrepTimeMinutes() {
return prepTimeMinutes;
}
public void setPrepTimeMinutes(Integer prepTimeMinutes) {
this.prepTimeMinutes = prepTimeMinutes;
}
public Integer getCookTimeMinutes() {
return cookTimeMinutes;
}
public void setCookTimeMinutes(Integer cookTimeMinutes) {
this.cookTimeMinutes = cookTimeMinutes;
}
public Integer getServings() {
return servings;
}
public void setServings(Integer servings) {
this.servings = servings;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<RecipeIngredientDto> getIngredients() {
return ingredients;
}
public void setIngredients(List<RecipeIngredientDto> ingredients) {
this.ingredients = ingredients;
}
}
@@ -0,0 +1,53 @@
package com.example.demo.dto;
import java.math.BigDecimal;
public class RecipeIngredientDto {
private String ingredientName;
private BigDecimal quantity;
private String unit;
private String notes;
public RecipeIngredientDto() {
super();
}
public RecipeIngredientDto(String ingredientName, BigDecimal quantity, String unit, String notes) {
super();
this.ingredientName = ingredientName;
this.quantity = quantity;
this.unit = unit;
this.notes = notes;
}
// getters and setters
public String getIngredientName() {
return ingredientName;
}
public void setIngredientName(String ingredientName) {
this.ingredientName = ingredientName;
}
public BigDecimal getQuantity() {
return quantity;
}
public void setQuantity(BigDecimal quantity) {
this.quantity = quantity;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
}
@@ -1,11 +1,18 @@
package com.example.demo.entity; package com.example.demo.entity;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint; import jakarta.persistence.UniqueConstraint;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
@Entity @Entity
@@ -18,9 +25,13 @@ public class Ingredient {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; private Integer id;
@Column(nullable = false, unique = true) @Column(nullable = false, unique = true)
private String name; private String name;
@OneToMany(mappedBy = "ingredient")
private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
// Default constructor required by JPA // Default constructor required by JPA
public Ingredient() {} public Ingredient() {}
@@ -35,4 +46,12 @@ public class Ingredient {
public String getName() { return name; } public String getName() { return name; }
public void setName(String name) { this.name = name; } public void setName(String name) { this.name = name; }
public Set<RecipeIngredient> getRecipeIngredients() {
return recipeIngredients;
}
public void setRecipeIngredients(Set<RecipeIngredient> recipeIngredients) {
this.recipeIngredients = recipeIngredients;
}
} }
@@ -1,12 +1,10 @@
package com.example.demo.entity; package com.example.demo.entity;
import jakarta.persistence.Entity; import jakarta.persistence.*;
import jakarta.persistence.Table; import java.math.BigDecimal;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Column;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
@Entity @Entity
@Table(name = "recipes") @Table(name = "recipes")
@@ -33,7 +31,12 @@ public class Recipe {
private LocalDateTime createdAt; private LocalDateTime createdAt;
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
// Default constructor required by JPA // Recipe ingredients relationship
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
// Default constructor
public Recipe() {} public Recipe() {}
// Convenience constructor // Convenience constructor
@@ -50,7 +53,32 @@ public class Recipe {
this.updatedAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now();
} }
// Getters and setters for all fields // Helper method to add an ingredient
public void addIngredient(Ingredient ingredient, BigDecimal quantity, String unit, String notes) {
RecipeIngredient recipeIngredient = new RecipeIngredient(this, ingredient, quantity, unit, notes);
recipeIngredients.add(recipeIngredient);
ingredient.getRecipeIngredients().add(recipeIngredient);
}
public void removeIngredient(Ingredient ingredient) {
RecipeIngredient remove = null;
for (RecipeIngredient recipeIngredient : recipeIngredients) {
if (recipeIngredient.getIngredient().equals(ingredient)) {
remove = recipeIngredient;
break;
}
}
if (remove != null) {
recipeIngredients.remove(remove);
ingredient.getRecipeIngredients().remove(remove);
remove.setRecipe(null);
remove.setIngredient(null);
}
}
// Getters and setters
public Integer getId() { return id; } public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; } public void setId(Integer id) { this.id = id; }
@@ -81,4 +109,7 @@ public class Recipe {
public LocalDateTime getUpdatedAt() { return updatedAt; } public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
public Set<RecipeIngredient> getRecipeIngredients() { return recipeIngredients; }
public void setRecipeIngredients(Set<RecipeIngredient> recipeIngredients) { this.recipeIngredients = recipeIngredients; }
} }
@@ -2,22 +2,28 @@ package com.example.demo.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import lombok.EqualsAndHashCode;
@Entity @Entity
@Table(name = "recipe_ingredient_junction", @Table(name = "recipe_ingredient_junction",
uniqueConstraints = {@UniqueConstraint(columnNames = {"recipe_id", "ingredient_id"})}) uniqueConstraints = {@UniqueConstraint(columnNames = {"recipe_id", "ingredient_id"})})
@EqualsAndHashCode(
onlyExplicitlyIncluded = true
)
public class RecipeIngredient { public class RecipeIngredient {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; // surrogate PK private Integer id;
@ManyToOne @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "recipe_id", nullable = false) @JoinColumn(name = "recipe_id", nullable = false)
@EqualsAndHashCode.Include
private Recipe recipe; private Recipe recipe;
@ManyToOne @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ingredient_id", nullable = false) @JoinColumn(name = "ingredient_id", nullable = false)
@EqualsAndHashCode.Include
private Ingredient ingredient; private Ingredient ingredient;
private BigDecimal quantity; private BigDecimal quantity;
@@ -1,9 +1,11 @@
package com.example.demo.repository; package com.example.demo.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import com.example.demo.entity.Ingredient; import com.example.demo.entity.Ingredient;
public interface IngredientRepo extends JpaRepository<Ingredient, Integer> { public interface IngredientRepo extends JpaRepository<Ingredient, Integer> {
Optional<Ingredient> findByName(String name);
} }
@@ -1,9 +1,11 @@
package com.example.demo.repository; package com.example.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import com.example.demo.entity.Recipe;
import com.example.demo.entity.RecipeIngredient; import com.example.demo.entity.RecipeIngredient;
public interface RecipeIngredientRepo extends JpaRepository<RecipeIngredient, Integer> { public interface RecipeIngredientRepo extends JpaRepository<RecipeIngredient, Integer> {
// Custom query: find all ingredients for a recipe // Custom query: find all ingredients for a recipe
void deleteByRecipe(Recipe recipe);
} }
@@ -5,4 +5,5 @@ import com.example.demo.entity.Recipe;
public interface RecipeRepo extends JpaRepository<Recipe, Integer> { public interface RecipeRepo extends JpaRepository<Recipe, Integer> {
} }
@@ -0,0 +1,208 @@
package com.example.demo.service.Impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.example.demo.dto.RecipeDto;
import com.example.demo.dto.RecipeIngredientDto;
import com.example.demo.entity.Ingredient;
import com.example.demo.entity.Recipe;
import com.example.demo.entity.RecipeIngredient;
import com.example.demo.entity.User;
import com.example.demo.exception.NotFoundException;
import com.example.demo.repository.IngredientRepo;
import com.example.demo.repository.RecipeIngredientRepo;
import com.example.demo.repository.RecipeRepo;
import com.example.demo.service.RecipeService;
import jakarta.transaction.Transactional;
@Service
public class RecipeServiceImpl implements RecipeService{
private RecipeRepo recipeRepository;
private IngredientRepo ingredientRepository;
private RecipeIngredientRepo recipeIngredientRepository;
public RecipeServiceImpl(RecipeRepo recipeRepository, IngredientRepo ingredientRepository, RecipeIngredientRepo recipeIngredientRepository) {
super();
this.recipeRepository = recipeRepository;
this.ingredientRepository = ingredientRepository;
this.recipeIngredientRepository = recipeIngredientRepository;
}
public RecipeDto convertToDto(Recipe recipe) {
List<RecipeIngredientDto> ingredientDtos = recipe.getRecipeIngredients().stream()
.map(ri -> new RecipeIngredientDto(
ri.getIngredient().getName(),
ri.getQuantity(),
ri.getUnit(),
ri.getNotes()
))
.toList();
return new RecipeDto(
recipe.getTitle(),
recipe.getDescription(),
recipe.getPrepTimeMinutes(),
recipe.getCookTimeMinutes(),
recipe.getServings(),
recipe.getUserId(),
recipe.getStatus(),
ingredientDtos
);
}
@Override
public RecipeDto saveRecipe(RecipeDto dto) {
Recipe recipe = new Recipe(
dto.getTitle(),
dto.getDescription(),
dto.getPrepTimeMinutes(),
dto.getCookTimeMinutes(),
dto.getServings(),
dto.getUserId(),
dto.getStatus()
);
for (RecipeIngredientDto riDto : dto.getIngredients()) {
Ingredient ingredient = ingredientRepository.findByName(riDto.getIngredientName())
.orElseGet(() -> new Ingredient(riDto.getIngredientName()));
if (ingredient.getId() == null) {
ingredientRepository.save(ingredient);
}
RecipeIngredient ri = new RecipeIngredient(
recipe,
ingredient,
riDto.getQuantity(),
riDto.getUnit(),
riDto.getNotes()
);
recipe.getRecipeIngredients().add(ri);
}
recipeRepository.save(recipe);
return dto;
}
@Override
@Transactional
public List<RecipeDto> getAllRecipes() {
List<RecipeDto> list = new ArrayList<>();
for (Recipe recipe : recipeRepository.findAll()) {
RecipeDto recipeDto = convertToDto(recipe);
list.add(recipeDto);
}
return list;
}
@Override
@Transactional
public RecipeDto getRecipeById(Integer Id) {
return convertToDto(recipeRepository.findById(Id).orElseThrow(() ->
new NotFoundException("Recipe", "id", Id)));
}
@Override
@Transactional
public RecipeDto updateRecipe(RecipeDto recipeDto, Integer id) {
Recipe existingRecipe = recipeRepository.findById(id)
.orElseThrow(() -> new NotFoundException("Recipe", "id", id));
existingRecipe.setTitle(recipeDto.getTitle());
existingRecipe.setDescription(recipeDto.getDescription());
existingRecipe.setPrepTimeMinutes(recipeDto.getPrepTimeMinutes());
existingRecipe.setCookTimeMinutes(recipeDto.getCookTimeMinutes());
existingRecipe.setServings(recipeDto.getServings());
existingRecipe.setStatus(recipeDto.getStatus());
List<RecipeIngredientDto> updatedIngredients = recipeDto.getIngredients();
List<RecipeIngredient> ingredientsToRemove = new ArrayList<>();
for (RecipeIngredient ri : existingRecipe.getRecipeIngredients()) {
boolean existsInUpdatedList = false;
for (RecipeIngredientDto dto : updatedIngredients) {
String updatedName = dto.getIngredientName();
String existingName = ri.getIngredient().getName();
if (updatedName.equals(existingName)) {
existsInUpdatedList = true;
break;
}
}
if (!existsInUpdatedList) {
ingredientsToRemove.add(ri);
}
}
existingRecipe.getRecipeIngredients().removeAll(ingredientsToRemove);
for (RecipeIngredientDto riDto : updatedIngredients) {
// go through the old list of ingredients until we find a match with updated list
RecipeIngredient existingRI = existingRecipe.getRecipeIngredients().stream()
.filter(ri -> ri.getIngredient().getName().equals(riDto.getIngredientName()))
.findFirst()
.orElse(null);
// if old ingredient just update parameters
if (existingRI != null) {
existingRI.setQuantity(riDto.getQuantity());
existingRI.setUnit(riDto.getUnit());
existingRI.setNotes(riDto.getNotes());
}
// if new ingredient, have to make a whole new thing
else {
Ingredient ingredient = ingredientRepository.findByName(riDto.getIngredientName())
.orElseGet(() -> new Ingredient(riDto.getIngredientName()));
if (ingredient.getId() == null) {
ingredientRepository.save(ingredient);
}
RecipeIngredient newRI = new RecipeIngredient(
existingRecipe,
ingredient,
riDto.getQuantity(),
riDto.getUnit(),
riDto.getNotes()
);
existingRecipe.getRecipeIngredients().add(newRI);
}
}
recipeRepository.save(existingRecipe);
return convertToDto(existingRecipe);
}
@Override
public void deleteRecipe(Integer Id) {
recipeRepository.findById(Id)
.orElseThrow(() -> new NotFoundException("Recipe", "id", Id));
recipeRepository.deleteById(Id);
}
}
@@ -0,0 +1,5 @@
package com.example.demo.service;
public class MapperService {
}
@@ -1,5 +1,19 @@
package com.example.demo.service; package com.example.demo.service;
import java.util.List;
import org.jspecify.annotations.Nullable;
import com.example.demo.dto.RecipeDto;
import com.example.demo.entity.Recipe;
import com.example.demo.entity.User;
public interface RecipeService { public interface RecipeService {
RecipeDto convertToDto(Recipe recipe) ;
RecipeDto saveRecipe(RecipeDto recipe);
List<RecipeDto> getAllRecipes();
RecipeDto getRecipeById(Integer recipeId);
RecipeDto updateRecipe(RecipeDto recipedto, Integer Id);
void deleteRecipe(Integer Id);
} }