From cce24ab61d8578294d1e2097a3a3dee5fa746bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Rasie=C5=84ski?= Date: Fri, 16 Jan 2026 19:04:51 +0100 Subject: [PATCH 1/4] Added isPending to ws dataservice --- client/src/main/java/client/utils/WebSocketDataService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/main/java/client/utils/WebSocketDataService.java b/client/src/main/java/client/utils/WebSocketDataService.java index 52d0b1f..33f26a9 100644 --- a/client/src/main/java/client/utils/WebSocketDataService.java +++ b/client/src/main/java/client/utils/WebSocketDataService.java @@ -58,7 +58,12 @@ public class WebSocketDataService { logger.info("Item " + id + " pending propagation. Adding to pending register."); return pendingRegister.putIfAbsent(id, future) == null; } + public boolean add(ID id) { return add(id, (_) -> {}); } + + public boolean isPending(ID id) { + return pendingRegister.containsKey(id); + } } From face654aa53538bdd4a53490f0ad615aaa77df27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Rasie=C5=84ski?= Date: Fri, 16 Jan 2026 19:11:48 +0100 Subject: [PATCH 2/4] Made delete() return a deepcopy of recipe. --- .../java/server/service/RecipeService.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/server/service/RecipeService.java b/server/src/main/java/server/service/RecipeService.java index ef219a5..a76c5bb 100644 --- a/server/src/main/java/server/service/RecipeService.java +++ b/server/src/main/java/server/service/RecipeService.java @@ -75,11 +75,28 @@ public class RecipeService { return Optional.of(saveWithDependencies(recipe)); } - public boolean delete(Long id) { + /** + * Deletes a recipe by id and returns the deleted object. + * @param id id of the recipe to delete. + * @return The deleted recipe (deep copy). + */ + public Optional delete(Long id) { // TODO: Propagate deletion to ingredients. - if (!recipeRepository.existsById(id)) return false; + Optional recipe = recipeRepository.findById(id); + if (recipe.isEmpty()) return Optional.empty(); + + // Make deep copy before removal. ( Had some lazy loading issues otherwise ) + Recipe r = recipe.get(); + Recipe copy = new Recipe( + r.getId(), + r.getName(), + r.getLocale(), + List.copyOf(r.getIngredients()), + List.copyOf(r.getPreparationSteps()) + ); + recipeRepository.deleteById(id); - return true; + return Optional.of(copy); } private Recipe saveWithDependencies(Recipe recipe) { From 675e2799a5dd8b29210e6a35ea89f2cc4da5e2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Rasie=C5=84ski?= Date: Fri, 16 Jan 2026 19:12:33 +0100 Subject: [PATCH 3/4] Fixed usages of delete. --- server/src/main/java/server/api/RecipeController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/server/api/RecipeController.java b/server/src/main/java/server/api/RecipeController.java index fe64067..2ead92d 100644 --- a/server/src/main/java/server/api/RecipeController.java +++ b/server/src/main/java/server/api/RecipeController.java @@ -145,10 +145,11 @@ public class RecipeController { @DeleteMapping("/recipe/{id}") public ResponseEntity deleteRecipe(@PathVariable Long id) { logger.info("DELETE /recipe/" + id + " called."); - if (!recipeService.delete(id)) { + Optional recipe = recipeService.delete(id); + if (recipe.isEmpty()) { return ResponseEntity.badRequest().build(); } - messagingTemplate.convertAndSend(Topics.RECIPES, new DeleteRecipeMessage(id)); // Send to WS. + messagingTemplate.convertAndSend(Topics.RECIPES, new DeleteRecipeMessage(recipe.get())); // Send to WS. return ResponseEntity.ok(true); } From caeacf86e05c141b19524d23aa245e1bfaf49632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Rasie=C5=84ski?= Date: Fri, 16 Jan 2026 19:13:35 +0100 Subject: [PATCH 4/4] Made Delete msg take Recipe instead of id and made alert for recipe mourning. --- .../client/scenes/FoodpalApplicationCtrl.java | 28 +++++++++++++++---- .../ws/messages/DeleteRecipeMessage.java | 20 +++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java index 93ec852..cb8e7d3 100644 --- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java +++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java @@ -122,7 +122,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { }; case DeleteRecipeMessage _ -> (m) -> { DeleteRecipeMessage drm = (DeleteRecipeMessage) m; - logger.info("Server informs us of the deletion of recipe with ID: " + drm.getRecipeId()); + logger.info("Server informs us of the deletion of recipe: " + drm.getRecipe()); return handleDeleteRecipeMessage(drm); }; case FavouriteRecipeMessage _ -> (m) -> { @@ -155,12 +155,28 @@ public class FoodpalApplicationCtrl implements LocaleAware { return new ImmutablePair<>(recipe.getId(), recipe); } private Pair handleDeleteRecipeMessage(DeleteRecipeMessage drm) { - this.recipeList.getItems().remove(findRecipeById(drm.getRecipeId()).orElseThrow( - () -> new InvalidModificationException("Invalid recipe id during delete: " + drm.getRecipeId()) + Recipe recipe = drm.getRecipe(); + + // If it's not pending means other client deleted. + boolean externalDelete = !dataService.isPending(recipe.getId()); + + this.recipeList.getItems().remove(findRecipeById(recipe.getId()).orElseThrow( + () -> new InvalidModificationException("Invalid recipe id during delete: " + recipe.getId()) )); - dataService.add(drm.getRecipeId()); - // TODO Make it an Optional so that we don't need to touch Null? - return new ImmutablePair<>(drm.getRecipeId(), null); + + // Show an alert to mourn a lost comrade (recipe). + if (externalDelete && config.isFavourite(recipe.getId())) { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Mourn your loss!!!"); + alert.setHeaderText(null); + + alert.setContentText("Your most beloved recipe by the name of \"" + + recipe.getName() + "\" has been removed."); + alert.showAndWait(); + } + + dataService.add(recipe.getId()); + return new ImmutablePair<>(recipe.getId(), recipe); } // TODO Implementation private Pair handleFavouriteRecipeMessage(FavouriteRecipeMessage frm) { diff --git a/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java b/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java index 8eab4e6..fd16d15 100644 --- a/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java +++ b/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java @@ -1,17 +1,19 @@ package commons.ws.messages; +import commons.Recipe; + /** * Message sent when a recipe is deleted. * * @see commons.ws.messages.Message.Type#RECIPE_DELETE */ public class DeleteRecipeMessage implements Message { - private Long recipeId; + private Recipe recipe; public DeleteRecipeMessage() {} // for jackson - public DeleteRecipeMessage(Long recipeId) { - this.recipeId = recipeId; + public DeleteRecipeMessage(Recipe recipe) { + this.recipe = recipe; } @Override @@ -20,16 +22,16 @@ public class DeleteRecipeMessage implements Message { } /** - * Get the ID of the deleted recipe. + * Get the deleted recipe. * - * @return The ID of the deleted recipe. + * @return The deleted recipe. */ - public Long getRecipeId() { - return recipeId; + public Recipe getRecipe() { + return recipe; } // for jackson - public void setRecipeId(Long recipeId) { - this.recipeId = recipeId; + public void setRecipe(Recipe recipe) { + this.recipe = recipe; } }