From 8e24e47813886133f623927c303c5de0cffe0fbe Mon Sep 17 00:00:00 2001 From: Natalia Cholewa Date: Thu, 4 Dec 2025 22:52:14 +0100 Subject: [PATCH] feat: topics, recipe messages --- commons/src/main/java/commons/ws/Topics.java | 5 +++ .../ws/messages/CreateRecipeMessage.java | 30 +++++++++++++ .../ws/messages/DeleteRecipeMessage.java | 28 ++++++++++++ .../java/commons/ws/messages/Message.java | 44 +++++++++++++++++++ .../ws/messages/UpdateRecipeMessage.java | 30 +++++++++++++ .../java/server/api/RecipeController.java | 23 +++++++--- .../java/server/api/RecipeControllerTest.java | 7 ++- 7 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 commons/src/main/java/commons/ws/Topics.java create mode 100644 commons/src/main/java/commons/ws/messages/CreateRecipeMessage.java create mode 100644 commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java create mode 100644 commons/src/main/java/commons/ws/messages/Message.java create mode 100644 commons/src/main/java/commons/ws/messages/UpdateRecipeMessage.java diff --git a/commons/src/main/java/commons/ws/Topics.java b/commons/src/main/java/commons/ws/Topics.java new file mode 100644 index 0000000..9eb8d9f --- /dev/null +++ b/commons/src/main/java/commons/ws/Topics.java @@ -0,0 +1,5 @@ +package commons.ws; + +public class Topics { + public static final String RECIPES = "/subscribe/recipe"; +} diff --git a/commons/src/main/java/commons/ws/messages/CreateRecipeMessage.java b/commons/src/main/java/commons/ws/messages/CreateRecipeMessage.java new file mode 100644 index 0000000..b036c1f --- /dev/null +++ b/commons/src/main/java/commons/ws/messages/CreateRecipeMessage.java @@ -0,0 +1,30 @@ +package commons.ws.messages; + +import commons.Recipe; + +/** + * Message sent when a new recipe is created. + * + * @see commons.ws.messages.Message.Type#RECIPE_CREATE + */ +public class CreateRecipeMessage implements Message { + private Recipe recipe; + + public CreateRecipeMessage(Recipe recipe) { + this.recipe = recipe; + } + + @Override + public Type getType() { + return Type.RECIPE_CREATE; + } + + /** + * Get the created recipe. + * + * @return The created recipe. + */ + public Recipe getRecipe() { + return recipe; + } +} diff --git a/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java b/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java new file mode 100644 index 0000000..1802525 --- /dev/null +++ b/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java @@ -0,0 +1,28 @@ +package commons.ws.messages; + +/** + * Message sent when a recipe is deleted. + * + * @see commons.ws.messages.Message.Type#RECIPE_DELETE + */ +public class DeleteRecipeMessage implements Message { + private Long recipeId; + + public DeleteRecipeMessage(Long recipeId) { + this.recipeId = recipeId; + } + + @Override + public Type getType() { + return Type.RECIPE_DELETE; + } + + /** + * Get the ID of the deleted recipe. + * + * @return The ID of the deleted recipe. + */ + public Long getRecipeId() { + return recipeId; + } +} diff --git a/commons/src/main/java/commons/ws/messages/Message.java b/commons/src/main/java/commons/ws/messages/Message.java new file mode 100644 index 0000000..681474f --- /dev/null +++ b/commons/src/main/java/commons/ws/messages/Message.java @@ -0,0 +1,44 @@ +package commons.ws.messages; + +public interface Message { + public enum Type { + /** + * Message sent when a new recipe is created. + * + * @see commons.ws.messages.CreateRecipeMessage + */ + RECIPE_CREATE, + + /** + * Message sent when an existing recipe is updated. + * + * @see commons.ws.messages.UpdateRecipeMessage + */ + RECIPE_UPDATE, + + /** + * Message sent when a recipe is deleted. + * + * @see commons.ws.messages.DeleteRecipeMessage + */ + RECIPE_DELETE + } + + /** + * Get the type of the message. + * This can be used to match the message to the appropriate handler. + * + *

Example

+ *
+     * Message msg = ...;
+     * switch (msg.getType()) {
+     *     case RECIPE_CREATE -> handleCreate((CreateRecipeMessage) msg);
+     *     case RECIPE_UPDATE -> handleUpdate((UpdateRecipeMessage) msg);
+     *     default -> { /* handle other cases *\/ }
+     * }
+     * 
+ * + * @return The type of the message. + */ + public Type getType(); +} diff --git a/commons/src/main/java/commons/ws/messages/UpdateRecipeMessage.java b/commons/src/main/java/commons/ws/messages/UpdateRecipeMessage.java new file mode 100644 index 0000000..c623b75 --- /dev/null +++ b/commons/src/main/java/commons/ws/messages/UpdateRecipeMessage.java @@ -0,0 +1,30 @@ +package commons.ws.messages; + +import commons.Recipe; + +/** + * Message sent when an existing recipe is updated. + * + * @see commons.ws.messages.Message.Type#RECIPE_UPDATE + */ +public class UpdateRecipeMessage implements Message { + private Recipe recipe; + + public UpdateRecipeMessage(Recipe recipe) { + this.recipe = recipe; + } + + @Override + public Type getType() { + return Type.RECIPE_UPDATE; + } + + /** + * Get the updated recipe. + * + * @return The updated recipe. + */ + public Recipe getRecipe() { + return recipe; + } +} diff --git a/server/src/main/java/server/api/RecipeController.java b/server/src/main/java/server/api/RecipeController.java index a1d79fe..989f9f0 100644 --- a/server/src/main/java/server/api/RecipeController.java +++ b/server/src/main/java/server/api/RecipeController.java @@ -2,10 +2,15 @@ package server.api; import commons.Recipe; +import commons.ws.Topics; +import commons.ws.messages.CreateRecipeMessage; +import commons.ws.messages.DeleteRecipeMessage; +import commons.ws.messages.UpdateRecipeMessage; import org.springframework.data.domain.Example; import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -25,9 +30,11 @@ import java.util.Optional; @RequestMapping("/api") public class RecipeController { private final RecipeRepository recipeRepository; // JPA repository used in this controller + private final SimpMessagingTemplate messagingTemplate; - public RecipeController(RecipeRepository recipeRepository) { + public RecipeController(RecipeRepository recipeRepository, SimpMessagingTemplate messagingTemplate) { this.recipeRepository = recipeRepository; + this.messagingTemplate = messagingTemplate; } /** @@ -61,6 +68,7 @@ public class RecipeController { PageRequest.of(0, limit.get()) ).toList()); } + return ResponseEntity.ok(recipeRepository.findAll()); } @@ -76,9 +84,10 @@ public class RecipeController { return ResponseEntity.badRequest().build(); } - // TODO: Send WS update to all subscribers with the updated recipe + Recipe saved = recipeRepository.save(recipe); + messagingTemplate.convertAndSend(Topics.RECIPES, new UpdateRecipeMessage(saved)); - return ResponseEntity.ok(recipeRepository.save(recipe)); + return ResponseEntity.ok(saved); } /** @@ -104,9 +113,10 @@ public class RecipeController { return ResponseEntity.badRequest().build(); } - // TODO: Send WS update to all subscribers with the new recipe + Recipe saved = recipeRepository.save(recipe); + messagingTemplate.convertAndSend(Topics.RECIPES, new CreateRecipeMessage(saved)); - return ResponseEntity.ok(recipeRepository.save(recipe)); + return ResponseEntity.ok(saved); } /** @@ -124,7 +134,8 @@ public class RecipeController { } recipeRepository.deleteById(id); - // TODO: Send WS update to propagate deletion + messagingTemplate.convertAndSend(Topics.RECIPES, new DeleteRecipeMessage(id)); + return ResponseEntity.ok(true); } } diff --git a/server/src/test/java/server/api/RecipeControllerTest.java b/server/src/test/java/server/api/RecipeControllerTest.java index f1a766e..5b76a9f 100644 --- a/server/src/test/java/server/api/RecipeControllerTest.java +++ b/server/src/test/java/server/api/RecipeControllerTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.TestInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.http.HttpStatus; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.test.context.ActiveProfiles; import server.database.RecipeRepository; @@ -30,6 +31,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; // This config uses an in-memory database @ActiveProfiles("mock-data-test") public class RecipeControllerTest { + + @Autowired + private SimpMessagingTemplate template; + private RecipeController controller; private List recipes; private final RecipeRepository recipeRepository; @@ -48,7 +53,7 @@ public class RecipeControllerTest { .range(0, NUM_RECIPES) .mapToObj(x -> new Recipe(null, "Recipe " + x, List.of(), List.of())) .toList(); - controller = new RecipeController(recipeRepository); + controller = new RecipeController(recipeRepository, template); Set tags = info.getTags(); List ids = new ArrayList<>();