feat: ingredient controller, websocket messages, ingredient controller tests
This commit is contained in:
parent
6129fc2a5a
commit
3af808ef58
13 changed files with 498 additions and 9 deletions
205
server/src/main/java/server/api/IngredientController.java
Normal file
205
server/src/main/java/server/api/IngredientController.java
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package server.api;
|
||||
|
||||
import commons.Ingredient;
|
||||
import commons.ws.Topics;
|
||||
import commons.ws.messages.CreateIngredientMessage;
|
||||
import commons.ws.messages.DeleteIngredientMessage;
|
||||
import commons.ws.messages.UpdateIngredientMessage;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import server.database.IngredientRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* REST controller for managing ingredients.
|
||||
* <p>
|
||||
* Exposes the following endpoints:
|
||||
* <ul>
|
||||
* <li>GET /api/ingredients - Get a list of ingredients, optionally paginated.</li>
|
||||
* <li>GET /api/ingredients/{id} - Get a specific ingredient by its ID.</li>
|
||||
* <li>POST /api/ingredients - Create a new ingredient.</li>
|
||||
* <li>PATCH /api/ingredients/{id} - Update an existing ingredient by its ID.</li>
|
||||
* <li>DELETE /api/ingredients/{id} - Delete an ingredient by its ID.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @see Ingredient
|
||||
* @see IngredientRepository
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class IngredientController {
|
||||
private final IngredientRepository ingredientRepository;
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
public IngredientController(IngredientRepository ingredientRepository,
|
||||
SimpMessagingTemplate messagingTemplate) {
|
||||
this.ingredientRepository = ingredientRepository;
|
||||
this.messagingTemplate = messagingTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of ingredients, optionally paginated. <br />
|
||||
* Maps to <code>GET /api/ingredients(?page=&limit=)</code>
|
||||
* <p>
|
||||
* If no limit is specified, all ingredients are returned sorted by name ascending.
|
||||
* When calling this function, consider using pagination to avoid large responses.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* When using pagination, you should provide a limit.
|
||||
* The page defaults to 0 if not provided.
|
||||
* </p>
|
||||
*
|
||||
* @param page The page number to retrieve (0-indexed). Optional.
|
||||
* @param limit The maximum number of ingredients to return. Optional.
|
||||
* @return A ResponseEntity containing the list of ingredients.
|
||||
*
|
||||
* @see Ingredient
|
||||
*/
|
||||
@GetMapping("/ingredients")
|
||||
public ResponseEntity<List<Ingredient>> getIngredients(
|
||||
@RequestParam Optional<Integer> page,
|
||||
@RequestParam Optional<Integer> limit
|
||||
) {
|
||||
List<Ingredient> ingredients = limit
|
||||
.map(l -> {
|
||||
return ingredientRepository.findAllByOrderByNameAsc
|
||||
(PageRequest.of(page.orElse(0), l)).toList();
|
||||
})
|
||||
.orElseGet(ingredientRepository::findAllByOrderByNameAsc);
|
||||
|
||||
return ResponseEntity.ok(ingredients);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific ingredient by its ID.
|
||||
* Maps to <code>GET /api/ingredients/{id}</code>
|
||||
*
|
||||
* <p>
|
||||
* Returns 200 OK with the ingredient if found,
|
||||
* or 404 Not Found if the ingredient does not exist.
|
||||
* </p>
|
||||
*
|
||||
* @param id The ID of the ingredient to retrieve.
|
||||
* @return The ingredient wrapped in a ResponseEntity.
|
||||
*
|
||||
* @see Ingredient
|
||||
*/
|
||||
@GetMapping("/ingredients/{id}")
|
||||
public ResponseEntity<Ingredient> getIngredientById(@PathVariable Long id) {
|
||||
return ingredientRepository.findById(id)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElseGet(() -> ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing ingredient by its ID.
|
||||
* Maps to <code>PATCH /api/ingredients/{id}</code>
|
||||
*
|
||||
* <p>
|
||||
* If the ingredient with the specified ID does not exist,
|
||||
* returns 404 Not Found.
|
||||
* <p>
|
||||
* If the ingredient exists, updates it with the provided data
|
||||
* and returns the updated ingredient with 200 OK.
|
||||
* </p>
|
||||
*
|
||||
* @param id The ID of the ingredient to update.
|
||||
* @param updated The updated ingredient data.
|
||||
* @return The updated ingredient wrapped in a ResponseEntity.
|
||||
*
|
||||
* @see Ingredient
|
||||
*/
|
||||
@PatchMapping("/ingredients/{id}")
|
||||
public ResponseEntity<Ingredient> updateIngredient(
|
||||
@PathVariable Long id,
|
||||
@RequestBody Ingredient updated
|
||||
) {
|
||||
if (!ingredientRepository.existsById(id)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// TODO: Refactor to use setters
|
||||
updated.id = id;
|
||||
Ingredient savedIngredient = ingredientRepository.save(updated);
|
||||
messagingTemplate.convertAndSend(Topics.INGREDIENTS, new CreateIngredientMessage(savedIngredient));
|
||||
|
||||
return ResponseEntity.ok(savedIngredient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ingredient.
|
||||
* Maps to <code>POST /api/ingredients</code>
|
||||
*
|
||||
* <p>
|
||||
* If an ingredient with the same name already exists,
|
||||
* returns 400 Bad Request.
|
||||
*
|
||||
* If the ingredient is created successfully,
|
||||
* returns the created ingredient with 200 OK.
|
||||
* </p>
|
||||
*
|
||||
* @param ingredient The ingredient to create.
|
||||
* @return The created ingredient wrapped in a ResponseEntity.
|
||||
*
|
||||
* @see Ingredient
|
||||
*/
|
||||
@PostMapping("/ingredients")
|
||||
public ResponseEntity<Ingredient> createIngredient(@RequestBody Ingredient ingredient) {
|
||||
if (ingredient.name == null || ingredient.name.isEmpty()) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
Ingredient example = new Ingredient();
|
||||
example.name = ingredient.name;
|
||||
|
||||
if (ingredientRepository.exists(Example.of(example))) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
Ingredient saved = ingredientRepository.save(ingredient);
|
||||
messagingTemplate.convertAndSend(Topics.INGREDIENTS, new UpdateIngredientMessage(saved));
|
||||
|
||||
return ResponseEntity.ok(saved);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an ingredient by its ID.
|
||||
* Maps to <code>DELETE /api/ingredients/{id}</code>
|
||||
*
|
||||
* <p>
|
||||
* Returns 404 Not Found if the ingredient does not exist.
|
||||
* If the ingredient is deleted successfully, returns 200 OK with true.
|
||||
* </p>
|
||||
*
|
||||
* @param id The ID of the ingredient to delete.
|
||||
* @return A ResponseEntity indicating the result of the operation.
|
||||
*
|
||||
* @see Ingredient
|
||||
*/
|
||||
@DeleteMapping("/ingredients/{id}")
|
||||
public ResponseEntity<Boolean> deleteIngredient(@PathVariable Long id) {
|
||||
if (!ingredientRepository.existsById(id)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
ingredientRepository.deleteById(id);
|
||||
messagingTemplate.convertAndSend(Topics.INGREDIENTS, new DeleteIngredientMessage(id));
|
||||
|
||||
return ResponseEntity.ok(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,8 @@ public class RecipeController {
|
|||
private final RecipeRepository recipeRepository; // JPA repository used in this controller
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
public RecipeController(RecipeRepository recipeRepository, SimpMessagingTemplate messagingTemplate) {
|
||||
public RecipeController(RecipeRepository recipeRepository,
|
||||
SimpMessagingTemplate messagingTemplate) {
|
||||
this.recipeRepository = recipeRepository;
|
||||
this.messagingTemplate = messagingTemplate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package server.api;
|
||||
|
||||
import commons.Recipe;
|
||||
import commons.ws.Topics;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
|
|
@ -22,7 +23,7 @@ public class UpdateMessagingController {
|
|||
* @return The updated {@link Recipe} object wrapped in a {@link org.springframework.http.ResponseEntity}.
|
||||
*/
|
||||
@MessageMapping("/updates/recipe")
|
||||
@SendTo("/subscribe/recipe")
|
||||
@SendTo(Topics.RECIPES)
|
||||
public ResponseEntity<Recipe> broadcastRecipeUpdate(Recipe recipe) {
|
||||
return recipeController.updateRecipe(recipe.getId(), recipe);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package server;
|
||||
package server.database;
|
||||
|
||||
import commons.Ingredient;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IngredientRepository extends JpaRepository<Ingredient, Long> {
|
||||
List<Ingredient> findAllByOrderByNameAsc();
|
||||
Page<Ingredient> findAllByOrderByNameAsc(Pageable pageable);
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package server;
|
||||
package server.database;
|
||||
|
||||
import commons.RecipeIngredient;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
Loading…
Add table
Add a link
Reference in a new issue