diff --git a/demo/src/main/java/com/example/demo/controller/RecipeController.java b/demo/src/main/java/com/example/demo/controller/RecipeController.java index 584d845..9650a16 100644 --- a/demo/src/main/java/com/example/demo/controller/RecipeController.java +++ b/demo/src/main/java/com/example/demo/controller/RecipeController.java @@ -1,5 +1,70 @@ package com.example.demo.controller; -public class RecipeController { +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 { + + private RecipeService recipeService; + + public RecipeController(RecipeService recipeService) { + super(); + this.recipeService = recipeService; + } + + //build create recipe REST API + @PostMapping + public ResponseEntity saveUser(@RequestBody Recipe recipe){ + RecipeDto recipeDto = recipeService.convertToDto(recipe); + return new ResponseEntity(recipeService.saveRecipe(recipeDto), HttpStatus.CREATED); + } + + //build get all recipes REST API + @GetMapping + public List getAllRecipes(){ + return recipeService.getAllRecipes(); + } + + //build get recipe by id REST API + // http://localhost:8080/api/recipes/(id number goes here) + @GetMapping("{id}") + public ResponseEntity getRecipeById(@PathVariable("id") Integer recipeId){ + return new ResponseEntity(recipeService.getRecipeById(recipeId), HttpStatus.OK); + } + + //build update recipe REST API + //http://localhost:8080/api/recipes/(id number goes here) + @PutMapping("{id}") + public ResponseEntity updateUser(@PathVariable("id") Integer recipeId, @RequestBody Recipe recipe){ + RecipeDto recipeDto = recipeService.convertToDto(recipe); + return new ResponseEntity(recipeService.updateRecipe(recipeDto, recipeId), HttpStatus.OK); + } + + //build delete recipe REST API + //http://localhost:8080/api/recipes/(id number goes here) + @DeleteMapping("{id}") + public ResponseEntity deleteUser(@PathVariable("id") Integer recipeId){ + recipeService.deleteRecipe(recipeId); + return new ResponseEntity("Recipe deleted succesfully!", HttpStatus.OK); + } + + + } diff --git a/demo/src/main/java/com/example/demo/dto/RecipeDto.java b/demo/src/main/java/com/example/demo/dto/RecipeDto.java new file mode 100644 index 0000000..e7645b8 --- /dev/null +++ b/demo/src/main/java/com/example/demo/dto/RecipeDto.java @@ -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 ingredients; + + + + + public RecipeDto() { + super(); + } + + public RecipeDto(String title, String description, Integer prepTimeMinutes, Integer cookTimeMinutes, + Integer servings, Integer userId, String status, List 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 getIngredients() { + return ingredients; + } + public void setIngredients(List ingredients) { + this.ingredients = ingredients; + } + +} diff --git a/demo/src/main/java/com/example/demo/dto/RecipeIngredientDto.java b/demo/src/main/java/com/example/demo/dto/RecipeIngredientDto.java new file mode 100644 index 0000000..7f63677 --- /dev/null +++ b/demo/src/main/java/com/example/demo/dto/RecipeIngredientDto.java @@ -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; + } + +} diff --git a/demo/src/main/java/com/example/demo/entity/Ingredient.java b/demo/src/main/java/com/example/demo/entity/Ingredient.java index 6419ee4..4cce815 100644 --- a/demo/src/main/java/com/example/demo/entity/Ingredient.java +++ b/demo/src/main/java/com/example/demo/entity/Ingredient.java @@ -1,11 +1,18 @@ package com.example.demo.entity; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; + +import java.util.HashSet; +import java.util.Set; + +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @Entity @@ -17,11 +24,15 @@ public class Ingredient { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; + @Column(nullable = false, unique = true) private String name; + + @OneToMany(mappedBy = "ingredient") + private Set recipeIngredients = new HashSet<>(); - // Default constructor required by JPA + // Default constructor required by JPA public Ingredient() {} // Convenience constructor @@ -35,4 +46,12 @@ public class Ingredient { public String getName() { return name; } public void setName(String name) { this.name = name; } + + public Set getRecipeIngredients() { + return recipeIngredients; + } + + public void setRecipeIngredients(Set recipeIngredients) { + this.recipeIngredients = recipeIngredients; + } } \ No newline at end of file diff --git a/demo/src/main/java/com/example/demo/entity/Recipe.java b/demo/src/main/java/com/example/demo/entity/Recipe.java index ab09e62..8f0e7a0 100644 --- a/demo/src/main/java/com/example/demo/entity/Recipe.java +++ b/demo/src/main/java/com/example/demo/entity/Recipe.java @@ -1,12 +1,10 @@ package com.example.demo.entity; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.Id; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Column; +import jakarta.persistence.*; +import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; @Entity @Table(name = "recipes") @@ -33,7 +31,12 @@ public class Recipe { private LocalDateTime createdAt; private LocalDateTime updatedAt; - // Default constructor required by JPA + // Recipe ingredients relationship + @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + private Set recipeIngredients = new HashSet<>(); + + + // Default constructor public Recipe() {} // Convenience constructor @@ -50,7 +53,32 @@ public class Recipe { 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 void setId(Integer id) { this.id = id; } @@ -81,4 +109,7 @@ public class Recipe { public LocalDateTime getUpdatedAt() { return updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } + public Set getRecipeIngredients() { return recipeIngredients; } + public void setRecipeIngredients(Set recipeIngredients) { this.recipeIngredients = recipeIngredients; } + } \ No newline at end of file diff --git a/demo/src/main/java/com/example/demo/entity/RecipeIngredient.java b/demo/src/main/java/com/example/demo/entity/RecipeIngredient.java index 4783e65..3ff17c7 100644 --- a/demo/src/main/java/com/example/demo/entity/RecipeIngredient.java +++ b/demo/src/main/java/com/example/demo/entity/RecipeIngredient.java @@ -2,22 +2,28 @@ package com.example.demo.entity; import jakarta.persistence.*; import java.math.BigDecimal; +import lombok.EqualsAndHashCode; @Entity @Table(name = "recipe_ingredient_junction", uniqueConstraints = {@UniqueConstraint(columnNames = {"recipe_id", "ingredient_id"})}) +@EqualsAndHashCode( + onlyExplicitlyIncluded = true + ) public class RecipeIngredient { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; // surrogate PK + private Integer id; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "recipe_id", nullable = false) + @EqualsAndHashCode.Include private Recipe recipe; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "ingredient_id", nullable = false) + @EqualsAndHashCode.Include private Ingredient ingredient; private BigDecimal quantity; diff --git a/demo/src/main/java/com/example/demo/repository/IngredientRepo.java b/demo/src/main/java/com/example/demo/repository/IngredientRepo.java index de13634..7f0e379 100644 --- a/demo/src/main/java/com/example/demo/repository/IngredientRepo.java +++ b/demo/src/main/java/com/example/demo/repository/IngredientRepo.java @@ -1,9 +1,11 @@ package com.example.demo.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import com.example.demo.entity.Ingredient; public interface IngredientRepo extends JpaRepository { - + Optional findByName(String name); } \ No newline at end of file diff --git a/demo/src/main/java/com/example/demo/repository/RecipeIngredientRepo.java b/demo/src/main/java/com/example/demo/repository/RecipeIngredientRepo.java index d7d9c26..4eefe46 100644 --- a/demo/src/main/java/com/example/demo/repository/RecipeIngredientRepo.java +++ b/demo/src/main/java/com/example/demo/repository/RecipeIngredientRepo.java @@ -1,9 +1,11 @@ package com.example.demo.repository; import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.demo.entity.Recipe; import com.example.demo.entity.RecipeIngredient; public interface RecipeIngredientRepo extends JpaRepository { // Custom query: find all ingredients for a recipe - + void deleteByRecipe(Recipe recipe); } \ No newline at end of file diff --git a/demo/src/main/java/com/example/demo/repository/RecipeRepo.java b/demo/src/main/java/com/example/demo/repository/RecipeRepo.java index ca84119..973fed4 100644 --- a/demo/src/main/java/com/example/demo/repository/RecipeRepo.java +++ b/demo/src/main/java/com/example/demo/repository/RecipeRepo.java @@ -5,4 +5,5 @@ import com.example.demo.entity.Recipe; public interface RecipeRepo extends JpaRepository { + } \ No newline at end of file diff --git a/demo/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java b/demo/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java new file mode 100644 index 0000000..bcd5cce --- /dev/null +++ b/demo/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java @@ -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 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 getAllRecipes() { + + List 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 updatedIngredients = recipeDto.getIngredients(); + List 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); + + } +} diff --git a/demo/src/main/java/com/example/demo/service/MapperService.java b/demo/src/main/java/com/example/demo/service/MapperService.java new file mode 100644 index 0000000..d4405a5 --- /dev/null +++ b/demo/src/main/java/com/example/demo/service/MapperService.java @@ -0,0 +1,5 @@ +package com.example.demo.service; + +public class MapperService { + +} diff --git a/demo/src/main/java/com/example/demo/service/RecipeService.java b/demo/src/main/java/com/example/demo/service/RecipeService.java index 9740c8e..df91ae6 100644 --- a/demo/src/main/java/com/example/demo/service/RecipeService.java +++ b/demo/src/main/java/com/example/demo/service/RecipeService.java @@ -1,5 +1,19 @@ package com.example.demo.service; -public interface RecipeService { +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 { + RecipeDto convertToDto(Recipe recipe) ; + RecipeDto saveRecipe(RecipeDto recipe); + List getAllRecipes(); + RecipeDto getRecipeById(Integer recipeId); + RecipeDto updateRecipe(RecipeDto recipedto, Integer Id); + void deleteRecipe(Integer Id); + }