mirror of
https://gitlab.com/etc404/software-engineering-project.git
synced 2026-05-10 20:52:58 +00:00
Update 2 files
- /src/main/java/com/example/demo/controller/RecipeUploadController.java - /src/main/resources/templates/create-recipe.html
This commit is contained in:
@@ -2,7 +2,6 @@ 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;
|
||||||
@@ -12,6 +11,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -34,6 +34,9 @@ public class RecipeUploadController {
|
|||||||
|
|
||||||
private final RecipeService recipeService;
|
private final RecipeService recipeService;
|
||||||
|
|
||||||
|
@Value("${app.upload.dir}")
|
||||||
|
private String uploadDir;
|
||||||
|
|
||||||
public RecipeUploadController(RecipeService recipeService) {
|
public RecipeUploadController(RecipeService recipeService) {
|
||||||
this.recipeService = recipeService;
|
this.recipeService = recipeService;
|
||||||
}
|
}
|
||||||
@@ -96,7 +99,8 @@ public class RecipeUploadController {
|
|||||||
String quantityString = getListValue(form.getIngredientQuantity(), i);
|
String quantityString = getListValue(form.getIngredientQuantity(), i);
|
||||||
String quantity = null;
|
String quantity = null;
|
||||||
if (quantityString != null && !quantityString.isBlank()) {
|
if (quantityString != null && !quantityString.isBlank()) {
|
||||||
quantity = parseQuantity(quantityString.trim()).toPlainString();
|
quantity = quantityString.trim();
|
||||||
|
validateQuantity(quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
String unit = getListValue(form.getIngredientUnit(), i);
|
String unit = getListValue(form.getIngredientUnit(), i);
|
||||||
@@ -170,11 +174,11 @@ public class RecipeUploadController {
|
|||||||
return value == null ? null : value.trim();
|
return value == null ? null : value.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal parseQuantity(String value) {
|
private void validateQuantity(String value) {
|
||||||
String cleaned = value.trim();
|
String cleaned = value.trim();
|
||||||
|
|
||||||
if (cleaned.matches("^\\d+(\\.\\d+)?$")) {
|
if (cleaned.matches("^\\d+(\\.\\d+)?$")) {
|
||||||
return new BigDecimal(cleaned);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleaned.matches("^\\d+/\\d+$")) {
|
if (cleaned.matches("^\\d+/\\d+$")) {
|
||||||
@@ -185,8 +189,10 @@ public class RecipeUploadController {
|
|||||||
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
|
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
throw new IllegalArgumentException("Ingredient quantity cannot have a zero denominator.");
|
throw new IllegalArgumentException("Ingredient quantity cannot have a zero denominator.");
|
||||||
}
|
}
|
||||||
|
if (numerator.compareTo(BigDecimal.ZERO) < 0 || denominator.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
return numerator.divide(denominator, 8, RoundingMode.HALF_UP).stripTrailingZeros();
|
throw new IllegalArgumentException("Ingredient quantity must be positive.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleaned.matches("^\\d+\\s+\\d+/\\d+$")) {
|
if (cleaned.matches("^\\d+\\s+\\d+/\\d+$")) {
|
||||||
@@ -200,9 +206,12 @@ public class RecipeUploadController {
|
|||||||
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
|
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
throw new IllegalArgumentException("Ingredient quantity cannot have a zero denominator.");
|
throw new IllegalArgumentException("Ingredient quantity cannot have a zero denominator.");
|
||||||
}
|
}
|
||||||
|
if (whole.compareTo(BigDecimal.ZERO) < 0
|
||||||
BigDecimal fractionalPart = numerator.divide(denominator, 8, RoundingMode.HALF_UP);
|
|| numerator.compareTo(BigDecimal.ZERO) < 0
|
||||||
return whole.add(fractionalPart).stripTrailingZeros();
|
|| denominator.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
|
throw new IllegalArgumentException("Ingredient quantity must be positive.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
@@ -220,12 +229,12 @@ public class RecipeUploadController {
|
|||||||
|
|
||||||
String storedFilename = UUID.randomUUID() + extension;
|
String storedFilename = UUID.randomUUID() + extension;
|
||||||
|
|
||||||
Path uploadDir = Paths.get("uploads");
|
Path uploadPath = Paths.get(uploadDir).toAbsolutePath().normalize();
|
||||||
if (!Files.exists(uploadDir)) {
|
if (!Files.exists(uploadPath)) {
|
||||||
Files.createDirectories(uploadDir);
|
Files.createDirectories(uploadPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
Path destination = uploadDir.resolve(storedFilename);
|
Path destination = uploadPath.resolve(storedFilename);
|
||||||
Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
return "/uploads/" + storedFilename;
|
return "/uploads/" + storedFilename;
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ function addIngredient(data = {}) {
|
|||||||
row.className = 'dynamic-row';
|
row.className = 'dynamic-row';
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<input type="text" class="ing-name" placeholder="Ingredient (e.g. Sugar)" value="${escapeHtml(data.ingredientName || '')}">
|
<input type="text" class="ing-name" placeholder="Ingredient (e.g. Sugar)" value="${escapeHtml(data.ingredientName || '')}">
|
||||||
<input type="text" class="ing-qty" placeholder="Qty (e.g. 3)" value="${data.quantity ?? ''}">
|
<input type="text" class="ing-qty" placeholder="Qty (e.g. 2, 1/2, 1 1/2)" value="${escapeHtml(data.quantity ?? '')}">
|
||||||
<input type="text" class="ing-unit" placeholder="Unit (e.g. tbsp)" value="${escapeHtml(data.unit || '')}">
|
<input type="text" class="ing-unit" placeholder="Unit (e.g. tbsp)" value="${escapeHtml(data.unit || '')}">
|
||||||
<input type="text" class="ing-notes" placeholder="Notes (optional)" value="${escapeHtml(data.notes || '')}">
|
<input type="text" class="ing-notes" placeholder="Notes (optional)" value="${escapeHtml(data.notes || '')}">
|
||||||
<button class="btn-remove" title="Remove" type="button">✕</button>`;
|
<button class="btn-remove" title="Remove" type="button">✕</button>`;
|
||||||
@@ -283,7 +283,7 @@ function showMessage(message, isError = false) {
|
|||||||
function showSuccessAndRedirect(message) {
|
function showSuccessAndRedirect(message) {
|
||||||
showMessage(message, false);
|
showMessage(message, false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = '/';
|
window.location.href = '/explore';
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,6 +317,29 @@ function clearAllErrors() {
|
|||||||
box.textContent = '';
|
box.textContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidQuantity(val) {
|
||||||
|
const cleaned = String(val ?? '').replace(/\s+/g, ' ').trim();
|
||||||
|
|
||||||
|
if (cleaned === '') return true;
|
||||||
|
|
||||||
|
if (/^\d+(\.\d+)?$/.test(cleaned)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\d+\/\d+$/.test(cleaned)) {
|
||||||
|
const [numerator, denominator] = cleaned.split('/');
|
||||||
|
return Number(numerator) >= 0 && Number(denominator) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\d+\s\d+\/\d+$/.test(cleaned)) {
|
||||||
|
const [whole, fraction] = cleaned.split(' ');
|
||||||
|
const [numerator, denominator] = fraction.split('/');
|
||||||
|
return Number(whole) >= 0 && Number(numerator) >= 0 && Number(denominator) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function validateForm() {
|
function validateForm() {
|
||||||
let valid = true;
|
let valid = true;
|
||||||
|
|
||||||
@@ -363,6 +386,7 @@ function validateForm() {
|
|||||||
|
|
||||||
const ingredientRows = [...document.querySelectorAll('#ingredients-container .dynamic-row')];
|
const ingredientRows = [...document.querySelectorAll('#ingredients-container .dynamic-row')];
|
||||||
const filledIngredientRows = ingredientRows.filter(row => row.querySelector('.ing-name').value.trim() !== '');
|
const filledIngredientRows = ingredientRows.filter(row => row.querySelector('.ing-name').value.trim() !== '');
|
||||||
|
|
||||||
if (filledIngredientRows.length === 0 && ingredientRows.length > 0) {
|
if (filledIngredientRows.length === 0 && ingredientRows.length > 0) {
|
||||||
showError(ingredientRows[0].querySelector('.ing-name'), 'Add at least one ingredient');
|
showError(ingredientRows[0].querySelector('.ing-name'), 'Add at least one ingredient');
|
||||||
valid = false;
|
valid = false;
|
||||||
@@ -371,6 +395,7 @@ function validateForm() {
|
|||||||
filledIngredientRows.forEach(row => {
|
filledIngredientRows.forEach(row => {
|
||||||
const nameInput = row.querySelector('.ing-name');
|
const nameInput = row.querySelector('.ing-name');
|
||||||
const qtyInput = row.querySelector('.ing-qty');
|
const qtyInput = row.querySelector('.ing-qty');
|
||||||
|
|
||||||
clearError(nameInput);
|
clearError(nameInput);
|
||||||
clearError(qtyInput);
|
clearError(qtyInput);
|
||||||
|
|
||||||
@@ -384,17 +409,6 @@ function validateForm() {
|
|||||||
showError(qtyInput, 'Enter a valid quantity (e.g. 2, 1.5, 1/2, 1 1/2) or leave blank');
|
showError(qtyInput, 'Enter a valid quantity (e.g. 2, 1.5, 1/2, 1 1/2) or leave blank');
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function isValidQuantity(val) {
|
|
||||||
if (val === '') return true;
|
|
||||||
if (!isNaN(Number(val))) return true;
|
|
||||||
if (/^\d+\/\d+$/.test(val)) return true;
|
|
||||||
if (/^\d+\s+\d+\/\d+$/.test(val)) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const stepAreas = [...document.querySelectorAll('#steps-container textarea')];
|
const stepAreas = [...document.querySelectorAll('#steps-container textarea')];
|
||||||
|
|||||||
Reference in New Issue
Block a user