Update 2 files

- /src/main/java/com/example/demo/controller/RecipeUploadController.java
- /src/main/java/com/example/demo/controller/RecipeUploadForm.java
This commit is contained in:
Madeleine Stamp
2026-04-23 12:41:10 -06:00
parent 339cbbdf6e
commit c4a40c1068
2 changed files with 96 additions and 233 deletions
@@ -2,6 +2,7 @@ package com.example.demo.controller;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -40,21 +41,13 @@ public class RecipeUploadController {
@PostMapping(value = "/api/recipes/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping(value = "/api/recipes/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<?> createRecipeWithUpload(@ModelAttribute RecipeUploadForm form, Principal principal) { public ResponseEntity<?> createRecipeWithUpload(@ModelAttribute RecipeUploadForm form, Principal principal) {
try { try {
System.out.println("UPLOAD ENDPOINT HIT");
System.out.println("Title: " + form.getTitle());
System.out.println("Image present: " + (form.getImage() != null && !form.getImage().isEmpty()));
RecipeDto dto = buildRecipeDto(form, true); RecipeDto dto = buildRecipeDto(form, true);
System.out.println("Image DTO count: " + (dto.getImages() == null ? 0 : dto.getImages().size()));
String currentUsername = principal.getName(); String currentUsername = principal.getName();
RecipeDto saved = recipeService.saveRecipe(dto, currentUsername); RecipeDto saved = recipeService.saveRecipe(dto, currentUsername);
System.out.println("Saved recipe id: " + saved.getId());
return new ResponseEntity<>(saved, HttpStatus.CREATED); return new ResponseEntity<>(saved, HttpStatus.CREATED);
} catch (IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return ResponseEntity.status(HttpStatus.BAD_REQUEST) return ResponseEntity.status(HttpStatus.BAD_REQUEST)
@@ -68,21 +61,13 @@ public class RecipeUploadController {
@ModelAttribute RecipeUploadForm form, @ModelAttribute RecipeUploadForm form,
Principal principal) { Principal principal) {
try { try {
System.out.println("UPDATE UPLOAD ENDPOINT HIT");
System.out.println("Recipe id: " + id);
System.out.println("Title: " + form.getTitle());
System.out.println("Image present: " + (form.getImage() != null && !form.getImage().isEmpty()));
System.out.println("Remove image: " + form.getRemoveImage());
RecipeDto dto = buildRecipeDto(form, false); RecipeDto dto = buildRecipeDto(form, false);
String currentUsername = principal.getName(); String currentUsername = principal.getName();
RecipeDto updated = recipeService.updateRecipe(dto, id, currentUsername); RecipeDto updated = recipeService.updateRecipe(dto, id, currentUsername);
System.out.println("Updated recipe id: " + updated.getId());
return new ResponseEntity<>(updated, HttpStatus.OK); return new ResponseEntity<>(updated, HttpStatus.OK);
} catch (IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return ResponseEntity.status(HttpStatus.BAD_REQUEST) return ResponseEntity.status(HttpStatus.BAD_REQUEST)
@@ -109,9 +94,9 @@ public class RecipeUploadController {
} }
String quantityString = getListValue(form.getIngredientQuantity(), i); String quantityString = getListValue(form.getIngredientQuantity(), i);
String quantity = null; BigDecimal quantity = null;
if (quantityString != null && !quantityString.isBlank()) { if (quantityString != null && !quantityString.isBlank()) {
quantity = new String(quantityString.trim()); quantity = parseQuantity(quantityString.trim());
} }
String unit = getListValue(form.getIngredientUnit(), i); String unit = getListValue(form.getIngredientUnit(), i);
@@ -119,7 +104,7 @@ public class RecipeUploadController {
ingredientDtos.add(new RecipeIngredientDto( ingredientDtos.add(new RecipeIngredientDto(
ingredientName.trim(), ingredientName.trim(),
quantity != null ? quantity.trim() : "", quantity,
unit != null ? unit.trim() : "", unit != null ? unit.trim() : "",
notes != null ? notes.trim() : "")); notes != null ? notes.trim() : ""));
} }
@@ -156,14 +141,11 @@ public class RecipeUploadController {
String imageUrl = saveUploadedFile(imageFile); String imageUrl = saveUploadedFile(imageFile);
imageDtos.add(new ImageDto(imageUrl)); imageDtos.add(new ImageDto(imageUrl));
dto.setImages(imageDtos); dto.setImages(imageDtos);
System.out.println("Saved file path: " + imageUrl);
} else if (removeImage) { } else if (removeImage) {
// Empty list means remove all images on update
dto.setImages(new ArrayList<>()); dto.setImages(new ArrayList<>());
} else if (isCreate) { } else if (isCreate) {
dto.setImages(new ArrayList<>()); dto.setImages(new ArrayList<>());
} else { } else {
// Null on update means keep the existing image as-is
dto.setImages(null); dto.setImages(null);
} }
@@ -188,6 +170,45 @@ public class RecipeUploadController {
return value == null ? null : value.trim(); return value == null ? null : value.trim();
} }
private BigDecimal parseQuantity(String value) {
String cleaned = value.trim();
if (cleaned.matches("^\\d+(\\.\\d+)?$")) {
return new BigDecimal(cleaned);
}
if (cleaned.matches("^\\d+/\\d+$")) {
String[] parts = cleaned.split("/");
BigDecimal numerator = new BigDecimal(parts[0]);
BigDecimal denominator = new BigDecimal(parts[1]);
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
throw new IllegalArgumentException("Ingredient quantity cannot have a zero denominator.");
}
return numerator.divide(denominator, 8, RoundingMode.HALF_UP).stripTrailingZeros();
}
if (cleaned.matches("^\\d+\\s+\\d+/\\d+$")) {
String[] mixed = cleaned.split("\\s+");
BigDecimal whole = new BigDecimal(mixed[0]);
String[] fraction = mixed[1].split("/");
BigDecimal numerator = new BigDecimal(fraction[0]);
BigDecimal denominator = new BigDecimal(fraction[1]);
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
throw new IllegalArgumentException("Ingredient quantity cannot have a zero denominator.");
}
BigDecimal fractionalPart = numerator.divide(denominator, 8, RoundingMode.HALF_UP);
return whole.add(fractionalPart).stripTrailingZeros();
}
throw new IllegalArgumentException(
"Invalid ingredient quantity: " + value + ". Use values like 1, 1.5, 1/4, or 2 1/2.");
}
private String saveUploadedFile(MultipartFile file) throws IOException { private String saveUploadedFile(MultipartFile file) throws IOException {
String originalFilename = StringUtils.cleanPath(file.getOriginalFilename()); String originalFilename = StringUtils.cleanPath(file.getOriginalFilename());
String extension = ""; String extension = "";
@@ -1,233 +1,75 @@
package com.example.demo.controller; package com.example.demo.controller;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.example.demo.dto.ImageDto; public class RecipeUploadForm {
import com.example.demo.dto.RecipeDto;
import com.example.demo.dto.RecipeIngredientDto;
import com.example.demo.dto.StepDto;
import com.example.demo.dto.TagDto;
import com.example.demo.service.RecipeService;
@RestController private String title;
public class RecipeUploadController { private String description;
private String prepTimeMinutes;
private String cookTimeMinutes;
private String servings;
private String cost;
private final RecipeService recipeService; private List<String> ingredientName;
private List<String> ingredientQuantity;
private List<String> ingredientUnit;
private List<String> ingredientNotes;
public RecipeUploadController(RecipeService recipeService) { private List<String> stepInstruction;
this.recipeService = recipeService; private List<String> tags;
}
@PostMapping(value = "/api/recipes/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) private MultipartFile image;
public ResponseEntity<?> createRecipeWithUpload(@ModelAttribute RecipeUploadForm form, Principal principal) { private Boolean removeImage;
try {
RecipeDto dto = buildRecipeDto(form, true);
String currentUsername = principal.getName();
RecipeDto saved = recipeService.saveRecipe(dto, currentUsername);
return new ResponseEntity<>(saved, HttpStatus.CREATED);
} catch (IllegalArgumentException e) { public RecipeUploadForm() {}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Recipe creation failed: " + e.getMessage());
}
}
@PostMapping(value = "/api/recipes/{id}/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) // ===== Basic fields =====
public ResponseEntity<?> updateRecipeWithUpload( public String getTitle() { return title; }
@PathVariable Integer id, public void setTitle(String title) { this.title = title; }
@ModelAttribute RecipeUploadForm form,
Principal principal) {
try {
RecipeDto dto = buildRecipeDto(form, false);
String currentUsername = principal.getName();
RecipeDto updated = recipeService.updateRecipe(dto, id, currentUsername);
return new ResponseEntity<>(updated, HttpStatus.OK);
} catch (IllegalArgumentException e) { public String getDescription() { return description; }
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); public void setDescription(String description) { this.description = description; }
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Recipe update failed: " + e.getMessage());
}
}
private RecipeDto buildRecipeDto(RecipeUploadForm form, boolean isCreate) throws IOException { public String getPrepTimeMinutes() { return prepTimeMinutes; }
RecipeDto dto = new RecipeDto(); public void setPrepTimeMinutes(String prepTimeMinutes) { this.prepTimeMinutes = prepTimeMinutes; }
dto.setTitle(safeTrim(form.getTitle()));
dto.setDescription(safeTrim(form.getDescription()));
dto.setPrepTimeMinutes(parseInteger(form.getPrepTimeMinutes()));
dto.setCookTimeMinutes(parseInteger(form.getCookTimeMinutes()));
dto.setServings(parseInteger(form.getServings()));
dto.setStatus("DRAFT");
dto.setCost(parseInteger(form.getCost()));
List<RecipeIngredientDto> ingredientDtos = new ArrayList<>(); public String getCookTimeMinutes() { return cookTimeMinutes; }
if (form.getIngredientName() != null) { public void setCookTimeMinutes(String cookTimeMinutes) { this.cookTimeMinutes = cookTimeMinutes; }
for (int i = 0; i < form.getIngredientName().size(); i++) {
String ingredientName = getListValue(form.getIngredientName(), i);
if (ingredientName == null || ingredientName.isBlank()) {
continue;
}
String quantityString = getListValue(form.getIngredientQuantity(), i); public String getServings() { return servings; }
BigDecimal quantity = null; public void setServings(String servings) { this.servings = servings; }
if (quantityString != null && !quantityString.isBlank()) {
quantity = parseQuantity(quantityString.trim());
}
String unit = getListValue(form.getIngredientUnit(), i); public String getCost() { return cost; }
String notes = getListValue(form.getIngredientNotes(), i); public void setCost(String cost) { this.cost = cost; }
ingredientDtos.add(new RecipeIngredientDto( // ===== Ingredients =====
ingredientName.trim(), public List<String> getIngredientName() { return ingredientName; }
quantity, public void setIngredientName(List<String> ingredientName) { this.ingredientName = ingredientName; }
unit != null ? unit.trim() : "",
notes != null ? notes.trim() : ""));
}
}
dto.setIngredients(ingredientDtos);
List<StepDto> stepDtos = new ArrayList<>(); public List<String> getIngredientQuantity() { return ingredientQuantity; }
if (form.getStepInstruction() != null) { public void setIngredientQuantity(List<String> ingredientQuantity) { this.ingredientQuantity = ingredientQuantity; }
for (int i = 0; i < form.getStepInstruction().size(); i++) {
String instruction = form.getStepInstruction().get(i);
if (instruction == null || instruction.isBlank()) {
continue;
}
stepDtos.add(new StepDto(i + 1, instruction.trim()));
}
}
dto.setSteps(stepDtos);
List<TagDto> tagDtos = new ArrayList<>(); public List<String> getIngredientUnit() { return ingredientUnit; }
if (form.getTags() != null) { public void setIngredientUnit(List<String> ingredientUnit) { this.ingredientUnit = ingredientUnit; }
for (String tag : form.getTags()) {
if (tag != null && !tag.isBlank()) {
tagDtos.add(new TagDto(tag.trim()));
}
}
}
dto.setTags(tagDtos);
MultipartFile imageFile = form.getImage(); public List<String> getIngredientNotes() { return ingredientNotes; }
boolean removeImage = Boolean.TRUE.equals(form.getRemoveImage()); public void setIngredientNotes(List<String> ingredientNotes) { this.ingredientNotes = ingredientNotes; }
if (imageFile != null && !imageFile.isEmpty()) { // ===== Steps =====
List<ImageDto> imageDtos = new ArrayList<>(); public List<String> getStepInstruction() { return stepInstruction; }
String imageUrl = saveUploadedFile(imageFile); public void setStepInstruction(List<String> stepInstruction) { this.stepInstruction = stepInstruction; }
imageDtos.add(new ImageDto(imageUrl));
dto.setImages(imageDtos);
} else if (removeImage) {
dto.setImages(new ArrayList<>());
} else if (isCreate) {
dto.setImages(new ArrayList<>());
} else {
dto.setImages(null);
}
return dto; // ===== Tags =====
} public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
private Integer parseInteger(String value) { // ===== Image =====
if (value == null || value.isBlank()) { public MultipartFile getImage() { return image; }
return null; public void setImage(MultipartFile image) { this.image = image; }
}
return Integer.valueOf(value.trim());
}
private String getListValue(List<String> list, int index) { public Boolean getRemoveImage() { return removeImage; }
if (list == null || index >= list.size()) { public void setRemoveImage(Boolean removeImage) { this.removeImage = removeImage; }
return null;
}
return list.get(index);
}
private String safeTrim(String value) {
return value == null ? null : value.trim();
}
private BigDecimal parseQuantity(String value) {
String cleaned = value.trim();
if (cleaned.matches("^\\d+(\\.\\d+)?$")) {
return new BigDecimal(cleaned);
}
if (cleaned.matches("^\\d+/\\d+$")) {
String[] parts = cleaned.split("/");
BigDecimal numerator = new BigDecimal(parts[0]);
BigDecimal denominator = new BigDecimal(parts[1]);
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
throw new IllegalArgumentException("Ingredient quantity cannot have a zero denominator.");
}
return numerator.divide(denominator, 8, RoundingMode.HALF_UP).stripTrailingZeros();
}
if (cleaned.matches("^\\d+\\s+\\d+/\\d+$")) {
String[] mixed = cleaned.split("\\s+");
BigDecimal whole = new BigDecimal(mixed[0]);
String[] fraction = mixed[1].split("/");
BigDecimal numerator = new BigDecimal(fraction[0]);
BigDecimal denominator = new BigDecimal(fraction[1]);
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
throw new IllegalArgumentException("Ingredient quantity cannot have a zero denominator.");
}
BigDecimal fractionalPart = numerator.divide(denominator, 8, RoundingMode.HALF_UP);
return whole.add(fractionalPart).stripTrailingZeros();
}
throw new IllegalArgumentException(
"Invalid ingredient quantity: " + value + ". Use values like 1, 1.5, 1/4, or 2 1/2.");
}
private String saveUploadedFile(MultipartFile file) throws IOException {
String originalFilename = StringUtils.cleanPath(file.getOriginalFilename());
String extension = "";
int dotIndex = originalFilename.lastIndexOf('.');
if (dotIndex >= 0) {
extension = originalFilename.substring(dotIndex);
}
String storedFilename = UUID.randomUUID() + extension;
Path uploadDir = Paths.get("uploads");
if (!Files.exists(uploadDir)) {
Files.createDirectories(uploadDir);
}
Path destination = uploadDir.resolve(storedFilename);
Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
return "/uploads/" + storedFilename;
}
} }