From 16c8d4c6dce5530a1b0ead53410907f6be069490 Mon Sep 17 00:00:00 2001 From: Aysegul Date: Thu, 8 Jan 2026 22:06:45 +0100 Subject: [PATCH 1/3] fixed todo delete progatation in recipe added count to IngredientController about what recipes we have or dont and 2 small typo mistakes also added counting ingredients to IngredientService --- commons/src/main/java/commons/Recipe.java | 3 +- .../java/server/api/IngredientController.java | 28 +++++++++++++------ .../server/service/IngredientService.java | 26 +++++++++++++---- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/commons/src/main/java/commons/Recipe.java b/commons/src/main/java/commons/Recipe.java index 1ae1f83..b8cfc3d 100644 --- a/commons/src/main/java/commons/Recipe.java +++ b/commons/src/main/java/commons/Recipe.java @@ -15,6 +15,7 @@ */ package commons; +import jakarta.persistence.CascadeType; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; @@ -60,7 +61,7 @@ public class Recipe { // | 1 (Steak) | 40g pepper | // | 1 (Steak) | Meat | // |----------------------------------| - @OneToMany + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @CollectionTable(name = "recipe_ingredients", joinColumns = @JoinColumn(name = "recipe_id")) @Column(name = "ingredient") // TODO: Replace String with Embeddable Ingredient Class diff --git a/server/src/main/java/server/api/IngredientController.java b/server/src/main/java/server/api/IngredientController.java index 324276b..4fec684 100644 --- a/server/src/main/java/server/api/IngredientController.java +++ b/server/src/main/java/server/api/IngredientController.java @@ -1,5 +1,6 @@ package server.api; + import commons.Ingredient; import commons.ws.Topics; import commons.ws.messages.CreateIngredientMessage; @@ -93,13 +94,22 @@ public class IngredientController { * * @see Ingredient */ - @GetMapping("/ingredients/{id}") - public ResponseEntity getIngredientById(@PathVariable Long id) { - return ingredientService.findById(id) - .map(ResponseEntity::ok) - .orElseGet(() -> ResponseEntity.notFound().build()); + @GetMapping("/ingredients/{id}/usage") + public ResponseEntity getIngredientUsage(@PathVariable Long id) { + if (ingredientService.findById(id).isEmpty()) { + return ResponseEntity.notFound().build(); + } + + // Server-side computation of how many recipes reference this ingredient + long usedInRecipes = ingredientService.countUsage(id); + return ResponseEntity.ok(new IngredientUsageResponse(id, usedInRecipes)); } + + + + + /** * Update an existing ingredient by its ID. * Maps to PATCH /api/ingredients/{id} @@ -126,7 +136,7 @@ public class IngredientController { updated.setId(id); return ingredientService.update(id, updated) .map(saved -> { - messagingTemplate.convertAndSend(Topics.INGREDIENTS, new CreateIngredientMessage(saved)); + messagingTemplate.convertAndSend(Topics.INGREDIENTS, new UpdateIngredientMessage(saved)); return ResponseEntity.ok(saved); }) .orElseGet(() -> ResponseEntity.notFound().build()); @@ -139,7 +149,7 @@ public class IngredientController { *

* If an ingredient with the same name already exists, * returns 400 Bad Request. - * + *

* If the ingredient is created successfully, * returns the created ingredient with 200 OK. *

@@ -157,7 +167,7 @@ public class IngredientController { return ingredientService.create(ingredient) .map(saved -> { - messagingTemplate.convertAndSend(Topics.INGREDIENTS, new UpdateIngredientMessage(saved)); + messagingTemplate.convertAndSend(Topics.INGREDIENTS, new CreateIngredientMessage(saved)); return ResponseEntity.ok(saved); }) .orElseGet(() -> ResponseEntity.badRequest().build()); @@ -186,4 +196,6 @@ public class IngredientController { messagingTemplate.convertAndSend(Topics.INGREDIENTS, new DeleteIngredientMessage(id)); return ResponseEntity.ok(true); } + + public record IngredientUsageResponse(Long ingredientId, long usedInRecipes){} } diff --git a/server/src/main/java/server/service/IngredientService.java b/server/src/main/java/server/service/IngredientService.java index 8a79659..37cb76c 100644 --- a/server/src/main/java/server/service/IngredientService.java +++ b/server/src/main/java/server/service/IngredientService.java @@ -4,16 +4,21 @@ import commons.Ingredient; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import server.database.IngredientRepository; +import server.database.RecipeIngredientRepository; import java.util.List; import java.util.Optional; @Service public class IngredientService { - IngredientRepository ingredientRepository; - public IngredientService(IngredientRepository ingredientRepository) { + private final IngredientRepository ingredientRepository; + private final RecipeIngredientRepository recipeIngredientRepository; + + public IngredientService(IngredientRepository ingredientRepository, + RecipeIngredientRepository recipeIngredientRepository) { this.ingredientRepository = ingredientRepository; + this.recipeIngredientRepository = recipeIngredientRepository; } public Optional findById(Long id) { @@ -30,12 +35,16 @@ public class IngredientService { /** * Creates a new ingredient. Returns empty if the recipe with the same name or id already exists. + * * @param ingredient Ingredient to be saved in the db. * @return The created ingredient (the ingredient arg with a new assigned id) or empty if it already exists in db. */ public Optional create(Ingredient ingredient) { - if (ingredientRepository.existsByName(ingredient.getName()) || - ingredientRepository.existsById(ingredient.getId())) { + if (ingredient == null || ingredient.getName() == null) { + return Optional.empty(); + } + + if (ingredientRepository.existsByName(ingredient.getName())) { return Optional.empty(); } @@ -44,7 +53,8 @@ public class IngredientService { /** * Updates an ingredient. The ingredient with the provided id will be replaced (in db) with the provided ingredient. - * @param id id of the ingredient to update. + * + * @param id id of the ingredient to update. * @param ingredient Ingredient to be saved in the db. * @return The created ingredient (the ingredient arg with a new assigned id.) */ @@ -64,4 +74,10 @@ public class IngredientService { return true; } + //actually counting the amount used in recipes + + public long countUsage(long ingredientId) { + return recipeIngredientRepository.countByIngredientId(ingredientId); + + } } From 79e7b013637e55136b9d9895f30636318228c861 Mon Sep 17 00:00:00 2001 From: Aysegul Date: Thu, 8 Jan 2026 22:20:54 +0100 Subject: [PATCH 2/3] fixed todo delete progatation in recipe added count to IngredientController about what recipes we have or dont and 2 small typo mistakes also added counting ingredients to IngredientService the pipeline failure is bc i forgot tests exist --- .../main/java/server/api/IngredientController.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/server/api/IngredientController.java b/server/src/main/java/server/api/IngredientController.java index 4fec684..99dcf54 100644 --- a/server/src/main/java/server/api/IngredientController.java +++ b/server/src/main/java/server/api/IngredientController.java @@ -94,6 +94,14 @@ public class IngredientController { * * @see Ingredient */ + + @GetMapping("/ingredients/{id}") + public ResponseEntity getIngredientById(@PathVariable Long id) { + return ingredientService.findById(id) + .map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); + } + @GetMapping("/ingredients/{id}/usage") public ResponseEntity getIngredientUsage(@PathVariable Long id) { if (ingredientService.findById(id).isEmpty()) { @@ -107,9 +115,6 @@ public class IngredientController { - - - /** * Update an existing ingredient by its ID. * Maps to PATCH /api/ingredients/{id} From 41353f2252371610f60b4007ec75950825bd1d18 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Fri, 9 Jan 2026 12:24:18 +0100 Subject: [PATCH 3/3] Logging for flow of the data exchange between client/server --- client/pom.xml | 12 +++++++++++ client/src/main/java/client/Main.java | 9 ++++++++ .../client/scenes/FoodpalApplicationCtrl.java | 21 +++++++++++++++---- .../client/scenes/LangSelectMenuCtrl.java | 3 +++ .../scenes/recipe/RecipeDetailCtrl.java | 2 +- .../main/java/client/utils/ConfigService.java | 7 +++++-- .../main/java/client/utils/ServerUtils.java | 9 +++++--- .../client/utils/WebSocketDataService.java | 8 ++++++- .../java/client/utils/WebSocketUtils.java | 10 ++++----- commons/src/main/java/commons/Ingredient.java | 8 +++++++ commons/src/main/java/commons/Recipe.java | 10 ++++----- .../java/server/api/RecipeController.java | 8 +++++++ 12 files changed, 85 insertions(+), 22 deletions(-) diff --git a/client/pom.xml b/client/pom.xml index 3bd793a..509f836 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -118,6 +118,18 @@ compile + + org.slf4j + jul-to-slf4j + 2.0.17 + + + + ch.qos.logback + logback-classic + 1.5.20 + + diff --git a/client/src/main/java/client/Main.java b/client/src/main/java/client/Main.java index a0bd716..0c22fda 100644 --- a/client/src/main/java/client/Main.java +++ b/client/src/main/java/client/Main.java @@ -15,8 +15,17 @@ */ package client; +import org.slf4j.bridge.SLF4JBridgeHandler; + public class Main { + static { + // Choose SLF4J Logger (Spring Boot) for JavaFX. + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + } public static void main(String[] args){ + + UI.launch(UI.class, args); } } \ No newline at end of file diff --git a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java index 2453ccd..cdbd565 100644 --- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java +++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.logging.Logger; import java.util.stream.Collectors; import client.exception.InvalidModificationException; @@ -45,7 +46,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { private final WebSocketUtils webSocketUtils; private final LocaleManager localeManager; private final WebSocketDataService dataService; - + private final Logger logger = Logger.getLogger(FoodpalApplicationCtrl.class.getName()); @FXML private RecipeDetailCtrl recipeDetailController; @@ -93,24 +94,31 @@ public class FoodpalApplicationCtrl implements LocaleAware { this.configService = configService; this.dataService = recipeDataService; setupDataService(); + logger.info("WebSocket processor initialized."); initializeWebSocket(); + logger.info("WebSocket connection handler initialized."); + logger.info("Main application controller initialized."); } private void setupDataService() { dataService.setMessageParser((msg) -> switch (msg) { case CreateRecipeMessage _ -> (m) -> { CreateRecipeMessage crm = (CreateRecipeMessage) m; + logger.info("Server informs us of creation of recipe: " + crm.getRecipe()); return handleCreateRecipeMessage(crm); }; case UpdateRecipeMessage _ -> (m) -> { UpdateRecipeMessage urm = (UpdateRecipeMessage) m; + logger.info("Server informs us of update for recipe: " + urm.getRecipe()); return handleUpdateRecipeMessage(urm); }; case DeleteRecipeMessage _ -> (m) -> { DeleteRecipeMessage drm = (DeleteRecipeMessage) m; + logger.info("Server informs us of the deletion of recipe with ID: " + drm.getRecipeId()); return handleDeleteRecipeMessage(drm); }; case FavouriteRecipeMessage _ -> (m) -> { FavouriteRecipeMessage frm = (FavouriteRecipeMessage) m; + logger.info("Server informs us of a favourite recipe being deleted: " + frm.getRecipeId()); return handleFavouriteRecipeMessage(frm); }; default -> throw new IllegalStateException("Unexpected value: " + msg); @@ -185,7 +193,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { this.recipeList.getItems().setAll(recipes); - System.out.println("Search returned " + recipes.size() + " recipes."); + logger.info("Search returned " + recipes.size() + " recipes."); // Restore selection, if possible if (newIndex != -1) { @@ -263,7 +271,9 @@ public class FoodpalApplicationCtrl implements LocaleAware { recipes = server.getRecipesFiltered(searchBarController.getFilter()); } catch (IOException | InterruptedException e) { recipes = Collections.emptyList(); - System.err.println("Failed to load recipes: " + e.getMessage()); + String msg = "Failed to load recipes: " + e.getMessage(); + logger.severe(msg); + printError(msg); } allRecipes = new ArrayList<>(recipes); @@ -295,7 +305,10 @@ public class FoodpalApplicationCtrl implements LocaleAware { dataService.add(newRecipe.getId(), recipe -> { this.recipeList.getSelectionModel().select(recipe); openSelectedRecipe(); - Platform.runLater(() -> this.recipeDetailController.editRecipeTitle()); + Platform.runLater(() -> { + logger.info("Focused recipe title edit box."); + this.recipeDetailController.editRecipeTitle(); + }); }); recipeList.refresh(); } catch (IOException | InterruptedException e) { diff --git a/client/src/main/java/client/scenes/LangSelectMenuCtrl.java b/client/src/main/java/client/scenes/LangSelectMenuCtrl.java index 1df3e54..41df1bd 100644 --- a/client/src/main/java/client/scenes/LangSelectMenuCtrl.java +++ b/client/src/main/java/client/scenes/LangSelectMenuCtrl.java @@ -15,6 +15,7 @@ import javafx.util.StringConverter; import java.io.InputStream; import java.util.Locale; +import java.util.logging.Logger; /** * The language selection menu controller. @@ -22,6 +23,7 @@ import java.util.Locale; * getLocaleString(String) function is available. */ public class LangSelectMenuCtrl implements LocaleAware { + private final Logger logger = Logger.getLogger(LangSelectMenuCtrl.class.getName()); public ComboBox langSelectMenu; private final LocaleManager manager; @@ -37,6 +39,7 @@ public class LangSelectMenuCtrl implements LocaleAware { @FXML private void switchLocale(ActionEvent event) { String lang = langSelectMenu.getSelectionModel().getSelectedItem(); + logger.info("Switching locale to " + lang); manager.setLocale(Locale.of(lang)); } diff --git a/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java b/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java index 0675407..716a128 100644 --- a/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java +++ b/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java @@ -207,7 +207,7 @@ public class RecipeDetailCtrl implements LocaleAware { try { server.updateRecipe(this.recipe); - this.refresh(); + // this.refresh(); } catch (IOException | InterruptedException e) { // throw a nice blanket UpdateException throw new UpdateException("Error occurred when updating recipe name!"); diff --git a/client/src/main/java/client/utils/ConfigService.java b/client/src/main/java/client/utils/ConfigService.java index d1d70c6..54c7036 100644 --- a/client/src/main/java/client/utils/ConfigService.java +++ b/client/src/main/java/client/utils/ConfigService.java @@ -7,10 +7,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.nio.file.Path; import java.io.File; import java.io.IOException; +import java.util.logging.Logger; public class ConfigService { private final Path configPath; private final ObjectMapper mapper = new ObjectMapper(); + private final Logger logger = Logger.getLogger(ConfigService.class.getName()); private Config config; @@ -70,9 +72,10 @@ public class ConfigService { try { File file = configPath.toFile(); // file is the config file here mapper.writeValue(file, config); // here we edit the value of the file using config - + logger.info("Config saved to " + file.getAbsolutePath()); } - catch (Exception e){ + catch (Exception e) { + logger.severe("Something bad happened while saving the config: " + e.getMessage()); throw new RuntimeException(e); } } diff --git a/client/src/main/java/client/utils/ServerUtils.java b/client/src/main/java/client/utils/ServerUtils.java index cb91e76..9c89508 100644 --- a/client/src/main/java/client/utils/ServerUtils.java +++ b/client/src/main/java/client/utils/ServerUtils.java @@ -18,12 +18,14 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.ArrayList; import java.util.List; +import java.util.logging.Logger; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; public class ServerUtils { private static final String SERVER = "http://localhost:8080/api"; + private Logger logger = Logger.getLogger(ServerUtils.class.getName()); private final HttpClient client; private final ObjectMapper objectMapper = new ObjectMapper(); private final int statusOK = 200; @@ -48,9 +50,10 @@ public class ServerUtils { throw new IOException("No recipe to get. Server responds with " + response.body()); } - - return objectMapper.readValue(response.body(), new TypeReference>() { - });// JSON string-> List (Jackson) + List list = objectMapper.readValue(response.body(), new TypeReference>() { + }); + logger.info("Received response from server: " + list); + return list; // JSON string-> List (Jackson) } public List getRecipesFiltered(String filter) throws IOException, InterruptedException { diff --git a/client/src/main/java/client/utils/WebSocketDataService.java b/client/src/main/java/client/utils/WebSocketDataService.java index 659d91b..52d0b1f 100644 --- a/client/src/main/java/client/utils/WebSocketDataService.java +++ b/client/src/main/java/client/utils/WebSocketDataService.java @@ -10,8 +10,10 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Function; +import java.util.logging.Logger; public class WebSocketDataService { + private Logger logger = Logger.getLogger(WebSocketDataService.class.getName()); public WebSocketDataService() { } @@ -49,7 +51,11 @@ public class WebSocketDataService { */ public boolean add(ID id, Consumer onComplete) { CompletableFuture future = new CompletableFuture<>(); - future.thenAccept(onComplete.andThen(_ -> pendingRegister.remove(id))); + future.thenAccept(onComplete.andThen(_ -> { + logger.info("Item " + id + " resolved. Removing from pending register."); + pendingRegister.remove(id); + })); + logger.info("Item " + id + " pending propagation. Adding to pending register."); return pendingRegister.putIfAbsent(id, future) == null; } public boolean add(ID id) { diff --git a/client/src/main/java/client/utils/WebSocketUtils.java b/client/src/main/java/client/utils/WebSocketUtils.java index ed16b37..bb070be 100644 --- a/client/src/main/java/client/utils/WebSocketUtils.java +++ b/client/src/main/java/client/utils/WebSocketUtils.java @@ -14,11 +14,13 @@ import java.lang.reflect.Type; import java.util.function.Consumer; import javax.annotation.Nullable; import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; public class WebSocketUtils { private static final String WS_URL = "ws://localhost:8080/updates"; private WebSocketStompClient stompClient; private StompSession stompSession; + private Logger logger = Logger.getLogger(WebSocketUtils.class.getName()); /** * Connect to the websocket server. @@ -36,21 +38,19 @@ public class WebSocketUtils { @Override public void afterConnected(StompSession session, StompHeaders connectedHeaders) { stompSession = session; - System.out.println("WebSocket connected: " + session.getSessionId()); + logger.info("WebSocket connected with session ID: " + session.getSessionId()); } @Override public void handleException(StompSession session, @Nullable StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) { - System.err.println("STOMP error: " + exception.getMessage()); - exception.printStackTrace(); + logger.severe("STOMP error: " + exception.getMessage()); } @Override public void handleTransportError(StompSession session, Throwable exception) { - System.err.println("STOMP transport error: " + exception.getMessage()); - exception.printStackTrace(); + logger.severe("STOMP transport error: " + exception.getMessage()); } } ); diff --git a/commons/src/main/java/commons/Ingredient.java b/commons/src/main/java/commons/Ingredient.java index dd93aab..5704b74 100644 --- a/commons/src/main/java/commons/Ingredient.java +++ b/commons/src/main/java/commons/Ingredient.java @@ -103,6 +103,14 @@ public class Ingredient { public int hashCode() { return Objects.hash(id, name, proteinPer100g, fatPer100g, carbsPer100g); } + + @Override + public String toString() { + return "Ingredient " + id + " - " + name + + "= P:" + proteinPer100g + + "/F:" + fatPer100g + + "/C:" + carbsPer100g + " per 100g"; + } } diff --git a/commons/src/main/java/commons/Recipe.java b/commons/src/main/java/commons/Recipe.java index 1ae1f83..5c9b19e 100644 --- a/commons/src/main/java/commons/Recipe.java +++ b/commons/src/main/java/commons/Recipe.java @@ -157,12 +157,10 @@ public class Recipe { @Override public String toString() { - return "Recipe{" + - "id=" + id + - ", name='" + name + '\'' + - ", ingredientsCount=" + ingredients.size() + - ", preparationStepsCount=" + preparationSteps.size() + - "}"; + return "Recipe " + id + + " - " + name + + ": " + ingredients.size() + " ingredients / " + + preparationSteps.size() + " steps"; } @SuppressWarnings("unused") diff --git a/server/src/main/java/server/api/RecipeController.java b/server/src/main/java/server/api/RecipeController.java index 37a2a78..e2143e7 100644 --- a/server/src/main/java/server/api/RecipeController.java +++ b/server/src/main/java/server/api/RecipeController.java @@ -24,16 +24,19 @@ import server.service.RecipeService; import java.util.List; import java.util.Optional; +import java.util.logging.Logger; @RestController @RequestMapping("/api") public class RecipeController { + private static final Logger logger = Logger.getLogger(RecipeController.class.getName()); private final SimpMessagingTemplate messagingTemplate; private final RecipeService recipeService; public RecipeController(RecipeService recipeService, SimpMessagingTemplate messagingTemplate) { this.recipeService = recipeService; this.messagingTemplate = messagingTemplate; + logger.info("Initialized controller."); } /** @@ -46,6 +49,7 @@ public class RecipeController { */ @GetMapping("/recipe/{id}") public ResponseEntity getRecipe(@PathVariable Long id) { + logger.info("GET /recipe/" + id + " called."); return recipeService.findById(id) .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); @@ -60,6 +64,7 @@ public class RecipeController { */ @GetMapping("/recipes") public ResponseEntity> getRecipes(@RequestParam Optional limit) { + logger.info("GET /recipes called."); return ResponseEntity.ok( // Choose the right overload. One has a limit, other doesn't. limit.map(recipeService::findAll).orElseGet(recipeService::findAll) @@ -76,6 +81,7 @@ public class RecipeController { */ @PostMapping("/recipe/{id}") public ResponseEntity updateRecipe(@PathVariable Long id, @RequestBody Recipe recipe) { + logger.info("POST /recipe/" + id + " called."); return recipeService.update(id, recipe) .map(saved -> { messagingTemplate.convertAndSend(Topics.RECIPES, new UpdateRecipeMessage(saved)); // Send to WS. @@ -96,6 +102,7 @@ public class RecipeController { */ @PutMapping("/recipe/new") public ResponseEntity createRecipe(@RequestBody Recipe recipe) { + logger.info("POST /recipe/new called."); return recipeService.create(recipe) .map(saved -> { messagingTemplate.convertAndSend(Topics.RECIPES, new CreateRecipeMessage(saved)); // Send to WS. @@ -114,6 +121,7 @@ public class RecipeController { */ @DeleteMapping("/recipe/{id}") public ResponseEntity deleteRecipe(@PathVariable Long id) { + logger.info("DELETE /recipe/" + id + " called."); if (!recipeService.delete(id)) { return ResponseEntity.badRequest().build(); }