Merge branch 'mourning-recipes' into 'main'

Mourning recipes

See merge request cse1105/2025-2026/teams/csep-team-76!65
This commit is contained in:
Oskar Rasieński 2026-01-16 22:10:57 +01:00
commit f6d5551408
5 changed files with 61 additions and 20 deletions

View file

@ -127,7 +127,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
}; };
case DeleteRecipeMessage _ -> (m) -> { case DeleteRecipeMessage _ -> (m) -> {
DeleteRecipeMessage drm = (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); return handleDeleteRecipeMessage(drm);
}; };
case FavouriteRecipeMessage _ -> (m) -> { case FavouriteRecipeMessage _ -> (m) -> {
@ -160,12 +160,28 @@ public class FoodpalApplicationCtrl implements LocaleAware {
return new ImmutablePair<>(recipe.getId(), recipe); return new ImmutablePair<>(recipe.getId(), recipe);
} }
private Pair<Long, Recipe> handleDeleteRecipeMessage(DeleteRecipeMessage drm) { private Pair<Long, Recipe> handleDeleteRecipeMessage(DeleteRecipeMessage drm) {
this.recipeList.getItems().remove(findRecipeById(drm.getRecipeId()).orElseThrow( Recipe recipe = drm.getRecipe();
() -> new InvalidModificationException("Invalid recipe id during delete: " + drm.getRecipeId())
// 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<Recipe> so that we don't need to touch Null? // Show an alert to mourn a lost comrade (recipe).
return new ImmutablePair<>(drm.getRecipeId(), null); 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 // TODO Implementation
private Pair<Long, Recipe> handleFavouriteRecipeMessage(FavouriteRecipeMessage frm) { private Pair<Long, Recipe> handleFavouriteRecipeMessage(FavouriteRecipeMessage frm) {

View file

@ -58,7 +58,12 @@ public class WebSocketDataService<ID, Value> {
logger.info("Item " + id + " pending propagation. Adding to pending register."); logger.info("Item " + id + " pending propagation. Adding to pending register.");
return pendingRegister.putIfAbsent(id, future) == null; return pendingRegister.putIfAbsent(id, future) == null;
} }
public boolean add(ID id) { public boolean add(ID id) {
return add(id, (_) -> {}); return add(id, (_) -> {});
} }
public boolean isPending(ID id) {
return pendingRegister.containsKey(id);
}
} }

View file

@ -1,17 +1,19 @@
package commons.ws.messages; package commons.ws.messages;
import commons.Recipe;
/** /**
* Message sent when a recipe is deleted. * Message sent when a recipe is deleted.
* *
* @see commons.ws.messages.Message.Type#RECIPE_DELETE * @see commons.ws.messages.Message.Type#RECIPE_DELETE
*/ */
public class DeleteRecipeMessage implements Message { public class DeleteRecipeMessage implements Message {
private Long recipeId; private Recipe recipe;
public DeleteRecipeMessage() {} // for jackson public DeleteRecipeMessage() {} // for jackson
public DeleteRecipeMessage(Long recipeId) { public DeleteRecipeMessage(Recipe recipe) {
this.recipeId = recipeId; this.recipe = recipe;
} }
@Override @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() { public Recipe getRecipe() {
return recipeId; return recipe;
} }
// for jackson // for jackson
public void setRecipeId(Long recipeId) { public void setRecipe(Recipe recipe) {
this.recipeId = recipeId; this.recipe = recipe;
} }
} }

View file

@ -145,10 +145,11 @@ public class RecipeController {
@DeleteMapping("/recipe/{id}") @DeleteMapping("/recipe/{id}")
public ResponseEntity<Boolean> deleteRecipe(@PathVariable Long id) { public ResponseEntity<Boolean> deleteRecipe(@PathVariable Long id) {
logger.info("DELETE /recipe/" + id + " called."); logger.info("DELETE /recipe/" + id + " called.");
if (!recipeService.delete(id)) { Optional<Recipe> recipe = recipeService.delete(id);
if (recipe.isEmpty()) {
return ResponseEntity.badRequest().build(); 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); return ResponseEntity.ok(true);
} }

View file

@ -75,11 +75,28 @@ public class RecipeService {
return Optional.of(saveWithDependencies(recipe)); 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<Recipe> delete(Long id) {
// TODO: Propagate deletion to ingredients. // TODO: Propagate deletion to ingredients.
if (!recipeRepository.existsById(id)) return false; Optional<Recipe> 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); recipeRepository.deleteById(id);
return true; return Optional.of(copy);
} }
private Recipe saveWithDependencies(Recipe recipe) { private Recipe saveWithDependencies(Recipe recipe) {