diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java index 32f2f70..90877dd 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java @@ -79,21 +79,23 @@ public class RecipeControllerTests { get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug()) ) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(1)) - .andExpect(jsonPath("$.created").exists()) // TODO: better matching of exact LocalDateTime - .andExpect(jsonPath("$.modified").doesNotExist()) - .andExpect(jsonPath("$.slug").value(recipe.getSlug())) - .andExpect(jsonPath("$.title").value("Test Recipe")) - .andExpect(jsonPath("$.preparationTime").value(recipe.getPreparationTime())) - .andExpect(jsonPath("$.cookingTime").value(recipe.getCookingTime())) - .andExpect(jsonPath("$.totalTime").value(recipe.getTotalTime())) - .andExpect(jsonPath("$.text").value("

Hello, World!

")) - .andExpect(jsonPath("$.owner.id").value(owner.getId())) - .andExpect(jsonPath("$.owner.username").value(owner.getUsername())) - .andExpect(jsonPath("$.starCount").value(0)) + .andExpect(jsonPath("$.recipe.id").value(1)) + .andExpect(jsonPath("$.recipe.created").exists()) // TODO: better matching of exact LocalDateTime + .andExpect(jsonPath("$.recipe.modified").doesNotExist()) + .andExpect(jsonPath("$.recipe.slug").value(recipe.getSlug())) + .andExpect(jsonPath("$.recipe.title").value("Test Recipe")) + .andExpect(jsonPath("$.recipe.preparationTime").value(recipe.getPreparationTime())) + .andExpect(jsonPath("$.recipe.cookingTime").value(recipe.getCookingTime())) + .andExpect(jsonPath("$.recipe.totalTime").value(recipe.getTotalTime())) + .andExpect(jsonPath("$.recipe.text").value("

Hello, World!

