fix: merge conflict
This commit is contained in:
commit
d62422bbdb
20 changed files with 879 additions and 93 deletions
131
client/src/main/java/client/Ingredient/IngredientController.java
Normal file
131
client/src/main/java/client/Ingredient/IngredientController.java
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
package client.Ingredient;
|
||||
|
||||
import commons.Ingredient;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.Button;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class IngredientController {
|
||||
|
||||
@FXML
|
||||
private ListView<Ingredient> ingredientListView;
|
||||
|
||||
@FXML
|
||||
private Button deleteButton;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate(); // Simplified REST client
|
||||
|
||||
@FXML
|
||||
private void handleDeleteIngredient(ActionEvent event) {
|
||||
// Get selected ingredient
|
||||
Ingredient selectedIngredient = ingredientListView.getSelectionModel().getSelectedItem();
|
||||
if (selectedIngredient == null) {
|
||||
showError("No ingredient selected", "Please select an ingredient to delete.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the ingredient is used in any recipe
|
||||
checkIngredientUsage(selectedIngredient);
|
||||
}
|
||||
|
||||
private void checkIngredientUsage(Ingredient ingredient) {
|
||||
String url = "http://localhost:8080/api/ingredients/" + ingredient.getId() + "/usage";
|
||||
|
||||
ResponseEntity<IngredientUsageResponse> response = restTemplate.getForEntity(url, IngredientUsageResponse.class);
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.NOT_FOUND) {
|
||||
showError("Ingredient not found", "The ingredient does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
IngredientUsageResponse usageResponse = response.getBody();
|
||||
long usedInRecipes = usageResponse != null ? usageResponse.getUsedInRecipes() : 0;
|
||||
|
||||
if (usedInRecipes > 0) {
|
||||
// If ingredient is in use, show warning to the user
|
||||
showWarningDialog(ingredient, usedInRecipes);
|
||||
} else {
|
||||
// delete if not in use
|
||||
deleteIngredient(ingredient);
|
||||
}
|
||||
}
|
||||
|
||||
private void showWarningDialog(Ingredient ingredient, long usedInRecipes) {
|
||||
Alert alert = new Alert(AlertType.WARNING);
|
||||
alert.setTitle("Warning");
|
||||
alert.setHeaderText("Ingredient in Use");
|
||||
alert.setContentText("The ingredient '" + ingredient.getName() + "' is used in " + usedInRecipes + " recipe(s). Are you sure you want to delete it?");
|
||||
|
||||
ButtonType deleteButton = new ButtonType("Delete Anyway");
|
||||
ButtonType cancelButton = new ButtonType("Cancel");
|
||||
|
||||
alert.getButtonTypes().setAll(deleteButton, cancelButton);
|
||||
|
||||
alert.showAndWait().ifPresent(response -> {
|
||||
if (response == deleteButton) {
|
||||
// delete if the user confirms
|
||||
deleteIngredient(ingredient);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteIngredient(Ingredient ingredient) {
|
||||
String url = "http://localhost:8080/api/ingredients/" + ingredient.getId();
|
||||
|
||||
restTemplate.delete(url);
|
||||
showConfirmation("Deletion Successful", "The ingredient '" + ingredient.getName() + "' has been successfully deleted.");
|
||||
|
||||
// refresh
|
||||
refreshIngredientList();
|
||||
}
|
||||
|
||||
private void showError(String title, String message) {
|
||||
Alert alert = new Alert(AlertType.ERROR);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void showConfirmation(String title, String message) {
|
||||
Alert alert = new Alert(AlertType.INFORMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void refreshIngredientList() {
|
||||
// Refresh
|
||||
ingredientListView.getItems().clear();
|
||||
}
|
||||
|
||||
// Inner class for usage response
|
||||
public static class IngredientUsageResponse {
|
||||
private long ingredientId;
|
||||
private long usedInRecipes;
|
||||
|
||||
public long getIngredientId() {
|
||||
return ingredientId;
|
||||
}
|
||||
|
||||
public void setIngredientId(long ingredientId) {
|
||||
this.ingredientId = ingredientId;
|
||||
}
|
||||
|
||||
public long getUsedInRecipes() {
|
||||
return usedInRecipes;
|
||||
}
|
||||
|
||||
public void setUsedInRecipes(long usedInRecipes) {
|
||||
this.usedInRecipes = usedInRecipes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,4 +26,8 @@ public class UI extends Application {
|
|||
var mainCtrl = INJECTOR.getInstance(MainCtrl.class);
|
||||
mainCtrl.setup(primaryStage, foodpal);
|
||||
}
|
||||
|
||||
public static MyFXML getFXML() {
|
||||
return FXML;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import java.util.logging.Logger;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import client.exception.InvalidModificationException;
|
||||
import client.scenes.recipe.IngredientsPopupCtrl;
|
||||
import client.scenes.recipe.RecipeDetailCtrl;
|
||||
|
||||
import client.utils.Config;
|
||||
|
|
@ -20,6 +21,7 @@ import client.utils.LocaleManager;
|
|||
import client.utils.ServerUtils;
|
||||
import client.utils.WebSocketDataService;
|
||||
import client.utils.WebSocketUtils;
|
||||
import commons.Ingredient;
|
||||
import commons.Recipe;
|
||||
|
||||
import commons.ws.Topics;
|
||||
|
|
@ -36,17 +38,20 @@ import javafx.scene.control.Button;
|
|||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
|
||||
public class FoodpalApplicationCtrl implements LocaleAware {
|
||||
private final ServerUtils server;
|
||||
private final WebSocketUtils webSocketUtils;
|
||||
private final LocaleManager localeManager;
|
||||
private final WebSocketDataService<Long, Recipe> dataService;
|
||||
private final Logger logger = Logger.getLogger(FoodpalApplicationCtrl.class.getName());
|
||||
|
||||
@FXML
|
||||
private RecipeDetailCtrl recipeDetailController;
|
||||
|
||||
|
|
@ -60,6 +65,9 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
@FXML
|
||||
public ListView<Recipe> recipeList;
|
||||
|
||||
@FXML
|
||||
private ListView<Ingredient> ingredientListView;
|
||||
|
||||
@FXML
|
||||
private Button addRecipeButton;
|
||||
|
||||
|
|
@ -93,6 +101,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
|
||||
this.configService = configService;
|
||||
this.dataService = recipeDataService;
|
||||
|
||||
setupDataService();
|
||||
logger.info("WebSocket processor initialized.");
|
||||
initializeWebSocket();
|
||||
|
|
@ -401,6 +410,139 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
this.recipeDetailController.refreshFavouriteButton();
|
||||
this.recipeDetailController.setVisible(!recipeList.getItems().isEmpty());
|
||||
}
|
||||
|
||||
//Delete Ingredient button click
|
||||
@FXML
|
||||
private void handleDeleteIngredient() {
|
||||
// Get selected ingredient
|
||||
Ingredient selectedIngredient = ingredientListView.getSelectionModel().getSelectedItem();
|
||||
|
||||
if (selectedIngredient == null) {
|
||||
// Show an error message if no ingredient is selected
|
||||
showError("No ingredient selected", "Please select an ingredient to delete.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the ingredient is used in any recipe
|
||||
checkIngredientUsage(selectedIngredient);
|
||||
}
|
||||
|
||||
// Check if ingredient is used in any recipe before deleting
|
||||
private void checkIngredientUsage(Ingredient ingredient) {
|
||||
try {
|
||||
long usageCount = server.getIngredientUsage(ingredient.getId()); // Check ingredient usage via ServerUtils
|
||||
|
||||
if (usageCount > 0) {
|
||||
// If ingredient is used, show a warning dialog
|
||||
showWarningDialog(ingredient, usageCount);
|
||||
} else {
|
||||
// If not used, delete
|
||||
deleteIngredient(ingredient);
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Error", "Failed to check ingredient usage: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteIngredient(Ingredient ingredient) {
|
||||
try {
|
||||
server.deleteIngredient(ingredient.getId()); // Call ServerUtils to delete the ingredient
|
||||
showConfirmation("Success", "Ingredient '" + ingredient.getName() + "' has been deleted.");
|
||||
refreshIngredientList(); // refresh the ingredient list
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Error", "Failed to delete ingredient: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void showWarningDialog(Ingredient ingredient, long usedInRecipes) {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||
}
|
||||
|
||||
private void showError(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void showConfirmation(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void refreshIngredientList() {
|
||||
// Refresh
|
||||
ingredientListView.getItems().clear();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void handleAddIngredient() {
|
||||
//ask the user for the ingredient name
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setTitle("Add Ingredient");
|
||||
dialog.setHeaderText("Enter the ingredient name:");
|
||||
dialog.setContentText("Ingredient:");
|
||||
|
||||
// Wait for the user to enter a value
|
||||
Optional<String> result = dialog.showAndWait();
|
||||
|
||||
result.ifPresent(name -> {
|
||||
// Create a new Ingredient object
|
||||
Ingredient newIngredient = new Ingredient();
|
||||
newIngredient.setName(name);
|
||||
|
||||
// Add the new ingredient to the ListView
|
||||
ingredientListView.getItems().add(newIngredient);
|
||||
});
|
||||
}
|
||||
|
||||
// display ingredient name
|
||||
@FXML
|
||||
private void initializeIngredients() {
|
||||
ingredientListView.setCellFactory(list -> new ListCell<Ingredient>() {
|
||||
@Override
|
||||
protected void updateItem(Ingredient item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getName()); // Display the ingredient name in the ListView
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void openIngredientsPopup() {
|
||||
try {
|
||||
var pair = client.UI.getFXML().load(
|
||||
IngredientsPopupCtrl.class,
|
||||
"client", "scenes", "recipe", "IngredientsPopup.fxml"
|
||||
);
|
||||
|
||||
var root = pair.getValue();
|
||||
|
||||
var stage = new javafx.stage.Stage();
|
||||
stage.setTitle("Ingredients");
|
||||
stage.initModality(javafx.stage.Modality.APPLICATION_MODAL);
|
||||
stage.setScene(new javafx.scene.Scene(root));
|
||||
stage.showAndWait();
|
||||
} catch (Exception e) {
|
||||
var alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Error");
|
||||
alert.setHeaderText("Failed to open ingredients popup");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -411,3 +553,8 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@ public class IngredientListCell extends OrderedEditableListCell<RecipeIngredient
|
|||
String name = nameInput.getText();
|
||||
if (unit == null || !unit.isFormal()) {
|
||||
String desc = amountInput.getText();
|
||||
if (desc.isEmpty() || name.isEmpty()) {
|
||||
// TODO printError() integration
|
||||
return; // The user is forced to kindly try again until something valid comes up.
|
||||
}
|
||||
Ingredient newIngredient = new Ingredient(name, 0., 0., 0.);
|
||||
commitEdit(new VagueIngredient(newIngredient, desc));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
package client.scenes.recipe;
|
||||
|
||||
import client.utils.ServerUtils;
|
||||
import commons.Ingredient;
|
||||
import jakarta.inject.Inject;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
//TODO and check for capital letter milk and MILK are seen as different
|
||||
|
||||
|
||||
public class IngredientsPopupCtrl {
|
||||
|
||||
private final ServerUtils server;
|
||||
|
||||
@FXML
|
||||
private ListView<Ingredient> ingredientListView;
|
||||
|
||||
@Inject
|
||||
public IngredientsPopupCtrl(ServerUtils server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
ingredientListView.setCellFactory(list -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(Ingredient item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
refresh();
|
||||
}
|
||||
@FXML
|
||||
private void addIngredient() {
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setTitle("Add Ingredient");
|
||||
dialog.setHeaderText("Create a new ingredient");
|
||||
dialog.setContentText("Name:");
|
||||
|
||||
Optional<String> result = dialog.showAndWait();
|
||||
if (result.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String name = result.get().trim();
|
||||
if (name.isEmpty()) {
|
||||
showError("Ingredient name cannot be empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
server.createIngredient(name); // calls POST /api/ingredients
|
||||
refresh(); // reload list from server
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Failed to create ingredient: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void refresh() {
|
||||
try {
|
||||
List<Ingredient> ingredients = server.getIngredients();
|
||||
ingredientListView.getItems().setAll(ingredients);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Failed to load ingredients: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void deleteSelected() {
|
||||
Ingredient selected = ingredientListView.getSelectionModel().getSelectedItem();
|
||||
if (selected == null) {
|
||||
showError("No ingredient selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long usageCount = server.getIngredientUsage(selected.getId());
|
||||
if (usageCount > 0) {
|
||||
boolean proceed = confirmDeleteUsed(selected.getName(), usageCount);
|
||||
if (!proceed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
server.deleteIngredient(selected.getId());
|
||||
refresh();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Failed to delete ingredient: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void close() {
|
||||
Stage stage = (Stage) ingredientListView.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
private boolean confirmDeleteUsed(String name, long usedInRecipes) {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||
alert.setTitle("Warning");
|
||||
alert.setHeaderText("Ingredient in use");
|
||||
alert.setContentText("Ingredient '" + name + "' is used in " + usedInRecipes
|
||||
+ " recipe(s). Delete anyway?");
|
||||
|
||||
var delete = new javafx.scene.control.ButtonType("Delete Anyway");
|
||||
var cancel = new javafx.scene.control.ButtonType("Cancel");
|
||||
alert.getButtonTypes().setAll(delete, cancel);
|
||||
|
||||
return alert.showAndWait().orElse(cancel) == delete;
|
||||
}
|
||||
|
||||
private void showError(String msg) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Error");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(msg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ public class DefaultValueFactory {
|
|||
return new Recipe(
|
||||
null,
|
||||
"Untitled recipe",
|
||||
"en",
|
||||
List.of(),
|
||||
List.of());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package client.utils;
|
|||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.inject.Inject;
|
||||
import commons.Ingredient;
|
||||
import commons.Recipe;
|
||||
import commons.RecipeIngredient;
|
||||
import jakarta.ws.rs.ProcessingException;
|
||||
|
|
@ -207,4 +208,84 @@ public class ServerUtils {
|
|||
|
||||
updateRecipe(recipe);
|
||||
}
|
||||
|
||||
|
||||
// how many ingredients are getting used in recipes
|
||||
|
||||
public long getIngredientUsage(long ingredientId) throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/ingredients/" + ingredientId + "/usage"))
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() != statusOK) {
|
||||
throw new IOException("Failed to get usage for ingredient with id: " + ingredientId
|
||||
+ " body: " + response.body());
|
||||
}
|
||||
|
||||
record IngredientUsageResponse(long ingredientId, long usedInRecipes) {}
|
||||
IngredientUsageResponse usage =
|
||||
objectMapper.readValue(response.body(), IngredientUsageResponse.class);
|
||||
|
||||
return usage.usedInRecipes();
|
||||
}
|
||||
|
||||
|
||||
public void deleteIngredient(long ingredientId) throws IOException, InterruptedException {
|
||||
// Send delete request to remove the ingredient
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/ingredients/" + ingredientId))
|
||||
.DELETE()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() != statusOK) {
|
||||
throw new IOException("Failed to delete ingredient with id: " + ingredientId + " body: " + response.body());
|
||||
}
|
||||
|
||||
logger.info("Successfully deleted ingredient with id: " + ingredientId);
|
||||
}
|
||||
|
||||
|
||||
//retrieves the list of ingredients saved to backend
|
||||
|
||||
public List<Ingredient> getIngredients() throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/ingredients"))
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() != statusOK) {
|
||||
throw new IOException("Failed to fetch ingredients. Server responds with: " + response.body());
|
||||
}
|
||||
|
||||
return objectMapper.readValue(response.body(), new com.fasterxml.jackson.core.type.TypeReference<List<Ingredient>>() {});
|
||||
}
|
||||
|
||||
//creates new ingredients in the ingredient list
|
||||
|
||||
public Ingredient createIngredient(String name) throws IOException, InterruptedException {
|
||||
Ingredient ingredient = new Ingredient(name, 0.0, 0.0, 0.0);
|
||||
String json = objectMapper.writeValueAsString(ingredient);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/ingredients"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(json))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() != statusOK) {
|
||||
throw new IOException("Failed to create ingredient. Server responds with: " + response.body());
|
||||
}
|
||||
|
||||
return objectMapper.readValue(response.body(), Ingredient.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
<Font name="System Bold" size="29.0" />
|
||||
</font>
|
||||
</Label>
|
||||
</HBox>
|
||||
</HBox>
|
||||
</top>
|
||||
|
||||
<!-- LEFT: RECIPE LIST -->
|
||||
|
|
@ -38,7 +38,8 @@
|
|||
<Label fx:id="recipesLabel" text="%menu.label.recipes">
|
||||
<font>
|
||||
<Font name="System Bold" size="15.0" />
|
||||
</font></Label>
|
||||
</font>
|
||||
</Label>
|
||||
|
||||
<fx:include fx:id="searchBar" source="SearchBar.fxml" />
|
||||
|
||||
|
|
@ -50,6 +51,11 @@
|
|||
<Button fx:id="cloneRecipeButton" mnemonicParsing="false" onAction="#cloneRecipe" text="%menu.button.clone" />
|
||||
<ToggleButton fx:id="favouritesOnlyToggle" onAction="#toggleFavouritesView" text="%menu.button.favourite" />
|
||||
</HBox>
|
||||
|
||||
<Button fx:id="manageIngredientsButton"
|
||||
onAction="#openIngredientsPopup"
|
||||
text="Ingredients..." />
|
||||
|
||||
</VBox>
|
||||
</left>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<VBox xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="client.scenes.recipe.IngredientsPopupCtrl"
|
||||
spacing="10" prefWidth="420" prefHeight="520">
|
||||
|
||||
<padding>
|
||||
<Insets top="12" right="12" bottom="12" left="12"/>
|
||||
</padding>
|
||||
|
||||
<Label text="Ingredients" style="-fx-font-size: 18px; -fx-font-weight: bold;"/>
|
||||
|
||||
<ListView fx:id="ingredientListView" VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<ButtonBar>
|
||||
<buttons>
|
||||
<Button text="Add" onAction="#addIngredient"/>
|
||||
<Button text="Refresh" onAction="#refresh"/>
|
||||
<Button text="Delete" onAction="#deleteSelected"/>
|
||||
<Button text="Close" onAction="#close"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
|
|
@ -34,7 +34,7 @@ public class PrintExportTest {
|
|||
List<String> preparationSteps = new ArrayList<>();
|
||||
preparationSteps.add("Mix Ingredients");
|
||||
preparationSteps.add("Heat in Oven");
|
||||
Recipe recipe1 = new Recipe(testRecipeId, "Banana Bread", ingredients, preparationSteps);
|
||||
Recipe recipe1 = new Recipe(testRecipeId, "Banana Bread", "en", ingredients, preparationSteps);
|
||||
|
||||
assertEquals("""
|
||||
Title: Banana Bread
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ public class Ingredient {
|
|||
this.carbsPer100g = carbsPer100g;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public double kcalPer100g() {
|
||||
return proteinPer100g * KCAL_PER_GRAM_PROTEIN
|
||||
+ carbsPer100g * KCAL_PER_GRAM_CARBS
|
||||
|
|
@ -92,6 +94,10 @@ public class Ingredient {
|
|||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name){
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ public class Recipe {
|
|||
@Column(name = "name", nullable = false, unique = true)
|
||||
private String name;
|
||||
|
||||
// Locale in which the recipe was created.
|
||||
@Column(name = "locale", nullable = false)
|
||||
private String locale = "en";
|
||||
|
||||
// Creates another table named recipe_ingredients which stores:
|
||||
// recipe_ingredients(recipe_id -> recipes(id), ingredient).
|
||||
// Example recipe_ingredients table:
|
||||
|
|
@ -95,11 +99,15 @@ public class Recipe {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
// TODO: Replace String with Embeddable Ingredient Class for ingredients
|
||||
public Recipe(Long id, String name, List<RecipeIngredient> ingredients, List<String> preparationSteps) {
|
||||
public Recipe(Long id,
|
||||
String name,
|
||||
String locale,
|
||||
List<RecipeIngredient> ingredients,
|
||||
List<String> preparationSteps) {
|
||||
// Not used by JPA/Spring
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.locale = locale;
|
||||
this.ingredients = ingredients;
|
||||
this.preparationSteps = preparationSteps;
|
||||
}
|
||||
|
|
@ -120,6 +128,14 @@ public class Recipe {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public void setLocale(String locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
// TODO: Replace String with Embeddable Ingredient Class
|
||||
public List<RecipeIngredient> getIngredients() {
|
||||
// Disallow modifying the returned list.
|
||||
|
|
@ -159,7 +175,7 @@ public class Recipe {
|
|||
@Override
|
||||
public String toString() {
|
||||
return "Recipe " + id +
|
||||
" - " + name +
|
||||
" - " + name + " (" + locale + ")" +
|
||||
": " + ingredients.size() + " ingredients / " +
|
||||
preparationSteps.size() + " steps";
|
||||
}
|
||||
|
|
@ -170,6 +186,7 @@ public class Recipe {
|
|||
return "Recipe{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
", locale='" + locale + "'" +
|
||||
", ingredients=" + ingredients +
|
||||
", preparationSteps=" + preparationSteps +
|
||||
'}';
|
||||
|
|
|
|||
146
docs/agenda/agenda-07.md
Normal file
146
docs/agenda/agenda-07.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Week 7 meeting agenda
|
||||
|
||||
| Key | Value |
|
||||
| ------------ |----------------------------------------------------------------------------------------------------------|
|
||||
| Date | Jan 9th 2026 |
|
||||
| Time | 16:45 |
|
||||
| Location | DW PC Hall 2 |
|
||||
| Chair | Mei Chang van der Werff |
|
||||
| Minute Taker | Aysegul Aydinlik |
|
||||
| Attendees | Natalia Cholewa,Oskar Rasienski, Rithvik Sriram, Aysegul Aydinlik, Steven Liu, Mei Chang van der Werff |
|
||||
## Table of contents
|
||||
### Opening
|
||||
|
||||
1. (1 min) Introduction by chair
|
||||
> Meeting starts at 16:48
|
||||
|
||||
2. (1 min) Any additions to the agenda?
|
||||
> None
|
||||
|
||||
3. (1-2 min) TA announcements?
|
||||
> - Technology feedback has been graded
|
||||
> - Implemented features to be graded this weekend
|
||||
> - For the final evaluation we need to have the basic requirements otherwise it won't get checked at all
|
||||
> - We can and should update read.me
|
||||
> - We get to see our LOC next week
|
||||
|
||||
### Feedback/assignments
|
||||
4. (3 min) Go over TA feedback given (technology)
|
||||
- Do we have any questions about the feedback?
|
||||
> **Technology feedback**
|
||||
> - Server and business logic and controller logic should be seperated
|
||||
> - Avoid hardcoding; move logic into server utilities where appropriate
|
||||
5. (3 min) Go over the implemented feature assignment due today
|
||||
> - 4.1 Printing not finished
|
||||
> - Meaningfull additions:
|
||||
> - Extra language support
|
||||
> - Text-to-voice functionality
|
||||
|
||||
### Current progress
|
||||
6. How far are we in terms of completion?
|
||||
* (5 min) Progress check in terms of feature/issue completion
|
||||
- What have we completed/going to complete this week?
|
||||
> - 4.1 not finished
|
||||
> - 4.2 done
|
||||
> - 4.3 requires significant work
|
||||
- Who implemented what, this week?
|
||||
> - Natalia: refactored server-side, recipe language handling
|
||||
> - Steven: refactoring, fixing application compatibility with intelliJ
|
||||
> - Aysegul: Ingredient list view + ingredient usage
|
||||
> - Oskar: Refactoring
|
||||
> - Rithvik: Fixed recipe detail UI sizing, implemented server changed, printing
|
||||
> - Mei Chang: Client-side testing, hardcoded value fixes, refactoring
|
||||
* (3 min) Small Showcase of the product
|
||||
> - small showcase of the favourite button
|
||||
|
||||
### Planning
|
||||
7. (15 min) Planning week 8
|
||||
- What needs to be done? (provisionary tasks below)
|
||||
- Handle Backend-Server code delegation
|
||||
- How do we do it?
|
||||
- Who does what?
|
||||
|
||||
|
||||
* (2 min) Designate teams.
|
||||
* (1 min) Who is the next chair and minute taker?
|
||||
> **Planning week 8**
|
||||
> - resolve unfinished issues from previous weeks
|
||||
> - improve server utilities and dependency injection
|
||||
> - No shopping list implementation planned
|
||||
> - Nutrition logic should be implemented
|
||||
>
|
||||
> **SERVER**
|
||||
>
|
||||
> Mei Chang and Aysegul
|
||||
>
|
||||
> **BACKEND**
|
||||
>
|
||||
> Oskar, Natalia, Rithvik, Steven
|
||||
>
|
||||
> **Task Distribution**
|
||||
>
|
||||
> Aysegul: Server Testing + Additional server tasks
|
||||
>
|
||||
> Mei Chang: Nutrition logic & serving logic + server testing
|
||||
>
|
||||
> Oskar: Figuring out Mockito
|
||||
>
|
||||
> Rithvik: Printing functionality
|
||||
>
|
||||
> Steven: UI nutrition + dropdowns ingredients
|
||||
>
|
||||
> Natalia: Refactoring
|
||||
|
||||
Next chair and minute taker
|
||||
> Aysegul will be the next Chair
|
||||
>
|
||||
> Natalia will be the next Minute taker
|
||||
|
||||
### Closing
|
||||
8. (2 min) Questions?
|
||||
> -Emphasis on separation, refactoring, and maintainability
|
||||
9. (2 min) Summarize everything
|
||||
> - Focus next week: finishing 4.1 and 4.3
|
||||
>
|
||||
> - Server refactoring and nutrition logic are priorities
|
||||
>
|
||||
> - 2 members must still fulfill server LOC requirements
|
||||
>
|
||||
> - README must clearly reflect extensions and improvementsa
|
||||
|
||||
10. (1 min) TA remarks
|
||||
|
||||
> TA will check implemented Features on sunday
|
||||
>
|
||||
> meeting ends at 17:32
|
||||
>
|
||||
> actual meeting time: 44 minutes
|
||||
|
||||
Estimated meeting time: ~40 minutes
|
||||
|
||||
Provisionary tasks (updated)
|
||||
4.1
|
||||
- Connect the printing functionality to the button, rn the button does nothing
|
||||
|
||||
4.2
|
||||
-removing the refresh button and method, since it's automatic? (optional)
|
||||
- fix: not being able to make a empty title for recipe name
|
||||
|
||||
4.3
|
||||
- Implement fats/protein/carbs/kcal
|
||||
- Being able to select ingredients from a dropdown menu
|
||||
- Actually receive the delete recipe warning
|
||||
- Serving implementation
|
||||
- Informal unit fix (Steven)
|
||||
|
||||
4.4
|
||||
- Make the search bar functionality
|
||||
- Get the favourite warning message
|
||||
|
||||
|
||||
4.5 (old)
|
||||
Assuming that we're going to start with the shopping cart
|
||||
- Create shopping list
|
||||
- reset shopping list (backend)
|
||||
|
||||
- Issues/task that weren't finished this week
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
# Week 7 meeting agenda
|
||||
|
||||
| Key | Value |
|
||||
| ------------ |-----------------------------------------------------------------------------------------|
|
||||
| Date | Jan 9th 2026 |
|
||||
| Time | 14:45 |
|
||||
| Location | DW PC Hall 2 |
|
||||
| Chair | Mei Chang van der Werff |
|
||||
| Minute Taker | Aysegul Aydinlik |
|
||||
| Attendees | Natalia Cholewa, Rithvik Sriram, Aysegul Aydinlik, Steven Liu, Mei Chang van der Werff |
|
||||
## Table of contents
|
||||
### Opening
|
||||
|
||||
1. (1 min) Introduction by chair
|
||||
|
||||
2. (1 min) Any additions to the agenda?
|
||||
3. (1-2 min) TA announcements?
|
||||
|
||||
### Feedback/assignments
|
||||
4. (3 min) Go over TA feedback given (technology)
|
||||
- Do we have any questions about the feedback?
|
||||
5. (3 min) Go over the implemented feature assignment due today
|
||||
|
||||
### Current progress
|
||||
6. How far are we in terms of completion?
|
||||
* (5 min) Progress check in terms of feature/issue completion
|
||||
- What have we completed/going to complete this week?
|
||||
- Who implemented what, this week?
|
||||
* (3 min) Small Showcase of the product
|
||||
|
||||
### Planning
|
||||
7. (15 min) Planning week 8
|
||||
- What needs to be done? (provisionary tasks below)
|
||||
- Handle Backend-Server code delegation
|
||||
- How do we do it?
|
||||
- Who does what?
|
||||
|
||||
|
||||
* (2 min) Designate teams.
|
||||
* (1 min) Who is the next chair and minute taker?
|
||||
|
||||
### Closing
|
||||
8. (2 min) Questions?
|
||||
9. (2 min) Summarize everything
|
||||
|
||||
10. (1 min) TA remarks
|
||||
Estimated meeting time: ~40 minutes
|
||||
|
||||
Provisionary tasks (updated)
|
||||
4.1
|
||||
- Connect the printing functionality to the button, rn the button does nothing
|
||||
|
||||
4.2
|
||||
-removing the refresh button and method, since it's automatic? (optional)
|
||||
- fix: not being able to make a empty title for recipe name
|
||||
|
||||
4.3
|
||||
- Implement fats/protein/carbs/kcal
|
||||
- Being able to select ingredients from a dropdown menu
|
||||
- Actually receive the delete recipe warning
|
||||
- Serving implementation
|
||||
- Informal unit fix (Steven)
|
||||
|
||||
4.4
|
||||
- Make the search bar functionality
|
||||
- Get the favourite warning message
|
||||
|
||||
|
||||
4.5 (old)
|
||||
Assuming that we're going to start with the shopping cart
|
||||
- Create shopping list
|
||||
- reset shopping list (backend)
|
||||
|
||||
- Issues/task that weren't finished this week
|
||||
73
docs/feedback/week-07.md
Normal file
73
docs/feedback/week-07.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
|
||||
# Meeting feedback
|
||||
|
||||
**Quick Summary Scale**
|
||||
|
||||
Insufficient/Sufficient/Good/Excellent
|
||||
|
||||
|
||||
#### Agenda
|
||||
|
||||
|
||||
Feedback: **Excellent**
|
||||
|
||||
- The agenda was uploaded on time
|
||||
- The agenda is formatted according to the template.
|
||||
- The individual points are clear.
|
||||
- I appreciate that you used separate sections for your ideas (e.g. assignments, current progress and planning).
|
||||
|
||||
|
||||
#### Performance of the Previous Minute Taker
|
||||
|
||||
Feedback: **Excellent**
|
||||
|
||||
- The notes have been merged into the agenda file.
|
||||
- Notes are clear and grouped based on the agenda point. The formatting makes the notes clearer.
|
||||
- The agreements are clear and realistic.
|
||||
- I like that you included what everyone completed up to that point.
|
||||
- Everyone was assigned to a task. I understand you do a proper task distribution separate from the TA meeting.
|
||||
- I also appreciated that you added all questions that arose during the meeting.
|
||||
|
||||
#### Chair performance
|
||||
|
||||
Feedback: **Excellent**
|
||||
|
||||
- I liked how you started the meeting and checked in with everyone.
|
||||
- You followed all points from the agenda and everyone followed along.
|
||||
- You initiated all points and ensured everyone was heard, including members who joined online.
|
||||
- I appreciated that you asked everyone for feedback or other remarks.
|
||||
- You also pointed out what the team should focus more in the following week (e.g fixing bugs)
|
||||
- Time estimates seemed accurate.
|
||||
|
||||
|
||||
#### Attitude & Relation
|
||||
|
||||
Feedback: **Excellent**
|
||||
|
||||
|
||||
- It was a bit difficult to engage everyone that was also online, but you did a great job overall.
|
||||
- Everyone took ownership of the meeting.
|
||||
- The atmosphere was constructive and positive.
|
||||
- All ideas were listened to and considered by all team members.
|
||||
- Everyone was active in the meeting and sharing ideas.
|
||||
|
||||
|
||||
#### Potentially Shippable Product
|
||||
|
||||
Feedback: **Excellent**
|
||||
|
||||
|
||||
- The team presented the current state of the application.
|
||||
- The basic requirements are completed and most extensions show good progress. Make sure to not miss any requirements for the extensions you already started.
|
||||
- Progress has been made compared to the last meeting.
|
||||
- If you maintain the same workflow, I believe you can complete all extensions.
|
||||
|
||||
|
||||
#### Work Contribution/Distribution in the Team
|
||||
|
||||
Feedback: **Excellent**
|
||||
|
||||
- Everyone explained what they completed since the last meeting.
|
||||
- It is good that you are aware of the current issues and you are working on them.
|
||||
- Everyone reached their goal. Well done!
|
||||
- Everyone contributes to the project.
|
||||
|
|
@ -6,6 +6,7 @@ import commons.ws.Topics;
|
|||
import commons.ws.messages.CreateIngredientMessage;
|
||||
import commons.ws.messages.DeleteIngredientMessage;
|
||||
import commons.ws.messages.UpdateIngredientMessage;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
|
|
@ -115,6 +116,8 @@ public class IngredientController {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Update an existing ingredient by its ID.
|
||||
* Maps to <code>PATCH /api/ingredients/{id}</code>
|
||||
|
|
@ -166,18 +169,26 @@ public class IngredientController {
|
|||
*/
|
||||
@PostMapping("/ingredients")
|
||||
public ResponseEntity<Ingredient> createIngredient(@RequestBody Ingredient ingredient) {
|
||||
if (ingredient.name == null || ingredient.name.isEmpty()) {
|
||||
if (ingredient == null
|
||||
|| ingredient.name == null
|
||||
|| ingredient.name.trim().isEmpty()) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
return ingredientService.create(ingredient)
|
||||
.map(saved -> {
|
||||
messagingTemplate.convertAndSend(Topics.INGREDIENTS, new CreateIngredientMessage(saved));
|
||||
messagingTemplate.convertAndSend(
|
||||
Topics.INGREDIENTS,
|
||||
new CreateIngredientMessage(saved)
|
||||
);
|
||||
return ResponseEntity.ok(saved);
|
||||
})
|
||||
.orElseGet(() -> ResponseEntity.badRequest().build());
|
||||
.orElseGet(() -> ResponseEntity.status
|
||||
(HttpStatus.CONFLICT).build()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete an ingredient by its ID.
|
||||
* Maps to <code>DELETE /api/ingredients/{id}</code>
|
||||
|
|
@ -194,13 +205,24 @@ public class IngredientController {
|
|||
*/
|
||||
@DeleteMapping("/ingredients/{id}")
|
||||
public ResponseEntity<Boolean> deleteIngredient(@PathVariable Long id) {
|
||||
//check if the ingredient is used in any recipe
|
||||
long usageCount = ingredientService.countUsage(id);
|
||||
|
||||
if (usageCount > 0) {
|
||||
// If used in recipes, return a warning response (HTTP 400)
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(false); // Ingredient in use, don't delete
|
||||
}
|
||||
|
||||
// delete if the ingredient is not in use
|
||||
if (!ingredientService.delete(id)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
messagingTemplate.convertAndSend(Topics.INGREDIENTS, new DeleteIngredientMessage(id));
|
||||
return ResponseEntity.ok(true);
|
||||
return ResponseEntity.ok(true); // deleted~
|
||||
}
|
||||
|
||||
|
||||
public record IngredientUsageResponse(Long ingredientId, long usedInRecipes){}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,19 +59,35 @@ public class RecipeController {
|
|||
}
|
||||
|
||||
/**
|
||||
* Mapping for <code>GET /recipes(?limit=)</code>
|
||||
* Mapping for <code>GET /recipes(?limit=)(&locales=)</code>
|
||||
* <p>
|
||||
* If the limit parameter is unspecified, return all recipes in the repository.
|
||||
* @param limit Integer limit of items you want to get
|
||||
* @return The list of recipes
|
||||
*/
|
||||
@GetMapping("/recipes")
|
||||
public ResponseEntity<List<Recipe>> getRecipes(@RequestParam Optional<Integer> limit) {
|
||||
public ResponseEntity<List<Recipe>> getRecipes(
|
||||
@RequestParam Optional<List<String>> locales,
|
||||
@RequestParam Optional<Integer> 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)
|
||||
);
|
||||
|
||||
// TODO: maybe refactor this. this is horrid and evil and nightmare
|
||||
var recipes = locales
|
||||
.map(loc -> {
|
||||
return limit.map(lim -> {
|
||||
return recipeService.findAllWithLocales(loc, lim);
|
||||
})
|
||||
.orElseGet(() -> {
|
||||
return recipeService.findAllWithLocales(loc);
|
||||
});
|
||||
})
|
||||
.orElseGet(
|
||||
// Choose the right overload. One has a limit, other doesn't.
|
||||
() -> limit.map(recipeService::findAll).orElseGet(recipeService::findAll));
|
||||
|
||||
|
||||
return ResponseEntity.ok(recipes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -19,6 +19,15 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||
|
||||
import commons.Recipe;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
public interface RecipeRepository extends JpaRepository<Recipe, Long> {
|
||||
boolean existsByName(String name);
|
||||
|
||||
List<Recipe> findAllByLocaleIsIn(Collection<String> locales);
|
||||
Page<Recipe> findAllByLocaleIsIn(Collection<String> locales, Pageable pageable);
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import server.database.IngredientRepository;
|
|||
import server.database.RecipeIngredientRepository;
|
||||
import server.database.RecipeRepository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
|
|
@ -36,6 +37,14 @@ public class RecipeService {
|
|||
return recipeRepository.findAll(PageRequest.of(0, limit)).toList();
|
||||
}
|
||||
|
||||
public List<Recipe> findAllWithLocales(Collection<String> locales) {
|
||||
return recipeRepository.findAllByLocaleIsIn(locales);
|
||||
}
|
||||
|
||||
public List<Recipe> findAllWithLocales(Collection<String> locales, int limit) {
|
||||
return recipeRepository.findAllByLocaleIsIn(locales, PageRequest.of(0, limit)).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new recipe. Returns empty if the recipe with the same name already exists.
|
||||
* @param recipe Recipe to be saved in the db.
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public class RecipeControllerTest {
|
|||
public void setup(TestInfo info) {
|
||||
recipes = LongStream
|
||||
.range(0, NUM_RECIPES)
|
||||
.mapToObj(x -> new Recipe(null, "Recipe " + x, List.of(), List.of()))
|
||||
.mapToObj(x -> new Recipe(null, "Recipe " + x, "en", List.of(), List.of()))
|
||||
.toList();
|
||||
controller = new RecipeController(
|
||||
recipeService,
|
||||
|
|
@ -107,7 +107,7 @@ public class RecipeControllerTest {
|
|||
@Tag("test-from-init-data")
|
||||
public void getManyRecipes() {
|
||||
// The number of recipes returned is the same as the entire input list
|
||||
assertEquals(recipes.size(), controller.getRecipes(Optional.empty()).getBody().size());
|
||||
assertEquals(recipes.size(), controller.getRecipes(Optional.empty(), Optional.empty()).getBody().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -115,7 +115,29 @@ public class RecipeControllerTest {
|
|||
public void getSomeRecipes() {
|
||||
final int LIMIT = 5;
|
||||
// The number of recipes returned is the same as the entire input list
|
||||
assertEquals(LIMIT, controller.getRecipes(Optional.of(LIMIT)).getBody().size());
|
||||
assertEquals(LIMIT, controller.getRecipes(Optional.empty(), Optional.of(LIMIT)).getBody().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("test-from-init-data")
|
||||
public void getManyRecipesWithLocale() {
|
||||
// The number of recipes returned is the same as the entire input list
|
||||
assertEquals(recipes.size(), controller.getRecipes(Optional.of(List.of("en", "nl")), Optional.empty()).getBody().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("test-from-init-data")
|
||||
public void getNoRecipesWithLocale() {
|
||||
// should have NO Dutch recipes (thank god)
|
||||
assertEquals(0, controller.getRecipes(Optional.of(List.of("nl")), Optional.empty()).getBody().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("test-from-init-data")
|
||||
public void getSomeRecipesWithLocale() {
|
||||
final int LIMIT = 5;
|
||||
// The number of recipes returned is the same as the entire input list
|
||||
assertEquals(LIMIT, controller.getRecipes(Optional.of(List.of("en", "nl")), Optional.of(LIMIT)).getBody().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue