From e23526dbcc5094586ebc1e7e6f6ac806ed666047 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Tue, 13 Aug 2024 11:13:24 -0500 Subject: [PATCH] Added addStar to RecipeController and related. --- .../api/recipe/RecipeControllerTests.java | 18 +++++ .../recipe/star/RecipeStarServiceTests.java | 75 +++++++++++++++++++ .../api/recipe/RecipeController.java | 15 +++- .../api/recipe/RecipeService.java | 2 + .../api/recipe/RecipeServiceImpl.java | 9 +++ .../api/recipe/star/RecipeStarService.java | 2 + .../recipe/star/RecipeStarServiceImpl.java | 21 +++++- 7 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceTests.java diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java index 69dfa0e..05bef73 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java @@ -15,6 +15,7 @@ import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.Matchers.hasSize; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -125,4 +126,21 @@ public class RecipeControllerTests { .andExpect(jsonPath("$.content", hasSize(3))); } + @Test + @DirtiesContext + public void addStarToRecipe() throws Exception { + final User owner = this.createTestUser("recipe-owner"); + final User starer = this.createTestUser("recipe-starer"); + final Recipe recipe = this.createTestRecipe(owner, true); + final String accessToken = this.authService.login(starer.getUsername(), "test") + .getAccessToken() + .getToken(); + this.mockMvc.perform( + post("/recipes/{username}/{slug}/stars", recipe.getOwner().getUsername(), recipe.getSlug()) + .header("Authorization", "Bearer " + accessToken) + ) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.date").exists()); + } + } diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceTests.java new file mode 100644 index 0000000..70d3a64 --- /dev/null +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceTests.java @@ -0,0 +1,75 @@ +package app.mealsmadeeasy.api.recipe.star; + +import app.mealsmadeeasy.api.recipe.Recipe; +import app.mealsmadeeasy.api.recipe.RecipeService; +import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; +import app.mealsmadeeasy.api.user.User; +import app.mealsmadeeasy.api.user.UserCreateException; +import app.mealsmadeeasy.api.user.UserService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +@SpringBootTest +public class RecipeStarServiceTests { + + @Autowired + private RecipeStarService recipeStarService; + + @Autowired + private UserService userService; + + @Autowired + private RecipeService recipeService; + + private User getTestUser(String username) { + try { + return this.userService.createUser(username, username + "@test.com", "test"); + } catch (UserCreateException e) { + throw new RuntimeException(e); + } + } + + private Recipe getTestRecipe(User owner, String slug, boolean isPublic) { + final RecipeCreateSpec spec = new RecipeCreateSpec(); + spec.setSlug(slug); + spec.setTitle("Test Recipe"); + spec.setRawText("My great recipe has five ingredients."); + spec.setPublic(isPublic); + return this.recipeService.create(owner, spec); + } + + @Test + @DirtiesContext + public void createViaUsernameAndSlug() { + final User owner = this.getTestUser("recipe-owner"); + final User starer = this.getTestUser("recipe-starer"); + final Recipe recipe = this.getTestRecipe(owner, "test-recipe", true); + final RecipeStar star = assertDoesNotThrow(() -> this.recipeStarService.create( + recipe.getOwner().getUsername(), + recipe.getSlug(), + starer + )); + assertThat(star.getDate(), is(notNullValue())); + } + + @Test + @DirtiesContext + public void createViaId() { + final User owner = this.getTestUser("recipe-owner"); + final User starer = this.getTestUser("recipe-starer"); + final Recipe recipe = this.getTestRecipe(owner, "test-recipe", true); + final RecipeStar star = assertDoesNotThrow(() -> this.recipeStarService.create( + recipe.getId(), + starer.getUsername() + )); + assertThat(star.getDate(), is(notNullValue())); + } + +} diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java index 0cc3879..caa4f6e 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java @@ -1,5 +1,7 @@ package app.mealsmadeeasy.api.recipe; +import app.mealsmadeeasy.api.recipe.star.RecipeStar; +import app.mealsmadeeasy.api.recipe.star.RecipeStarService; import app.mealsmadeeasy.api.recipe.view.FullRecipeView; import app.mealsmadeeasy.api.recipe.view.RecipeExceptionView; import app.mealsmadeeasy.api.recipe.view.RecipeInfoView; @@ -19,9 +21,11 @@ import java.util.Map; public class RecipeController { private final RecipeService recipeService; + private final RecipeStarService recipeStarService; - public RecipeController(RecipeService recipeService) { + public RecipeController(RecipeService recipeService, RecipeStarService recipeStarService) { this.recipeService = recipeService; + this.recipeStarService = recipeStarService; } @ExceptionHandler(RecipeException.class) @@ -61,4 +65,13 @@ public class RecipeController { return ResponseEntity.ok(view); } + @PostMapping("/{username}/{slug}/stars") + public ResponseEntity addStar( + @PathVariable String username, + @PathVariable String slug, + @AuthenticationPrincipal User principal + ) throws RecipeException { + return ResponseEntity.status(HttpStatus.CREATED).body(this.recipeStarService.create(username, slug, principal)); + } + } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java index 19d87e5..5f394a6 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java @@ -17,6 +17,8 @@ public interface RecipeService { Recipe getById(long id, @Nullable User viewer) throws RecipeException; Recipe getByIdWithStars(long id, @Nullable User viewer) throws RecipeException; + Recipe getByUsernameAndSlug(String username, String slug, @Nullable User viewer) throws RecipeException; + FullRecipeView getFullViewById(long id, @Nullable User viewer) throws RecipeException; FullRecipeView getFullViewByUsernameAndSlug(String username, String slug, @Nullable User viewer) throws RecipeException; diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java index bd16fbb..91ba888 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java @@ -85,6 +85,15 @@ public class RecipeServiceImpl implements RecipeService { )); } + @Override + @PostAuthorize("@recipeSecurity.isViewableBy(returnObject, #viewer)") + public Recipe getByUsernameAndSlug(String username, String slug, @Nullable User viewer) throws RecipeException { + return this.recipeRepository.findByOwnerUsernameAndSlug(username, slug).orElseThrow(() -> new RecipeException( + RecipeException.Type.INVALID_USERNAME_OR_SLUG, + "No such Recipe for username " + username + " and slug " + slug + )); + } + private String getRenderedMarkdown(RecipeEntity entity) { if (entity.getCachedRenderedText() == null) { entity.setCachedRenderedText(renderAndCleanMarkdown(entity.getRawText())); diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java index d63694e..a7a7afc 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java @@ -1,9 +1,11 @@ package app.mealsmadeeasy.api.recipe.star; import app.mealsmadeeasy.api.recipe.RecipeException; +import app.mealsmadeeasy.api.user.User; public interface RecipeStarService { RecipeStar create(long recipeId, String ownerUsername); + RecipeStar create(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException; RecipeStar get(long recipeId, String ownerUsername) throws RecipeException; void delete(long recipeId, String ownerUsername); } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceImpl.java index add871c..f7d4931 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceImpl.java @@ -1,17 +1,23 @@ package app.mealsmadeeasy.api.recipe.star; +import app.mealsmadeeasy.api.recipe.Recipe; import app.mealsmadeeasy.api.recipe.RecipeException; +import app.mealsmadeeasy.api.recipe.RecipeService; +import app.mealsmadeeasy.api.user.User; import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.util.Optional; @Service public class RecipeStarServiceImpl implements RecipeStarService { private final RecipeStarRepository recipeStarRepository; + private final RecipeService recipeService; - public RecipeStarServiceImpl(RecipeStarRepository recipeStarRepository) { + public RecipeStarServiceImpl(RecipeStarRepository recipeStarRepository, RecipeService recipeService) { this.recipeStarRepository = recipeStarRepository; + this.recipeService = recipeService; } @Override @@ -25,6 +31,19 @@ public class RecipeStarServiceImpl implements RecipeStarService { return this.recipeStarRepository.save(draft); } + @Override + public RecipeStar create(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException { + final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer); + final Optional existing = this.recipeStarRepository.findByRecipeIdAndOwnerUsername( + recipe.getId(), + starer.getUsername() + ); + if (existing.isPresent()) { + return existing.get(); + } + return this.create(recipe.getId(), starer.getUsername()); + } + @Override public RecipeStar get(long recipeId, String ownerUsername) throws RecipeException { return this.recipeStarRepository.findByRecipeIdAndOwnerUsername(recipeId, ownerUsername).orElseThrow(