From 3301c75354f6478607b3c0eed35b2332e077bed3 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Tue, 16 Dec 2025 13:24:11 +0100 Subject: [PATCH 01/14] feat: init ui definition nutrition view --- .../client/scenes/FoodpalApplicationCtrl.java | 2 +- .../nutrition/NutritionDetailsCtrl.java | 38 +++++++++++ .../scenes/nutrition/NutritionViewCtrl.java | 68 +++++++++++++++++++ .../scenes/nutrition/NutritionDetails.fxml | 32 +++++++++ .../scenes/nutrition/NutritionView.fxml | 19 ++++++ 5 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java create mode 100644 client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java create mode 100644 client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml create mode 100644 client/src/main/resources/client/scenes/nutrition/NutritionView.fxml diff --git a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java index ed97139..9248bb4 100644 --- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java +++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java @@ -56,7 +56,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { public Label recipesLabel; @FXML - private ListView recipeList; + public ListView recipeList; @FXML private Button addRecipeButton; diff --git a/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java b/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java new file mode 100644 index 0000000..9a60b4a --- /dev/null +++ b/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java @@ -0,0 +1,38 @@ +package client.scenes.nutrition; + +import client.utils.LocaleAware; +import client.utils.LocaleManager; +import com.google.inject.Inject; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; + +public class NutritionDetailsCtrl implements LocaleAware { + private final LocaleManager manager; + + public Label ingredientName; + public Label fatInputLabel; + public Label proteinInputLabel; + public Label carbInputLabel; + public Label estimatedKcalLabel; + public Label usageLabel; + + public TextField fatInputElement; + public TextField proteinInputElement; + public TextField carbInputElement; + + @Inject + public NutritionDetailsCtrl( + LocaleManager manager + ) { + this.manager = manager; + } + @Override + public void updateText() { + + } + + @Override + public LocaleManager getLocaleManager() { + return manager; + } +} diff --git a/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java b/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java new file mode 100644 index 0000000..493db8e --- /dev/null +++ b/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java @@ -0,0 +1,68 @@ +package client.scenes.nutrition; + +import client.scenes.FoodpalApplicationCtrl; +import com.google.inject.Inject; +import commons.Recipe; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.control.ListView; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class NutritionViewCtrl { + private ObservableList recipes; + private HashMap ingredientStats; + public ListView nutritionIngredientsView; + private final NutritionDetailsCtrl nutritionDetailsCtrl; + + // TODO into Ingredient class definition + + /** + * Comedically verbose function to count unique appearances of an ingredient by name in each recipe. + * For each recipe: + * 1. Collect unique ingredients that appeared in that recipe. + * 2. For each unique ingredient in said recipe: + * 1. Initialize the appearance for that ingredient to 0. + * 2. For each recipe in list: + * 1. If the name of the ingredient exists in the recipe list, increment the statistic by 1. + * 2. Else maintain the same value for that statistic. + * @param recipeList + */ + private void updateIngredientStats( + List recipeList + ) { + recipeList.forEach(recipe -> { + Set uniqueIngredients = new HashSet<>(recipe.getIngredients()); + nutritionIngredientsView.getItems().setAll(uniqueIngredients); + uniqueIngredients.forEach(ingredient -> { + ingredientStats.put(ingredient, 0); + recipeList.forEach(r -> + ingredientStats.put( + ingredient, + ingredientStats.get(ingredient) + ( + (r.getIngredients().contains(ingredient)) + ? 1 : 0 + ) + ) + ); + }); + }); + } + @Inject + public NutritionViewCtrl( + FoodpalApplicationCtrl foodpalApplicationCtrl, + NutritionDetailsCtrl nutritionDetailsCtrl + ) { + this.recipes = foodpalApplicationCtrl.recipeList.getItems(); + this.recipes.addListener((ListChangeListener) _ -> { + updateIngredientStats(this.recipes); + }); + this.nutritionDetailsCtrl = nutritionDetailsCtrl; + this.nutritionIngredientsView.selectionModelProperty().addListener((observable, oldValue, newValue) -> { + + }); + } +} diff --git a/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml b/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml new file mode 100644 index 0000000..1820eb4 --- /dev/null +++ b/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/client/src/main/resources/client/scenes/nutrition/NutritionView.fxml b/client/src/main/resources/client/scenes/nutrition/NutritionView.fxml new file mode 100644 index 0000000..428694e --- /dev/null +++ b/client/src/main/resources/client/scenes/nutrition/NutritionView.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + From e41dc70f97bd77cccd5dc8cc2647027eb2e7275e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Rasie=C5=84ski?= Date: Wed, 17 Dec 2025 16:19:33 +0100 Subject: [PATCH 02/14] Modified Message and its subclasses to work nicely with jackson --- .../ws/messages/CreateIngredientMessage.java | 7 +++++++ .../commons/ws/messages/CreateRecipeMessage.java | 7 +++++++ .../ws/messages/DeleteIngredientMessage.java | 7 +++++++ .../commons/ws/messages/DeleteRecipeMessage.java | 7 +++++++ .../main/java/commons/ws/messages/Message.java | 16 ++++++++++++++++ .../ws/messages/UpdateIngredientMessage.java | 7 +++++++ .../commons/ws/messages/UpdateRecipeMessage.java | 7 +++++++ 7 files changed, 58 insertions(+) diff --git a/commons/src/main/java/commons/ws/messages/CreateIngredientMessage.java b/commons/src/main/java/commons/ws/messages/CreateIngredientMessage.java index a6392cb..8abd1b9 100644 --- a/commons/src/main/java/commons/ws/messages/CreateIngredientMessage.java +++ b/commons/src/main/java/commons/ws/messages/CreateIngredientMessage.java @@ -10,6 +10,8 @@ import commons.Ingredient; public class CreateIngredientMessage implements Message { private Ingredient ingredient; + public CreateIngredientMessage() {} // for jackson + public CreateIngredientMessage(Ingredient ingredient) { this.ingredient = ingredient; } @@ -27,4 +29,9 @@ public class CreateIngredientMessage implements Message { public Ingredient getIngredient() { return ingredient; } + + // for jackson + public void setIngredient(Ingredient ingredient) { + this.ingredient = ingredient; + } } diff --git a/commons/src/main/java/commons/ws/messages/CreateRecipeMessage.java b/commons/src/main/java/commons/ws/messages/CreateRecipeMessage.java index b036c1f..adc5b28 100644 --- a/commons/src/main/java/commons/ws/messages/CreateRecipeMessage.java +++ b/commons/src/main/java/commons/ws/messages/CreateRecipeMessage.java @@ -10,6 +10,8 @@ import commons.Recipe; public class CreateRecipeMessage implements Message { private Recipe recipe; + public CreateRecipeMessage() {} // for jackson + public CreateRecipeMessage(Recipe recipe) { this.recipe = recipe; } @@ -27,4 +29,9 @@ public class CreateRecipeMessage implements Message { public Recipe getRecipe() { return recipe; } + + // for jackson + public void setRecipe(Recipe recipe) { + this.recipe = recipe; + } } diff --git a/commons/src/main/java/commons/ws/messages/DeleteIngredientMessage.java b/commons/src/main/java/commons/ws/messages/DeleteIngredientMessage.java index 879df60..fc6985b 100644 --- a/commons/src/main/java/commons/ws/messages/DeleteIngredientMessage.java +++ b/commons/src/main/java/commons/ws/messages/DeleteIngredientMessage.java @@ -8,6 +8,8 @@ package commons.ws.messages; public class DeleteIngredientMessage implements Message { private Long ingredientId; + public DeleteIngredientMessage() {} // for jackson + public DeleteIngredientMessage(Long ingredientId) { this.ingredientId = ingredientId; } @@ -25,4 +27,9 @@ public class DeleteIngredientMessage implements Message { public Long getIngredientId() { return ingredientId; } + + // for jackson + public void setIngredientId(Long ingredientId) { + this.ingredientId = ingredientId; + } } diff --git a/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java b/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java index 1802525..8eab4e6 100644 --- a/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java +++ b/commons/src/main/java/commons/ws/messages/DeleteRecipeMessage.java @@ -8,6 +8,8 @@ package commons.ws.messages; public class DeleteRecipeMessage implements Message { private Long recipeId; + public DeleteRecipeMessage() {} // for jackson + public DeleteRecipeMessage(Long recipeId) { this.recipeId = recipeId; } @@ -25,4 +27,9 @@ public class DeleteRecipeMessage implements Message { public Long getRecipeId() { return recipeId; } + + // for jackson + public void setRecipeId(Long recipeId) { + this.recipeId = recipeId; + } } diff --git a/commons/src/main/java/commons/ws/messages/Message.java b/commons/src/main/java/commons/ws/messages/Message.java index 9e20df0..5c05d7f 100644 --- a/commons/src/main/java/commons/ws/messages/Message.java +++ b/commons/src/main/java/commons/ws/messages/Message.java @@ -1,5 +1,21 @@ package commons.ws.messages; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "type" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = CreateRecipeMessage.class, name = "RECIPE_CREATE"), + @JsonSubTypes.Type(value = UpdateRecipeMessage.class, name = "RECIPE_UPDATE"), + @JsonSubTypes.Type(value = DeleteRecipeMessage.class, name = "RECIPE_DELETE"), + @JsonSubTypes.Type(value = CreateIngredientMessage.class, name = "INGREDIENT_CREATE"), + @JsonSubTypes.Type(value = UpdateIngredientMessage.class, name = "INGREDIENT_UPDATE"), + @JsonSubTypes.Type(value = DeleteIngredientMessage.class, name = "INGREDIENT_DELETE") +}) public interface Message { public enum Type { /** diff --git a/commons/src/main/java/commons/ws/messages/UpdateIngredientMessage.java b/commons/src/main/java/commons/ws/messages/UpdateIngredientMessage.java index e997873..e45e5a5 100644 --- a/commons/src/main/java/commons/ws/messages/UpdateIngredientMessage.java +++ b/commons/src/main/java/commons/ws/messages/UpdateIngredientMessage.java @@ -10,6 +10,8 @@ import commons.Ingredient; public class UpdateIngredientMessage implements Message { private Ingredient ingredient; + public UpdateIngredientMessage() {} // for jackson + public UpdateIngredientMessage(Ingredient ingredient) { this.ingredient = ingredient; } @@ -27,4 +29,9 @@ public class UpdateIngredientMessage implements Message { public Ingredient getIngredient() { return ingredient; } + + // for jackson + public void setIngredient(Ingredient ingredient) { + this.ingredient = ingredient; + } } diff --git a/commons/src/main/java/commons/ws/messages/UpdateRecipeMessage.java b/commons/src/main/java/commons/ws/messages/UpdateRecipeMessage.java index c623b75..72c9fd6 100644 --- a/commons/src/main/java/commons/ws/messages/UpdateRecipeMessage.java +++ b/commons/src/main/java/commons/ws/messages/UpdateRecipeMessage.java @@ -10,6 +10,8 @@ import commons.Recipe; public class UpdateRecipeMessage implements Message { private Recipe recipe; + public UpdateRecipeMessage() {} // for jackson + public UpdateRecipeMessage(Recipe recipe) { this.recipe = recipe; } @@ -27,4 +29,9 @@ public class UpdateRecipeMessage implements Message { public Recipe getRecipe() { return recipe; } + + // for jackson + public void setRecipe(Recipe recipe) { + this.recipe = recipe; + } } From 3da89c386b7595502b00ceeda17bca810c85383f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Rasie=C5=84ski?= Date: Wed, 17 Dec 2025 16:26:27 +0100 Subject: [PATCH 03/14] Created utils for websockets. --- client/pom.xml | 27 +++++ client/src/main/java/client/MyModule.java | 5 + .../java/client/utils/WebSocketUtils.java | 111 ++++++++++++++++++ commons/pom.xml | 6 + 4 files changed, 149 insertions(+) create mode 100644 client/src/main/java/client/utils/WebSocketUtils.java diff --git a/client/pom.xml b/client/pom.xml index d48d069..3bd793a 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -34,21 +34,25 @@ jersey-client ${version.jersey} + org.glassfish.jersey.inject jersey-hk2 ${version.jersey} + org.glassfish.jersey.media jersey-media-json-jackson ${version.jersey} + jakarta.activation jakarta.activation-api 2.1.3 + com.google.inject guice @@ -61,11 +65,13 @@ javafx-fxml ${version.jfx} + org.openjfx javafx-controls ${version.jfx} + org.openjfx javafx-web @@ -91,6 +97,27 @@ ${version.mockito} test + + + + org.springframework + spring-messaging + 6.2.12 + compile + + + + org.glassfish.tyrus.bundles + tyrus-standalone-client + 2.2.1 + + + org.springframework + spring-websocket + 6.2.12 + compile + + diff --git a/client/src/main/java/client/MyModule.java b/client/src/main/java/client/MyModule.java index 4f3464e..21da1dc 100644 --- a/client/src/main/java/client/MyModule.java +++ b/client/src/main/java/client/MyModule.java @@ -19,6 +19,8 @@ import client.scenes.FoodpalApplicationCtrl; import client.scenes.recipe.IngredientListCtrl; import client.scenes.recipe.RecipeStepListCtrl; import client.utils.LocaleManager; +import client.utils.ServerUtils; +import client.utils.WebSocketUtils; import com.google.inject.Binder; import com.google.inject.Module; import com.google.inject.Scopes; @@ -33,6 +35,9 @@ public class MyModule implements Module { binder.bind(FoodpalApplicationCtrl.class).in(Scopes.SINGLETON); binder.bind(IngredientListCtrl.class).in(Scopes.SINGLETON); binder.bind(RecipeStepListCtrl.class).in(Scopes.SINGLETON); + binder.bind(LocaleManager.class).in(Scopes.SINGLETON); + binder.bind(ServerUtils.class).in(Scopes.SINGLETON); + binder.bind(WebSocketUtils.class).in(Scopes.SINGLETON); } } \ No newline at end of file diff --git a/client/src/main/java/client/utils/WebSocketUtils.java b/client/src/main/java/client/utils/WebSocketUtils.java new file mode 100644 index 0000000..ed16b37 --- /dev/null +++ b/client/src/main/java/client/utils/WebSocketUtils.java @@ -0,0 +1,111 @@ +package client.utils; + +import commons.ws.messages.Message; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.simp.stomp.StompSession; +import org.springframework.messaging.simp.stomp.StompHeaders; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompFrameHandler; +import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.messaging.WebSocketStompClient; + +import java.lang.reflect.Type; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import java.util.concurrent.CompletableFuture; + +public class WebSocketUtils { + private static final String WS_URL = "ws://localhost:8080/updates"; + private WebSocketStompClient stompClient; + private StompSession stompSession; + + /** + * Connect to the websocket server. + * @param onConnected OnConnected callback + */ + public void connect(@Nullable Runnable onConnected) { + StandardWebSocketClient webSocketClient = new StandardWebSocketClient(); // Create WS Client + + stompClient = new WebSocketStompClient(webSocketClient); + stompClient.setMessageConverter(new MappingJackson2MessageConverter()); // Use jackson + + CompletableFuture sessionFuture = stompClient.connectAsync( + WS_URL, + new StompSessionHandlerAdapter() { + @Override + public void afterConnected(StompSession session, StompHeaders connectedHeaders) { + stompSession = session; + System.out.println("WebSocket connected: " + 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(); + } + + @Override + public void handleTransportError(StompSession session, Throwable exception) { + System.err.println("STOMP transport error: " + exception.getMessage()); + exception.printStackTrace(); + } + } + ); + + sessionFuture.thenAccept(session -> { + stompSession = session; + System.out.println("Connection successful, session ready"); + if (onConnected != null) { + onConnected.run(); + } + }).exceptionally(throwable -> { + System.err.println("Failed to connect: " + throwable.getMessage()); + throwable.printStackTrace(); + return null; + }); + } + + /** + * Subscribe to a topic. + * @param destination Destination to subscribe to, for example: /subscribe/recipe + * @param messageHandler Handler for received messages + */ + public void subscribe(String destination, Consumer messageHandler) { + if (stompSession == null || !stompSession.isConnected()) { + System.err.println("Cannot subscribe - not connected"); + return; + } + + stompSession.subscribe(destination, new StompFrameHandler() { + @Override + public Type getPayloadType(StompHeaders headers) { + return Message.class; + } + + @Override + public void handleFrame(StompHeaders headers, @Nullable Object payload) { + Message message = (Message) payload; + messageHandler.accept(message); + } + }); + System.out.println("Subscribed to: " + destination); + } + + public void disconnect() { + if (stompSession != null && stompSession.isConnected()) { + stompSession.disconnect(); + } + + if (stompClient != null) { + stompClient.stop(); + } + } + + public boolean isConnected() { + return stompSession != null && stompSession.isConnected(); + } + +} diff --git a/commons/pom.xml b/commons/pom.xml index e5d5ff4..f48fbcf 100644 --- a/commons/pom.xml +++ b/commons/pom.xml @@ -45,6 +45,12 @@ ${version.mockito} test + + com.fasterxml.jackson.core + jackson-databind + 2.20.1 + compile + From 8bceffa67c0a28dbe4a45473a48262ce2aac9ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Rasie=C5=84ski?= Date: Wed, 17 Dec 2025 16:50:30 +0100 Subject: [PATCH 04/14] Implemented websockets in foodpal ctrl. --- .../client/scenes/FoodpalApplicationCtrl.java | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java index 0b3aeba..2cb8d85 100644 --- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java +++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java @@ -13,10 +13,14 @@ import client.utils.DefaultRecipeFactory; import client.utils.LocaleAware; import client.utils.LocaleManager; import client.utils.ServerUtils; +import client.utils.WebSocketUtils; import commons.Recipe; +import commons.ws.Topics; +import commons.ws.messages.Message; import jakarta.inject.Inject; +import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Alert; @@ -32,6 +36,7 @@ import javafx.scene.text.Font; public class FoodpalApplicationCtrl implements LocaleAware { private final ServerUtils server; + private final WebSocketUtils webSocketUtils; private final LocaleManager localeManager; private final IngredientListCtrl ingredientListCtrl; private final RecipeStepListCtrl stepListCtrl; @@ -81,15 +86,42 @@ public class FoodpalApplicationCtrl implements LocaleAware { @Inject public FoodpalApplicationCtrl( ServerUtils server, + WebSocketUtils webSocketUtils, LocaleManager localeManager, IngredientListCtrl ingredientListCtrl, RecipeStepListCtrl stepListCtrl ) { this.server = server; + this.webSocketUtils = webSocketUtils; this.localeManager = localeManager; this.ingredientListCtrl = ingredientListCtrl; this.stepListCtrl = stepListCtrl; + + initializeWebSocket(); } + + private void initializeWebSocket() { + webSocketUtils.connect(() -> { + webSocketUtils.subscribe(Topics.RECIPES, (Message _) -> { + Platform.runLater(() -> { + Recipe selectedRecipe = recipeList.getSelectionModel().getSelectedItem(); + refresh(); // refresh the left list + if (selectedRecipe == null) { + return; + } + + // select last selected recipe if it still exists, first otherwise (done by refresh()) + Recipe recipeInList = recipeList.getItems().stream() + .filter(r -> r.getId().equals(selectedRecipe.getId())) + .findFirst() + .orElse(null); + + showRecipeDetails(recipeInList); + }); // runLater as it's on another non-FX thread. + }); + }); + } + @Override public void initializeComponents() { // TODO Reduce code duplication?? @@ -146,6 +178,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { refresh(); } + private void showName(String name) { final int NAME_FONT_SIZE = 20; editableTitleArea.getChildren().clear(); @@ -153,6 +186,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { nameLabel.setFont(new Font("System Bold", NAME_FONT_SIZE)); editableTitleArea.getChildren().add(nameLabel); } + @Override public void updateText() { addRecipeButton.setText(getLocaleString("menu.button.add.recipe")); @@ -206,11 +240,13 @@ public class FoodpalApplicationCtrl implements LocaleAware { } detailsScreen.visibleProperty().set(!recipes.isEmpty()); } + private void printError(String msg) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setContentText(msg); alert.showAndWait(); } + /** * Adds a recipe, by providing a default name "Untitled recipe (n)" * The UX flow is now: @@ -283,7 +319,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { try { server.updateRecipe(selected); refresh(); - recipeList.getSelectionModel().select(selected); + //recipeList.getSelectionModel().select(selected); } catch (IOException | InterruptedException e) { // throw a nice blanket UpdateException throw new UpdateException("Error occurred when updating recipe name!"); @@ -294,7 +330,6 @@ public class FoodpalApplicationCtrl implements LocaleAware { edit.requestFocus(); } - // Language buttons @FXML private void switchLocale(ActionEvent event) { From a5ec17e486b42a1c9d2a67ffcd5ff5e484fbe71b Mon Sep 17 00:00:00 2001 From: Mei Chang van der Werff Date: Thu, 18 Dec 2025 03:25:12 +0100 Subject: [PATCH 05/14] created FavouriteRecipeMessage class --- .../ws/messages/FavouriteRecipeMessage.java | 28 +++++++++++++++++++ .../java/commons/ws/messages/Message.java | 9 ++++++ 2 files changed, 37 insertions(+) create mode 100644 commons/src/main/java/commons/ws/messages/FavouriteRecipeMessage.java diff --git a/commons/src/main/java/commons/ws/messages/FavouriteRecipeMessage.java b/commons/src/main/java/commons/ws/messages/FavouriteRecipeMessage.java new file mode 100644 index 0000000..127eaeb --- /dev/null +++ b/commons/src/main/java/commons/ws/messages/FavouriteRecipeMessage.java @@ -0,0 +1,28 @@ +package commons.ws.messages; + +/** + * Message sent when a recipe becomes a favourite. + * @see commons.ws.messages.Message.Type#RECIPE_FAVOURITE + */ +public class FavouriteRecipeMessage implements Message{ + + private long recipeId; + + public FavouriteRecipeMessage(long recipeId) { + this.recipeId = recipeId; + } + + @Override + public Type getType() { + return Type.RECIPE_FAVOURITE; + } + + /** + * gets the ID of the recipe that got favourite. + * @return + */ + 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 index 9e20df0..ce494e9 100644 --- a/commons/src/main/java/commons/ws/messages/Message.java +++ b/commons/src/main/java/commons/ws/messages/Message.java @@ -23,6 +23,15 @@ public interface Message { */ RECIPE_DELETE, + /** + * Message sent when a recipe became a favourite. + * + * @see commons.ws.messages.FavouriteRecipeMessage + */ + + RECIPE_FAVOURITE, + + /** * Message sent when a new ingredient is created. * From a45f31abededb8cfa840c1e906ba7be35e30c25c Mon Sep 17 00:00:00 2001 From: Mei Chang van der Werff Date: Thu, 18 Dec 2025 03:44:01 +0100 Subject: [PATCH 06/14] Removed all left over example code --- .../java/client/utils/ServerUtilsExample.java | 72 ------ commons/src/main/java/commons/Person.java | 63 ----- commons/src/main/java/commons/Quote.java | 66 ----- commons/src/test/java/commons/PersonTest.java | 56 ----- commons/src/test/java/commons/QuoteTest.java | 58 ----- .../server/api/PersonListingController.java | 52 ---- .../main/java/server/api/QuoteController.java | 79 ------ .../java/server/database/QuoteRepository.java | 22 -- .../api/PersonListingControllerTest.java | 53 ----- .../java/server/api/QuoteControllerTest.java | 83 ------- .../java/server/api/TestQuoteRepository.java | 225 ------------------ 11 files changed, 829 deletions(-) delete mode 100644 client/src/main/java/client/utils/ServerUtilsExample.java delete mode 100644 commons/src/main/java/commons/Person.java delete mode 100644 commons/src/main/java/commons/Quote.java delete mode 100644 commons/src/test/java/commons/PersonTest.java delete mode 100644 commons/src/test/java/commons/QuoteTest.java delete mode 100644 server/src/main/java/server/api/PersonListingController.java delete mode 100644 server/src/main/java/server/api/QuoteController.java delete mode 100644 server/src/main/java/server/database/QuoteRepository.java delete mode 100644 server/src/test/java/server/api/PersonListingControllerTest.java delete mode 100644 server/src/test/java/server/api/QuoteControllerTest.java delete mode 100644 server/src/test/java/server/api/TestQuoteRepository.java diff --git a/client/src/main/java/client/utils/ServerUtilsExample.java b/client/src/main/java/client/utils/ServerUtilsExample.java deleted file mode 100644 index bc4a187..0000000 --- a/client/src/main/java/client/utils/ServerUtilsExample.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package client.utils; - -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; - -import java.net.ConnectException; -import java.util.List; - -import jakarta.ws.rs.core.GenericType; -import org.glassfish.jersey.client.ClientConfig; - -import commons.Quote; -import jakarta.ws.rs.ProcessingException; -import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.Entity; - -public class ServerUtilsExample { - - private static final String SERVER = "http://localhost:8080/"; - -// public void getQuotesTheHardWay() throws IOException, URISyntaxException { -// var url = new URI("http://localhost:8080/api/quotes").toURL(); -// var is = url.openConnection().getInputStream(); -// var br = new BufferedReader(new InputStreamReader(is)); -// String line; -// while ((line = br.readLine()) != null) { -// System.out.println(line); -// } -// } - - public List getQuotes() { - return ClientBuilder.newClient(new ClientConfig()) // - .target(SERVER).path("api/quotes") // - .request(APPLICATION_JSON) // - .get(new GenericType>() {}); - } - - public Quote addQuote(Quote quote) { - return ClientBuilder.newClient(new ClientConfig()) // - .target(SERVER).path("api/quotes") // - .request(APPLICATION_JSON) // - .post(Entity.entity(quote, APPLICATION_JSON), Quote.class); - } - - public boolean isServerAvailable() { - try { - ClientBuilder.newClient(new ClientConfig()) // - .target(SERVER) // - .request(APPLICATION_JSON) // - .get(); - } catch (ProcessingException e) { - if (e.getCause() instanceof ConnectException) { - return false; - } - } - return true; - } -} \ No newline at end of file diff --git a/commons/src/main/java/commons/Person.java b/commons/src/main/java/commons/Person.java deleted file mode 100644 index f7d2d31..0000000 --- a/commons/src/main/java/commons/Person.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package commons; - -import static org.apache.commons.lang3.builder.ToStringStyle.MULTI_LINE_STYLE; - -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.commons.lang3.builder.ToStringBuilder; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; - -@Entity -public class Person { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - public long id; - - public String firstName; - public String lastName; - - @SuppressWarnings("unused") - private Person() { - // for object mapper - } - - public Person(String firstName, String lastName) { - this.firstName = firstName; - this.lastName = lastName; - } - - @Override - public boolean equals(Object obj) { - return EqualsBuilder.reflectionEquals(this, obj); - } - - @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); - } - - @Override - public String toString() { - return ToStringBuilder.reflectionToString(this, MULTI_LINE_STYLE); - } -} \ No newline at end of file diff --git a/commons/src/main/java/commons/Quote.java b/commons/src/main/java/commons/Quote.java deleted file mode 100644 index 60cb27d..0000000 --- a/commons/src/main/java/commons/Quote.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package commons; - -import static org.apache.commons.lang3.builder.ToStringStyle.MULTI_LINE_STYLE; - -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.commons.lang3.builder.ToStringBuilder; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToOne; - -@Entity -public class Quote { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - public long id; - - @OneToOne(cascade = CascadeType.PERSIST) - public Person person; - public String quote; - - @SuppressWarnings("unused") - private Quote() { - // for object mappers - } - - public Quote(Person person, String quote) { - this.person = person; - this.quote = quote; - } - - @Override - public boolean equals(Object obj) { - return EqualsBuilder.reflectionEquals(this, obj); - } - - @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); - } - - @Override - public String toString() { - return ToStringBuilder.reflectionToString(this, MULTI_LINE_STYLE); - } -} \ No newline at end of file diff --git a/commons/src/test/java/commons/PersonTest.java b/commons/src/test/java/commons/PersonTest.java deleted file mode 100644 index 93c545c..0000000 --- a/commons/src/test/java/commons/PersonTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package commons; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -public class PersonTest { - - @Test - public void checkConstructor() { - var p = new Person("f", "l"); - assertEquals("f", p.firstName); - assertEquals("l", p.lastName); - } - - @Test - public void equalsHashCode() { - var a = new Person("a", "b"); - var b = new Person("a", "b"); - assertEquals(a, b); - assertEquals(a.hashCode(), b.hashCode()); - } - - @Test - public void notEqualsHashCode() { - var a = new Person("a", "b"); - var b = new Person("a", "c"); - assertNotEquals(a, b); - assertNotEquals(a.hashCode(), b.hashCode()); - } - - @Test - public void hasToString() { - var actual = new Person("a", "b").toString(); - assertTrue(actual.contains(Person.class.getSimpleName())); - assertTrue(actual.contains("\n")); - assertTrue(actual.contains("firstName")); - } -} \ No newline at end of file diff --git a/commons/src/test/java/commons/QuoteTest.java b/commons/src/test/java/commons/QuoteTest.java deleted file mode 100644 index 85ee201..0000000 --- a/commons/src/test/java/commons/QuoteTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package commons; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -public class QuoteTest { - - private static final Person SOME_PERSON = new Person("a", "b"); - - @Test - public void checkConstructor() { - var q = new Quote(SOME_PERSON, "q"); - assertEquals(SOME_PERSON, q.person); - assertEquals("q", q.quote); - } - - @Test - public void equalsHashCode() { - var a = new Quote(new Person("a", "b"), "c"); - var b = new Quote(new Person("a", "b"), "c"); - assertEquals(a, b); - assertEquals(a.hashCode(), b.hashCode()); - } - - @Test - public void notEqualsHashCode() { - var a = new Quote(new Person("a", "b"), "c"); - var b = new Quote(new Person("a", "b"), "d"); - assertNotEquals(a, b); - assertNotEquals(a.hashCode(), b.hashCode()); - } - - @Test - public void hasToString() { - var actual = new Quote(new Person("a", "b"), "c").toString(); - assertTrue(actual.contains(Quote.class.getSimpleName())); - assertTrue(actual.contains("\n")); - assertTrue(actual.contains("person")); - } -} \ No newline at end of file diff --git a/server/src/main/java/server/api/PersonListingController.java b/server/src/main/java/server/api/PersonListingController.java deleted file mode 100644 index ea06a74..0000000 --- a/server/src/main/java/server/api/PersonListingController.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2024 Sebastian Proksch - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package server.api; - -import java.util.LinkedList; -import java.util.List; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import commons.Person; - -@RestController -@RequestMapping("/api/people") -public class PersonListingController { - - private List people = new LinkedList<>(); - - public PersonListingController() { - people.add(new Person("Mickey", "Mouse")); - people.add(new Person("Donald", "Duck")); - } - - @GetMapping("/") - public List list() { - return people; - } - - @PostMapping("/") - public List add(@RequestBody Person p) { - if (!people.contains(p)) { - people.add(p); - } - return people; - } -} \ No newline at end of file diff --git a/server/src/main/java/server/api/QuoteController.java b/server/src/main/java/server/api/QuoteController.java deleted file mode 100644 index f68960e..0000000 --- a/server/src/main/java/server/api/QuoteController.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server.api; - -import java.util.List; -import java.util.Random; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import commons.Quote; -import server.database.QuoteRepository; - -@RestController -@RequestMapping("/api/quotes") -public class QuoteController { - - private final Random random; - private final QuoteRepository repo; - - public QuoteController(Random random, QuoteRepository repo) { - this.random = random; - this.repo = repo; - } - - @GetMapping(path = { "", "/" }) - public List getAll() { - return repo.findAll(); - } - - @GetMapping("/{id}") - public ResponseEntity getById(@PathVariable("id") long id) { - if (id < 0 || !repo.existsById(id)) { - return ResponseEntity.badRequest().build(); - } - return ResponseEntity.ok(repo.findById(id).get()); - } - - @PostMapping(path = { "", "/" }) - public ResponseEntity add(@RequestBody Quote quote) { - - if (quote.person == null || isNullOrEmpty(quote.person.firstName) || isNullOrEmpty(quote.person.lastName) - || isNullOrEmpty(quote.quote)) { - return ResponseEntity.badRequest().build(); - } - - Quote saved = repo.save(quote); - return ResponseEntity.ok(saved); - } - - private static boolean isNullOrEmpty(String s) { - return s == null || s.isEmpty(); - } - - @GetMapping("rnd") - public ResponseEntity getRandom() { - var quotes = repo.findAll(); - var idx = random.nextInt((int) repo.count()); - return ResponseEntity.ok(quotes.get(idx)); - } -} \ No newline at end of file diff --git a/server/src/main/java/server/database/QuoteRepository.java b/server/src/main/java/server/database/QuoteRepository.java deleted file mode 100644 index 2dd2c36..0000000 --- a/server/src/main/java/server/database/QuoteRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server.database; - -import org.springframework.data.jpa.repository.JpaRepository; - -import commons.Quote; - -public interface QuoteRepository extends JpaRepository {} \ No newline at end of file diff --git a/server/src/test/java/server/api/PersonListingControllerTest.java b/server/src/test/java/server/api/PersonListingControllerTest.java deleted file mode 100644 index 97b15fe..0000000 --- a/server/src/test/java/server/api/PersonListingControllerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2024 Sebastian Proksch - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package server.api; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import commons.Person; - -public class PersonListingControllerTest { - - private static final Person MICKEY = new Person("Mickey", "Mouse"); - private static final Person DONALD = new Person("Donald", "Duck"); - private static final Person SCROOGE = new Person("Scrooge", "McDuck"); - - private PersonListingController sut; - - @BeforeEach - public void setup() { - sut = new PersonListingController(); - } - - @Test - public void containsTwoDefaultNames() { - var actual = sut.list(); - var expected = List.of(MICKEY, DONALD); - assertEquals(expected, actual); - } - - @Test - public void canAddPeople() { - var actual = sut.add(SCROOGE); - var expected = List.of(MICKEY, DONALD, SCROOGE); - assertEquals(expected, actual); - } -} \ No newline at end of file diff --git a/server/src/test/java/server/api/QuoteControllerTest.java b/server/src/test/java/server/api/QuoteControllerTest.java deleted file mode 100644 index 12654da..0000000 --- a/server/src/test/java/server/api/QuoteControllerTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server.api; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.springframework.http.HttpStatus.BAD_REQUEST; - -import java.util.Random; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import commons.Person; -import commons.Quote; - -public class QuoteControllerTest { - - public int nextInt; - private MyRandom random; - private TestQuoteRepository repo; - - private QuoteController sut; - - @BeforeEach - public void setup() { - random = new MyRandom(); - repo = new TestQuoteRepository(); - sut = new QuoteController(random, repo); - } - - @Test - public void cannotAddNullPerson() { - var actual = sut.add(getQuote(null)); - assertEquals(BAD_REQUEST, actual.getStatusCode()); - } - - @Test - public void randomSelection() { - sut.add(getQuote("q1")); - sut.add(getQuote("q2")); - nextInt = 1; - var actual = sut.getRandom(); - - assertTrue(random.wasCalled); - assertEquals("q2", actual.getBody().quote); - } - - @Test - public void databaseIsUsed() { - sut.add(getQuote("q1")); - repo.calledMethods.contains("save"); - } - - private static Quote getQuote(String q) { - return new Quote(new Person(q, q), q); - } - - @SuppressWarnings("serial") - public class MyRandom extends Random { - - public boolean wasCalled = false; - - @Override - public int nextInt(int bound) { - wasCalled = true; - return nextInt; - } - } -} \ No newline at end of file diff --git a/server/src/test/java/server/api/TestQuoteRepository.java b/server/src/test/java/server/api/TestQuoteRepository.java deleted file mode 100644 index 9c605bd..0000000 --- a/server/src/test/java/server/api/TestQuoteRepository.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server.api; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -import org.springframework.data.domain.Example; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; - -import commons.Quote; -import server.database.QuoteRepository; - -public class TestQuoteRepository implements QuoteRepository { - - public final List quotes = new ArrayList<>(); - public final List calledMethods = new ArrayList<>(); - - private void call(String name) { - calledMethods.add(name); - } - - @Override - public List findAll() { - calledMethods.add("findAll"); - return quotes; - } - - @Override - public List findAll(Sort sort) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List findAllById(Iterable ids) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List saveAll(Iterable entities) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void flush() { - // TODO Auto-generated method stub - - } - - @Override - public S saveAndFlush(S entity) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List saveAllAndFlush(Iterable entities) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void deleteAllInBatch(Iterable entities) { - // TODO Auto-generated method stub - - } - - @Override - public void deleteAllByIdInBatch(Iterable ids) { - // TODO Auto-generated method stub - - } - - @Override - public void deleteAllInBatch() { - // TODO Auto-generated method stub - - } - - @Override - public Quote getOne(Long id) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Quote getById(Long id) { - call("getById"); - return find(id).get(); - } - - @Override - public Quote getReferenceById(Long id) { - call("getReferenceById"); - return find(id).get(); - } - - private Optional find(Long id) { - return quotes.stream().filter(q -> q.id == id).findFirst(); - } - - @Override - public List findAll(Example example) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List findAll(Example example, Sort sort) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Page findAll(Pageable pageable) { - // TODO Auto-generated method stub - return null; - } - - @Override - public S save(S entity) { - call("save"); - entity.id = (long) quotes.size(); - quotes.add(entity); - return entity; - } - - @Override - public Optional findById(Long id) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean existsById(Long id) { - call("existsById"); - return find(id).isPresent(); - } - - @Override - public long count() { - return quotes.size(); - } - - @Override - public void deleteById(Long id) { - // TODO Auto-generated method stub - - } - - @Override - public void delete(Quote entity) { - // TODO Auto-generated method stub - - } - - @Override - public void deleteAllById(Iterable ids) { - // TODO Auto-generated method stub - - } - - @Override - public void deleteAll(Iterable entities) { - // TODO Auto-generated method stub - - } - - @Override - public void deleteAll() { - // TODO Auto-generated method stub - - } - - @Override - public Optional findOne(Example example) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Page findAll(Example example, Pageable pageable) { - // TODO Auto-generated method stub - return null; - } - - @Override - public long count(Example example) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public boolean exists(Example example) { - // TODO Auto-generated method stub - return false; - } - - @Override - public R findBy(Example example, Function, R> queryFunction) { - // TODO Auto-generated method stub - return null; - } -} \ No newline at end of file From d752dfbec037ee89f1b883585ac5583bdb6d8074 Mon Sep 17 00:00:00 2001 From: Aysegul Aydinlik Date: Thu, 18 Dec 2025 13:25:20 +0100 Subject: [PATCH 07/14] added client side recipe favouriting --- client/src/main/java/client/utils/Config.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/client/src/main/java/client/utils/Config.java b/client/src/main/java/client/utils/Config.java index c94dd4c..97eaca1 100644 --- a/client/src/main/java/client/utils/Config.java +++ b/client/src/main/java/client/utils/Config.java @@ -12,7 +12,8 @@ public class Config { private List favourites = new ArrayList<>(); private List shoppingList = new ArrayList<>(); - public Config(){} + public Config() { + } public String getLanguage() { return language; @@ -22,10 +23,6 @@ public class Config { return shoppingList; } - public List getFavourites() { - return favourites; - } - public String getServerUrl() { return serverUrl; } @@ -45,4 +42,29 @@ public class Config { public void setShoppingList(List shoppingList) { this.shoppingList = shoppingList; } + + // favourite helper + + public List getFavourites() { + if (favourites == null) { + favourites = new ArrayList<>(); + } + return favourites; + } +// to avoid null pointers. + + public boolean isFavourite(long recipeId) { + return getFavourites().contains(recipeId); + } + + public void addFavourite(long recipeId) { + if (!getFavourites().contains(recipeId)) { + getFavourites().add(recipeId); + } + } + + public void removeFavourite(long recipeId) { + getFavourites().remove(recipeId); + } } + From 7f82d81b8c2619d4d1f00953217be5c7b97e7d2c Mon Sep 17 00:00:00 2001 From: Rithvik Sriram Date: Thu, 18 Dec 2025 14:27:02 +0100 Subject: [PATCH 08/14] Edit agenda-05.md --- docs/agenda/agenda-05.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/agenda/agenda-05.md b/docs/agenda/agenda-05.md index 534eb73..ed54a3e 100644 --- a/docs/agenda/agenda-05.md +++ b/docs/agenda/agenda-05.md @@ -13,8 +13,7 @@ 2. (1 min) Any additions to the agenda? 3. (1-2 min) TA announcements? 4. (1 min) Go over TA feedback given -5. (1 min) Next meeting thursday after midterms. -6. (33 min) **Meeting content** +5. (33 min) **Meeting content** 1. (5 min) Week 6 progress in terms of completion - How far are we in terms of completion? 2. (5 min) Progress check in terms of feature/issue completion @@ -27,7 +26,7 @@ - Who does it? 5. (2 min) Designate teams. 6. (1 min) Who is the next chair and minute taker? -7. (2 min) Questions -8. (1 min) Summarize everything -9. (1 min) TA remarks +6. (2 min) Questions +7. (1 min) Summarize everything +8. (1 min) TA remarks **Max time:** ~40 minutes From 6d66304bf30904e53545782188d1de913e8d0811 Mon Sep 17 00:00:00 2001 From: Rithvik Sriram Date: Thu, 18 Dec 2025 14:32:04 +0100 Subject: [PATCH 09/14] Edit agenda-05.md --- docs/agenda/agenda-05.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/agenda/agenda-05.md b/docs/agenda/agenda-05.md index ed54a3e..aff507c 100644 --- a/docs/agenda/agenda-05.md +++ b/docs/agenda/agenda-05.md @@ -22,6 +22,7 @@ 4. (20 min) **Sprint planning week 7-8** - What needs to be done? + - Handle Backend-Server code delegation - How do we do it? - Who does it? 5. (2 min) Designate teams. From 3e47fdbb96771e0796fe65b749e940146f0db1dc Mon Sep 17 00:00:00 2001 From: Mei Chang van der Werff Date: Thu, 18 Dec 2025 14:36:04 +0100 Subject: [PATCH 10/14] big Long instead of small long --- .../main/java/commons/ws/messages/FavouriteRecipeMessage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commons/src/main/java/commons/ws/messages/FavouriteRecipeMessage.java b/commons/src/main/java/commons/ws/messages/FavouriteRecipeMessage.java index 127eaeb..9594114 100644 --- a/commons/src/main/java/commons/ws/messages/FavouriteRecipeMessage.java +++ b/commons/src/main/java/commons/ws/messages/FavouriteRecipeMessage.java @@ -6,9 +6,9 @@ package commons.ws.messages; */ public class FavouriteRecipeMessage implements Message{ - private long recipeId; + private Long recipeId; - public FavouriteRecipeMessage(long recipeId) { + public FavouriteRecipeMessage(Long recipeId) { this.recipeId = recipeId; } From cf8d4096e71e38560a8e7b8cb9f51211cc707834 Mon Sep 17 00:00:00 2001 From: Maria Dumitrescu Date: Thu, 18 Dec 2025 16:31:41 +0100 Subject: [PATCH 11/14] feedback w6 --- docs/feedback/week-06.md | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 docs/feedback/week-06.md diff --git a/docs/feedback/week-06.md b/docs/feedback/week-06.md new file mode 100644 index 0000000..9907b99 --- /dev/null +++ b/docs/feedback/week-06.md @@ -0,0 +1,68 @@ + +# Meeting feedback + +**Quick Summary Scale** + +Insufficient/Sufficient/Good/Excellent + + +#### Agenda + +Feedback: **Sufficient** + +- The agenda was not uploaded on time. Generally, the agenda should be uploaded at least 2 days before the meeting so all members can access it, but I usually look for the agenda to be uploaded at the latest a day before the meeting. +- The agenda was formatted according to the template. +- The individual points were clear. +- Try to slightly adjust the layout of the agenda to be easier to read. For example, you can have sections like Opening, Updates, Feedback, Closing etc. It is easier to follow the agenda if the points are separated based on the discussed topics. + +#### Performance of the Previous Minute Taker + +Feedback: **Good** + +- The notes have been merged in the agenda file. +- The notes are clear, but try to organize them a bit more as it is difficult to read through the notes when they are placed right next to each other. +- You can group the notes based on discussed points. +- The agreements are clear and realistic. +- Everyone was assigned to a task. For better visualization, you can add a task distribution table. +- A similar table can be used for minutes: one column for discussed topics and another column for notes. + +#### Chair performance + +Feedback: **Excellent** + +- You did a good job with starting the meeting and checking in with everyone. +- You covered all points from the agenda. +- I liked that you took the initiative and guided the team through your points, e.g. when you discussed the feedback and contribution status. +- You asked for everyone's input, which is great. Well done! +- I also liked that you did a quick summary at the end. + + +#### Attitude & Relation + +Feedback: **Good - Excellent** + +- Overall the atmosphere was constructive and positive. +- All ideas were listened to and considered by all team members. +- Everyone was active and involved in the meeting. +- Everyone took ownership of the meeting. +- Most of you contributed to the discussion, but some seemed to be more quiet this week. + + +#### Potentially Shippable Product + +Feedback: **Excellent** + +- The team presented the current state of the application. +- The application is shippable, includes all basic requirements and great progress has been made on the extensions as well. +- Progress has been made compared to last week. Keep up the good work! +- You are on a good track to create a fully working application by the end, including all extensions! :) + + +#### Work Contribution/Distribution in the Team + +Feedback: **Excellent** + +- I liked that everyone explained what they did for this week. +- Most of you reached your goals. I understand that some items are still pending to be merged. Make sure you stay on track with the tasks of this week. +- So far it feels like everyone is still contributing equally to the team. +- Try to review what you planned the previous week and check if everything was completed. \ No newline at end of file From 5feab76d94ee8df145030d14606f2d2b97f02670 Mon Sep 17 00:00:00 2001 From: Rithvik Sriram Date: Thu, 18 Dec 2025 20:44:14 +0100 Subject: [PATCH 12/14] Edit agenda-05.md --- docs/agenda/agenda-05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/agenda/agenda-05.md b/docs/agenda/agenda-05.md index aff507c..195b56c 100644 --- a/docs/agenda/agenda-05.md +++ b/docs/agenda/agenda-05.md @@ -1,4 +1,4 @@ -# Week 3 meeting agenda +# Week 6 meeting agenda | Key | Value | | ------------ |-----------------------------------------------------------------------------------------| From 7a58153cd63b30fe6f94fbcf53361c6b0c75d0db Mon Sep 17 00:00:00 2001 From: Aysegul Aydinlik Date: Thu, 18 Dec 2025 19:38:26 +0100 Subject: [PATCH 13/14] added a star for favouriting a recipe and also unfavouriting it --- client/src/main/java/client/MyModule.java | 6 ++ .../client/scenes/FoodpalApplicationCtrl.java | 57 +++++++++++++++++-- .../client/scenes/FoodpalApplication.fxml | 1 + 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/client/src/main/java/client/MyModule.java b/client/src/main/java/client/MyModule.java index 21da1dc..68ef84f 100644 --- a/client/src/main/java/client/MyModule.java +++ b/client/src/main/java/client/MyModule.java @@ -18,6 +18,7 @@ package client; import client.scenes.FoodpalApplicationCtrl; import client.scenes.recipe.IngredientListCtrl; import client.scenes.recipe.RecipeStepListCtrl; +import client.utils.ConfigService; import client.utils.LocaleManager; import client.utils.ServerUtils; import client.utils.WebSocketUtils; @@ -27,6 +28,8 @@ import com.google.inject.Scopes; import client.scenes.MainCtrl; +import java.nio.file.Path; + public class MyModule implements Module { @Override @@ -39,5 +42,8 @@ public class MyModule implements Module { binder.bind(LocaleManager.class).in(Scopes.SINGLETON); binder.bind(ServerUtils.class).in(Scopes.SINGLETON); binder.bind(WebSocketUtils.class).in(Scopes.SINGLETON); + + binder.bind(ConfigService.class).toInstance(new ConfigService(Path.of("config.json"))); + } } \ 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 4079723..0baff95 100644 --- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java +++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java @@ -9,12 +9,14 @@ import java.util.Locale; import client.exception.UpdateException; import client.scenes.recipe.IngredientListCtrl; import client.scenes.recipe.RecipeStepListCtrl; + +import client.utils.Config; +import client.utils.ConfigService; import client.utils.DefaultRecipeFactory; import client.utils.LocaleAware; import client.utils.LocaleManager; import client.utils.ServerUtils; import client.utils.WebSocketUtils; - import commons.Recipe; import commons.ws.Topics; @@ -72,6 +74,13 @@ public class FoodpalApplicationCtrl implements LocaleAware { @FXML public Button cloneRecipeButton; + @FXML + private Button favouriteButton; + + private Config config; + private final ConfigService configService; + + // === CENTER: RECIPE DETAILS === @FXML @@ -89,13 +98,15 @@ public class FoodpalApplicationCtrl implements LocaleAware { WebSocketUtils webSocketUtils, LocaleManager localeManager, IngredientListCtrl ingredientListCtrl, - RecipeStepListCtrl stepListCtrl + RecipeStepListCtrl stepListCtrl, + ConfigService configService ) { this.server = server; this.webSocketUtils = webSocketUtils; this.localeManager = localeManager; this.ingredientListCtrl = ingredientListCtrl; this.stepListCtrl = stepListCtrl; + this.configService = configService; initializeWebSocket(); } @@ -124,6 +135,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { @Override public void initializeComponents() { + config = configService.getConfig(); // TODO Reduce code duplication?? // Initialize callback for ingredient list updates this.ingredientListCtrl.setUpdateCallback(newList -> { @@ -162,10 +174,12 @@ public class FoodpalApplicationCtrl implements LocaleAware { setText(empty || item == null ? "" : item.getName()); } }); - // When your selection changes, update details in the panel recipeList.getSelectionModel().selectedItemProperty().addListener( - (obs, oldRecipe, newRecipe) -> showRecipeDetails(newRecipe) + (obs, oldRecipe, newRecipe) -> { + showRecipeDetails(newRecipe); + updateFavouriteButton(newRecipe); + } ); // Double-click to go to detail screen @@ -175,8 +189,8 @@ public class FoodpalApplicationCtrl implements LocaleAware { openSelectedRecipe(); } }); - refresh(); + updateFavouriteButton(recipeList.getSelectionModel().getSelectedItem()); } private void showName(String name) { @@ -357,6 +371,39 @@ public class FoodpalApplicationCtrl implements LocaleAware { recipeList.getSelectionModel().select(cloned); } + private void updateFavouriteButton(Recipe recipe) { + if (recipe == null) { + favouriteButton.setDisable(true); + favouriteButton.setText("☆"); + return; + } + + favouriteButton.setDisable(false); + favouriteButton.setText( + config.isFavourite(recipe.getId()) ? "★" : "☆" + ); + } + + @FXML + private void toggleFavourite() { + Recipe selected = recipeList.getSelectionModel().getSelectedItem(); + if (selected == null) { + return; + } + + long id = selected.getId(); + + if (config.isFavourite(id)) { + config.removeFavourite(id); + } else { + config.addFavourite(id); + } + + configService.save(); + updateFavouriteButton(selected); + } + + } diff --git a/client/src/main/resources/client/scenes/FoodpalApplication.fxml b/client/src/main/resources/client/scenes/FoodpalApplication.fxml index 27b5bc2..21f79fa 100644 --- a/client/src/main/resources/client/scenes/FoodpalApplication.fxml +++ b/client/src/main/resources/client/scenes/FoodpalApplication.fxml @@ -113,6 +113,7 @@