Refactored recipe controller to use services

This commit is contained in:
Oskar Rasieński 2026-01-08 01:05:36 +01:00
commit e9b6d81a27
2 changed files with 36 additions and 84 deletions

View file

@ -7,8 +7,6 @@ import commons.ws.Topics;
import commons.ws.messages.CreateRecipeMessage;
import commons.ws.messages.DeleteRecipeMessage;
import commons.ws.messages.UpdateRecipeMessage;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.simp.SimpMessagingTemplate;
@ -22,9 +20,7 @@ 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 server.database.RecipeIngredientRepository;
import server.database.RecipeRepository;
import server.service.RecipeService;
import java.util.List;
import java.util.Optional;
@ -32,19 +28,12 @@ import java.util.Optional;
@RestController
@RequestMapping("/api")
public class RecipeController {
private final RecipeRepository recipeRepository; // JPA repository used in this controller
private final SimpMessagingTemplate messagingTemplate;
private final RecipeIngredientRepository recipeIngredientRepository;
private final IngredientRepository ingredientRepository;
private final RecipeService recipeService;
public RecipeController(RecipeRepository recipeRepository,
SimpMessagingTemplate messagingTemplate,
IngredientRepository ingredientRepository,
RecipeIngredientRepository recipeIngredientRepository) {
this.recipeRepository = recipeRepository;
public RecipeController(RecipeService recipeService, SimpMessagingTemplate messagingTemplate) {
this.recipeService = recipeService;
this.messagingTemplate = messagingTemplate;
this.recipeIngredientRepository = recipeIngredientRepository;
this.ingredientRepository = ingredientRepository;
}
/**
@ -57,10 +46,9 @@ public class RecipeController {
*/
@GetMapping("/recipe/{id}")
public ResponseEntity<Recipe> getRecipe(@PathVariable Long id) {
if (!recipeRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(recipeRepository.findById(id).get());
return recipeService.findById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
/**
@ -72,28 +60,12 @@ public class RecipeController {
*/
@GetMapping("/recipes")
public ResponseEntity<List<Recipe>> getRecipes(@RequestParam Optional<Integer> limit) {
if (limit.isPresent()) {
return ResponseEntity.ok(
recipeRepository.findAll(
PageRequest.of(0, limit.get())
).toList());
// Choose the right overload. One has a limit, other doesn't.
limit.map(recipeService::findAll).orElseGet(recipeService::findAll)
);
}
return ResponseEntity.ok(recipeRepository.findAll());
}
private Recipe saveRecipeAndDependencies(Recipe recipe) {
recipe.getIngredients()
.forEach(recipeIngredient ->
recipeIngredient.setIngredient(
ingredientRepository
.findByName(recipeIngredient.getIngredient().name)
.orElseGet(() -> ingredientRepository.save(recipeIngredient.getIngredient())
))
);
recipeIngredientRepository.saveAll(recipe.getIngredients());
Recipe saved = recipeRepository.save(recipe);
return saved;
}
/**
* Mapping for <code>POST /recipe/{id}</code>.
* Also creates the ingredient elements if they do not exist.
@ -104,13 +76,12 @@ public class RecipeController {
*/
@PostMapping("/recipe/{id}")
public ResponseEntity<Recipe> updateRecipe(@PathVariable Long id, @RequestBody Recipe recipe) {
if (!recipeRepository.existsById(id)) {
return ResponseEntity.badRequest().build();
}
Recipe saved = saveRecipeAndDependencies(recipe);
messagingTemplate.convertAndSend(Topics.RECIPES, new UpdateRecipeMessage(saved));
return recipeService.update(id, recipe)
.map(saved -> {
messagingTemplate.convertAndSend(Topics.RECIPES, new UpdateRecipeMessage(saved)); // Send to WS.
return ResponseEntity.ok(saved);
})
.orElseGet(() -> ResponseEntity.notFound().build()); // Recipe with that id not found.
}
/**
@ -125,22 +96,12 @@ public class RecipeController {
*/
@PutMapping("/recipe/new")
public ResponseEntity<Recipe> createRecipe(@RequestBody Recipe recipe) {
// We initialize a new example recipe with the name of input recipe
// This is the only attribute we are concerned about making sure it's unique
Recipe example = new Recipe();
example.setName(recipe.getName());
/* Here we use very funny JPA magic repository.exists(Example<Recipe>)
We check if any recipe in the repository has the same name as the input
*/
if (recipeRepository.exists(Example.of(example))) {
return ResponseEntity.badRequest().build();
}
Recipe saved = saveRecipeAndDependencies(recipe);
messagingTemplate.convertAndSend(Topics.RECIPES, new CreateRecipeMessage(saved));
return recipeService.create(recipe)
.map(saved -> {
messagingTemplate.convertAndSend(Topics.RECIPES, new CreateRecipeMessage(saved)); // Send to WS.
return ResponseEntity.ok(saved);
})
.orElseGet(() -> ResponseEntity.badRequest().build()); // That recipe already exists.
}
/**
@ -153,13 +114,10 @@ public class RecipeController {
*/
@DeleteMapping("/recipe/{id}")
public ResponseEntity<Boolean> deleteRecipe(@PathVariable Long id) {
if (!recipeRepository.existsById(id)) {
if (!recipeService.delete(id)) {
return ResponseEntity.badRequest().build();
}
recipeRepository.deleteById(id);
messagingTemplate.convertAndSend(Topics.RECIPES, new DeleteRecipeMessage(id));
messagingTemplate.convertAndSend(Topics.RECIPES, new DeleteRecipeMessage(id)); // Send to WS.
return ResponseEntity.ok(true);
}
}

View file

@ -13,9 +13,8 @@ import org.springframework.http.HttpStatus;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.test.context.ActiveProfiles;
import server.WebSocketConfig;
import server.database.IngredientRepository;
import server.database.RecipeIngredientRepository;
import server.database.RecipeRepository;
import server.service.RecipeService;
import java.util.ArrayList;
import java.util.List;
@ -38,30 +37,27 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
// This is required to enable WebSocket messaging in tests
//
// Without this line, Spring screams about missing SimpMessagingTemplate bean
@Import(WebSocketConfig.class)
@Import({WebSocketConfig.class, RecipeService.class})
public class RecipeControllerTest {
private final RecipeService recipeService;
private final RecipeRepository recipeRepository;
private final SimpMessagingTemplate template;
private RecipeController controller;
private List<Recipe> recipes;
private final RecipeRepository recipeRepository;
private List<Long> recipeIds;
public static final int NUM_RECIPES = 10;
private final IngredientRepository ingredientRepository;
private final RecipeIngredientRepository recipeIngredientRepository;
// Injects a test repository into the test class
@Autowired
public RecipeControllerTest(
RecipeService recipeService,
RecipeRepository recipeRepository,
SimpMessagingTemplate template,
IngredientRepository ingredientRepository,
RecipeIngredientRepository recipeIngredientRepository
SimpMessagingTemplate template
) {
this.recipeService = recipeService;
this.recipeRepository = recipeRepository;
this.template = template;
this.ingredientRepository = ingredientRepository;
this.recipeIngredientRepository = recipeIngredientRepository;
}
@BeforeEach
@ -71,10 +67,8 @@ public class RecipeControllerTest {
.mapToObj(x -> new Recipe(null, "Recipe " + x, List.of(), List.of()))
.toList();
controller = new RecipeController(
recipeRepository,
template,
ingredientRepository,
recipeIngredientRepository
recipeService,
template
);
Set<String> tags = info.getTags();
List<Long> ids = new ArrayList<>();