")) + .andExpect(jsonPath("$.recipe.owner.id").value(owner.getId())) + .andExpect(jsonPath("$.recipe.owner.username").value(owner.getUsername())) + .andExpect(jsonPath("$.recipe.starCount").value(0)) + .andExpect(jsonPath("$.recipe.viewerCount").value(0)) + .andExpect(jsonPath("$.recipe.isPublic").value(true)) .andExpect(jsonPath("$.isStarred").value(nullValue())) - .andExpect(jsonPath("$.viewerCount").value(0)) - .andExpect(jsonPath("$.isPublic").value(true)); + .andExpect(jsonPath("$.isOwner").value(nullValue())); + } @Test diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java index a38fe98..9ec3f9c 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java @@ -4,7 +4,6 @@ import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; 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.RecipeInfoView; import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.UserEntity; @@ -24,7 +23,6 @@ import static app.mealsmadeeasy.api.recipe.ContainsRecipeInfoViewsForRecipesMatc import static app.mealsmadeeasy.api.recipe.ContainsRecipesMatcher.containsRecipes; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -352,44 +350,4 @@ public class RecipeServiceTests { assertThrows(AccessDeniedException.class, () -> this.recipeService.deleteRecipe(toDelete.getId(), notOwner)); } - @Test - @DirtiesContext - public void getFullViewByUsernameAndSlugIncludesStarredFalseWhenViewerNotNullButNotStarer() throws RecipeException { - final User owner = this.createTestUser("recipeOwner"); - final Recipe recipe = this.createTestRecipe(owner); - final FullRecipeView view = this.recipeService.getFullViewByUsernameAndSlug( - owner.getUsername(), - recipe.getSlug(), - owner - ); - assertThat(view.getIsStarred(), is(false)); - } - - @Test - @DirtiesContext - public void getFullViewByUsernameAndSlugIncludesStarredTrueWhenViewerNotNullAndStarer() throws RecipeException { - final User owner = this.createTestUser("recipeOwner"); - final Recipe recipe = this.createTestRecipe(owner); - this.recipeStarService.create(recipe.getId(), owner.getUsername()); - final FullRecipeView view = this.recipeService.getFullViewByUsernameAndSlug( - owner.getUsername(), - recipe.getSlug(), - owner - ); - assertThat(view.getIsStarred(), is(true)); - } - - @Test - @DirtiesContext - public void getFullViewByUsernameIncludesStarredNullWhenViewerNull() throws RecipeException { - final User owner = this.createTestUser("recipeOwner"); - final Recipe recipe = this.createTestRecipe(owner, true); - final FullRecipeView view = this.recipeService.getFullViewByUsernameAndSlug( - owner.getUsername(), - recipe.getSlug(), - null - ); - assertThat(view.getIsStarred(), is(nullValue())); - } - } diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java index 0f14670..bba59a3 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java @@ -57,7 +57,14 @@ public class RecipeStarRepositoryTests { starDraft.setId(starId); this.recipeStarRepository.save(starDraft); - assertThat(this.recipeStarRepository.isStarer(recipe.getId(), owner.getUsername()), is(true)); + assertThat( + this.recipeStarRepository.isStarer( + recipe.getOwner().getUsername(), + recipe.getSlug(), + owner.getUsername() + ), + is(true) + ); } @Test @@ -65,7 +72,14 @@ public class RecipeStarRepositoryTests { public void returnsFalseIfNotStarer() { final UserEntity owner = this.getOwnerUser(); final RecipeEntity recipe = this.getTestRecipe(owner); - assertThat(this.recipeStarRepository.isStarer(recipe.getId(), owner.getUsername()), is(false)); + assertThat( + this.recipeStarRepository.isStarer( + recipe.getOwner().getUsername(), + recipe.getSlug(), + owner.getUsername() + ), + is(false) + ); } } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java index 1fc017a..32f935b 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java @@ -43,13 +43,17 @@ public class RecipeController { } @GetMapping("/{username}/{slug}") - public ResponseEntity getById( + public ResponseEntity> getByUsernameAndSlug( @PathVariable String username, @PathVariable String slug, @AuthenticationPrincipal User viewer - ) - throws RecipeException { - return ResponseEntity.ok(this.recipeService.getFullViewByUsernameAndSlug(username, slug, viewer)); + ) throws RecipeException { + final FullRecipeView recipe = this.recipeService.getFullViewByUsernameAndSlug(username, slug, viewer); + final Map body = new HashMap<>(); + body.put("recipe", recipe); + body.put("isStarred", this.recipeService.isStarer(username, slug, viewer)); + body.put("isOwner", this.recipeService.isOwner(username, slug, viewer)); + return ResponseEntity.ok(body); } @GetMapping diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java index 5f394a6..a481d04 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java @@ -5,6 +5,7 @@ import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; import app.mealsmadeeasy.api.recipe.view.FullRecipeView; import app.mealsmadeeasy.api.recipe.view.RecipeInfoView; import app.mealsmadeeasy.api.user.User; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -36,4 +37,10 @@ public interface RecipeService { void deleteRecipe(long id, User modifier); + @Contract("_, _, null -> null") + @Nullable Boolean isStarer(String username, String slug, @Nullable User viewer); + + @Contract("_, _, null -> null") + @Nullable Boolean isOwner(String username, String slug, @Nullable User viewer); + } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java index 3ca6cd6..b402952 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java @@ -130,9 +130,6 @@ public class RecipeServiceImpl implements RecipeService { recipe, this.getRenderedMarkdown(recipe), this.getStarCount(recipe), - viewer != null - ? this.recipeStarRepository.isStarer(recipe.getId(), viewer.getUsername()) - : null, this.getViewerCount(recipe.getId()), this.getImageView(recipe.getMainImage(), viewer) ); @@ -274,4 +271,24 @@ public class RecipeServiceImpl implements RecipeService { this.recipeRepository.deleteById(id); } + @Override + @PreAuthorize("@recipeSecurity.isViewableBy(#username, #slug, #viewer)") + @Contract("_, _, null -> null") + public @Nullable Boolean isStarer(String username, String slug, @Nullable User viewer) { + if (viewer == null) { + return null; + } + return this.recipeStarRepository.isStarer(username, slug, viewer.getUsername()); + } + + @Override + @PreAuthorize("@recipeSecurity.isViewableBy(#username, #slug, #viewer)") + @Contract("_, _, null -> null") + public @Nullable Boolean isOwner(String username, String slug, @Nullable User viewer) { + if (viewer == null) { + return null; + } + return viewer.getUsername().equals(username); + } + } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java index 1d160b1..6434174 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java @@ -12,8 +12,8 @@ public interface RecipeStarRepository extends JpaRepository findByRecipeIdAndOwnerUsername(Long recipeId, String username); - @Query("SELECT count(rs) > 0 FROM RecipeStar rs WHERE rs.id.recipeId = ?1 AND rs.id.ownerUsername = ?2") - boolean isStarer(long recipeId, String username); + @Query("SELECT count(rs) > 0 FROM RecipeStar rs, Recipe r WHERE r.owner.username = ?1 AND r.slug = ?2 AND r.id = rs.id.recipeId AND rs.id.ownerUsername = ?3") + boolean isStarer(String ownerUsername, String slug, String viewerUsername); @Modifying @Transactional diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java b/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java index e91009d..c64278f 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java @@ -13,7 +13,6 @@ public class FullRecipeView { Recipe recipe, String renderedText, int starCount, - Boolean starred, int viewerCount, ImageView mainImage ) { @@ -29,7 +28,6 @@ public class FullRecipeView { view.setText(renderedText); view.setOwner(UserInfoView.from(recipe.getOwner())); view.setStarCount(starCount); - view.setIsStarred(starred); view.setViewerCount(viewerCount); view.setMainImage(mainImage); view.setIsPublic(recipe.isPublic()); @@ -47,7 +45,6 @@ public class FullRecipeView { private String text; private UserInfoView owner; private int starCount; - private @Nullable Boolean starred; private int viewerCount; private ImageView mainImage; private boolean isPublic; @@ -140,14 +137,6 @@ public class FullRecipeView { this.starCount = starCount; } - public @Nullable Boolean getIsStarred() { - return this.starred; - } - - public void setIsStarred(@Nullable Boolean starred) { - this.starred = starred; - } - public int getViewerCount() { return this.viewerCount; }