From 341133f7792e8b9d8a85a2bb4da24c1f47e7044d Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Sun, 28 Jul 2024 14:52:00 -0500 Subject: [PATCH] Massive refactor of RecipeService and related. All tests passing. --- .../api/recipe/RecipeControllerTests.java | 15 +- .../api/recipe/RecipeServiceTests.java | 263 ++++++---------- .../mealsmadeeasy/api/DevConfiguration.java | 14 +- .../app/mealsmadeeasy/api/recipe/Recipe.java | 2 + .../api/recipe/RecipeController.java | 6 +- .../api/recipe/RecipeEntity.java | 16 +- .../api/recipe/RecipeException.java | 2 +- .../api/recipe/RecipeRepository.java | 9 +- .../api/recipe/RecipeSecurity.java | 2 +- .../api/recipe/RecipeSecurityImpl.java | 9 +- .../api/recipe/RecipeService.java | 57 +--- .../api/recipe/RecipeServiceImpl.java | 293 ++++++------------ .../comment/RecipeCommentCreateSpec.java | 15 + .../recipe/comment/RecipeCommentService.java | 11 + .../comment/RecipeCommentServiceImpl.java | 75 +++++ .../comment/RecipeCommentUpdateSpec.java | 15 + .../api/recipe/spec/RecipeCreateSpec.java | 45 +++ .../api/recipe/spec/RecipeUpdateSpec.java | 45 +++ .../api/recipe/star/RecipeStar.java | 6 - .../api/recipe/star/RecipeStarEntity.java | 43 +-- .../api/recipe/star/RecipeStarId.java | 36 +++ .../api/recipe/star/RecipeStarRepository.java | 14 +- .../api/recipe/star/RecipeStarService.java | 9 + .../recipe/star/RecipeStarServiceImpl.java | 43 +++ ...ecipePageView.java => FullRecipeView.java} | 2 +- .../api/matchers/ContainsItemsMatcher.java | 20 +- .../recipe/ContainsRecipeStarsMatcher.java | 13 +- 27 files changed, 593 insertions(+), 487 deletions(-) create mode 100644 src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentCreateSpec.java create mode 100644 src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentService.java create mode 100644 src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentServiceImpl.java create mode 100644 src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentUpdateSpec.java create mode 100644 src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeCreateSpec.java create mode 100644 src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java create mode 100644 src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarId.java create mode 100644 src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java create mode 100644 src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceImpl.java rename src/main/java/app/mealsmadeeasy/api/recipe/view/{RecipePageView.java => FullRecipeView.java} (98%) diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java index 6a2be03..2a65832 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java @@ -1,5 +1,6 @@ package app.mealsmadeeasy.api.recipe; +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; @@ -36,16 +37,19 @@ public class RecipeControllerTests { } } - private Recipe createTestRecipe(User owner) { - return this.recipeService.create(owner, "Test Recipe", "# Hello, World!"); + private Recipe createTestRecipe(User owner, boolean isPublic) { + final RecipeCreateSpec spec = new RecipeCreateSpec(); + spec.setTitle("Test Recipe"); + spec.setRawText("# Hello, World!"); + spec.setPublic(isPublic); + return this.recipeService.create(owner, spec); } @Test @DirtiesContext public void getRecipePageViewByIdPublicRecipeNoPrincipal() throws Exception { final User owner = this.createTestUser("owner"); - final Recipe recipe = this.createTestRecipe(owner); - this.recipeService.setPublic(recipe, owner, true); + final Recipe recipe = this.createTestRecipe(owner, true); this.mockMvc.perform(get("/recipes/{id}", recipe.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1)) @@ -61,8 +65,7 @@ public class RecipeControllerTests { @DirtiesContext public void getRecipeInfoViewsNoPrincipal() throws Exception { final User owner = this.createTestUser("owner"); - final Recipe recipe = this.createTestRecipe(owner); - this.recipeService.setPublic(recipe, owner, true); + final Recipe recipe = this.createTestRecipe(owner, true); this.mockMvc.perform(get("/recipes")) .andExpect(status().isOk()) .andExpect(jsonPath("$.slice.number").value(0)) diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java index 762dfdf..b188132 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java @@ -1,10 +1,13 @@ package app.mealsmadeeasy.api.recipe; -import app.mealsmadeeasy.api.user.IsUserMatcher; +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.user.User; import app.mealsmadeeasy.api.user.UserEntity; import app.mealsmadeeasy.api.user.UserRepository; +import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -14,16 +17,20 @@ import org.springframework.test.annotation.DirtiesContext; import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +// TODO: test mainImage included @SpringBootTest public class RecipeServiceTests { @Autowired private RecipeService recipeService; + @Autowired + private RecipeStarService recipeStarService; + @Autowired private UserRepository userRepository; @@ -35,25 +42,26 @@ public class RecipeServiceTests { return this.userRepository.save(draft); } - private Recipe createTestRecipe(User owner) { - return this.recipeService.create(owner, "My Recipe" , "Hello!"); + private Recipe createTestRecipe(@Nullable User owner) { + return this.createTestRecipe(owner, false); + } + + private Recipe createTestRecipe(@Nullable User owner, boolean isPublic) { + final RecipeCreateSpec spec = new RecipeCreateSpec(); + spec.setTitle("My Recipe"); + spec.setRawText("Hello!"); + spec.setPublic(isPublic); + return this.recipeService.create(owner, spec); } @Test - @DirtiesContext - public void createViaUsername() throws RecipeException { - final User user = this.createTestUser("recipeOwner"); - final Recipe recipe = this.recipeService.create(user.getUsername(), "My Recipe" , "Hello!"); - assertThat(recipe.getOwner(), is(user)); - assertThat(recipe.getTitle(), is("My Recipe")); - assertThat(recipe.getRawText(), is("Hello!")); - } + public void smokeScreen() {} @Test @DirtiesContext - public void createViaUser() { + public void create() { final User user = this.createTestUser("recipeOwner"); - final Recipe recipe = this.recipeService.create(user, "My Recipe", "Hello!"); + final Recipe recipe = this.createTestRecipe(user); assertThat(recipe.getOwner().getUsername(), is(user.getUsername())); assertThat(recipe.getTitle(), is("My Recipe")); assertThat(recipe.getRawText(), is("Hello!")); @@ -61,22 +69,36 @@ public class RecipeServiceTests { @Test @DirtiesContext - public void getByIdPublic() throws RecipeException { - final User owner = this.createTestUser("recipeOwner"); - Recipe recipe = this.createTestRecipe(owner); - recipe = this.recipeService.setPublic(recipe, owner, true); - final Recipe byId = this.recipeService.getById(recipe.getId()); - assertThat(byId.getId(), is(recipe.getId())); - assertThat(byId.getTitle(), is("My Recipe")); - assertThat(byId.getRawText(), is("Hello!")); + public void createWithoutOwnerThrowsAccessDenied() { + assertThrows(AccessDeniedException.class, () -> this.recipeService.create(null, new RecipeCreateSpec())); } @Test @DirtiesContext - public void getByIdThrowsWhenNotPublic() { + public void getByIdPublicNoViewerDoesNotThrow() { final User owner = this.createTestUser("recipeOwner"); - final Recipe recipe = this.createTestRecipe(owner); - assertThrows(AccessDeniedException.class, () -> this.recipeService.getById(recipe.getId())); + final Recipe recipe = this.createTestRecipe(owner, true); + assertDoesNotThrow(() -> this.recipeService.getById(recipe.getId(), null)); + } + + @Test + @DirtiesContext + public void getByIdHasCorrectProperties() throws RecipeException { + final User owner = this.createTestUser("recipeOwner"); + final Recipe recipe = this.createTestRecipe(owner, true); + final Recipe byId = this.recipeService.getById(recipe.getId(), null); + assertThat(byId.getId(), is(recipe.getId())); + assertThat(byId.getTitle(), is("My Recipe")); + assertThat(byId.getRawText(), is("Hello!")); + assertThat(byId.isPublic(), is(true)); + } + + @Test + @DirtiesContext + public void getByIdThrowsWhenNotPublicAndNoViewer() { + final User owner = this.createTestUser("recipeOwner"); + final Recipe recipe = this.createTestRecipe(owner, false); // not public + assertThrows(AccessDeniedException.class, () -> this.recipeService.getById(recipe.getId(), null)); } @Test @@ -90,11 +112,10 @@ public class RecipeServiceTests { @Test @DirtiesContext - public void getByIdOkayWhenPublic() { + public void getByIdOkayWhenPublicAndNoViewer() { final User owner = this.createTestUser("recipeOwner"); - final Recipe notYetPublicRecipe = this.createTestRecipe(owner); - final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, owner, true); - assertDoesNotThrow(() -> this.recipeService.getById(publicRecipe.getId())); + final Recipe recipe = this.createTestRecipe(owner, true); + assertDoesNotThrow(() -> this.recipeService.getById(recipe.getId(), null)); } @Test @@ -102,25 +123,25 @@ public class RecipeServiceTests { public void getByIdOkayWhenPublicRecipeWithViewer() { final User owner = this.createTestUser("recipeOwner"); final User viewer = this.createTestUser("viewer"); - final Recipe notYetPublicRecipe = this.createTestRecipe(owner); - final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, owner, true); - assertDoesNotThrow(() -> this.recipeService.getById(publicRecipe.getId(), viewer)); + final Recipe recipe = this.createTestRecipe(owner, true); + assertDoesNotThrow(() -> this.recipeService.getById(recipe.getId(), viewer)); } @Test @DirtiesContext - public void getByIdWithStarsPublic() throws RecipeException { + public void getByIdOkayWithStarsPublicAndNoViewer() { final User owner = this.createTestUser("recipeOwner"); - Recipe recipe = this.createTestRecipe(owner); - recipe = this.recipeService.setPublic(recipe, owner, true); - final RecipeStar star = this.recipeService.addStar(recipe, owner); - final Recipe byIdWithStars = this.recipeService.getByIdWithStars(recipe.getId()); + final Recipe recipe = this.createTestRecipe(owner, true); + final RecipeStar star = this.recipeStarService.create(recipe.getId(), owner.getUsername()); + final Recipe byIdWithStars = assertDoesNotThrow(() -> this.recipeService.getByIdWithStars( + recipe.getId(), null + )); assertThat(byIdWithStars.getStars(), ContainsRecipeStarsMatcher.containsStars(star)); } @Test @DirtiesContext - public void getByIdWithStarsThrowsWhenNotViewer() { + public void getByIdOkayWithStarsThrowsWhenNotViewer() { final User owner = this.createTestUser("recipeOwner"); final User notViewer = this.createTestUser("notViewer"); final Recipe recipe = this.createTestRecipe(owner); @@ -129,46 +150,35 @@ public class RecipeServiceTests { @Test @DirtiesContext - public void getByIdWithStarsOkayWhenPublic() { - final User owner = this.createTestUser("recipeOwner"); - final Recipe notYetPublicRecipe = this.createTestRecipe(owner); - final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, owner, true); - assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(publicRecipe.getId())); - } - - @Test - @DirtiesContext - public void getByIdWithStarsOkayWhenPublicRecipeWithViewer() { + public void getByIdWithStarsOkayWhenPublicRecipeWithViewer() throws RecipeException { final User owner = this.createTestUser("recipeOwner"); final User viewer = this.createTestUser("viewer"); final Recipe notYetPublicRecipe = this.createTestRecipe(owner); - final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, owner, true); + final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(); + updateSpec.setPublic(true); + final Recipe publicRecipe = this.recipeService.update(notYetPublicRecipe.getId(), updateSpec, owner); assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(publicRecipe.getId(), viewer)); } @Test @DirtiesContext - public void getByMinimumStarsAllPublic() throws RecipeException { + public void getByMinimumStarsAllPublic() { final User owner = this.createTestUser("recipeOwner"); final User u0 = this.createTestUser("u0"); final User u1 = this.createTestUser("u1"); - Recipe r0 = this.createTestRecipe(owner); - Recipe r1 = this.createTestRecipe(owner); - Recipe r2 = this.createTestRecipe(owner); - - r0 = this.recipeService.setPublic(r0, owner, true); - r1 = this.recipeService.setPublic(r1, owner, true); - r2 = this.recipeService.setPublic(r2, owner, true); + final Recipe r0 = this.createTestRecipe(owner, true); + final Recipe r1 = this.createTestRecipe(owner, true); + final Recipe r2 = this.createTestRecipe(owner, true); // r0.stars = 0, r1.stars = 1, r2.stars = 2 - this.recipeService.addStar(r1, u0); - this.recipeService.addStar(r2, u0); - this.recipeService.addStar(r2, u1); + this.recipeStarService.create(r1.getId(), u0.getUsername()); + this.recipeStarService.create(r2.getId(), u0.getUsername()); + this.recipeStarService.create(r2.getId(), u1.getUsername()); - final List zeroStars = this.recipeService.getByMinimumStars(0); - final List oneStar = this.recipeService.getByMinimumStars(1); - final List twoStars = this.recipeService.getByMinimumStars(2); + final List zeroStars = this.recipeService.getByMinimumStars(0, null); + final List oneStar = this.recipeService.getByMinimumStars(1, null); + final List twoStars = this.recipeService.getByMinimumStars(2, null); assertThat(zeroStars.size(), is(3)); assertThat(oneStar.size(), is(2)); @@ -187,20 +197,20 @@ public class RecipeServiceTests { final User u1 = this.createTestUser("u1"); final User viewer = this.createTestUser("recipeViewer"); - Recipe r0 = this.createTestRecipe(owner); + Recipe r0 = this.createTestRecipe(owner); // not public Recipe r1 = this.createTestRecipe(owner); Recipe r2 = this.createTestRecipe(owner); for (final User starer : List.of(u0, u1)) { - r0 = this.recipeService.addViewer(r0, starer); - r1 = this.recipeService.addViewer(r1, starer); - r2 = this.recipeService.addViewer(r2, starer); + r0 = this.recipeService.addViewer(r0.getId(), owner, starer); + r1 = this.recipeService.addViewer(r1.getId(), owner, starer); + r2 = this.recipeService.addViewer(r2.getId(), owner, starer); } // r0.stars = 0, r1.stars = 1, r2.stars = 2 - this.recipeService.addStar(r1, u0); - this.recipeService.addStar(r2, u0); - this.recipeService.addStar(r2, u1); + this.recipeStarService.create(r1.getId(), u0.getUsername()); + this.recipeStarService.create(r2.getId(), u0.getUsername()); + this.recipeStarService.create(r2.getId(), u1.getUsername()); final List zeroStarsNoneViewable = this.recipeService.getByMinimumStars(0, viewer); final List oneStarNoneViewable = this.recipeService.getByMinimumStars(1, viewer); @@ -211,9 +221,9 @@ public class RecipeServiceTests { assertThat(twoStarsNoneViewable.size(), is(0)); // Now make them viewable - r0 = this.recipeService.addViewer(r0, viewer); - r1 = this.recipeService.addViewer(r1, viewer); - r2 = this.recipeService.addViewer(r2, viewer); + r0 = this.recipeService.addViewer(r0.getId(), owner, viewer); + r1 = this.recipeService.addViewer(r1.getId(), owner, viewer); + r2 = this.recipeService.addViewer(r2.getId(), owner, viewer); final List zeroStarsViewable = this.recipeService.getByMinimumStars(0, viewer); final List oneStarViewable = this.recipeService.getByMinimumStars(1, viewer); @@ -230,14 +240,11 @@ public class RecipeServiceTests { @Test @DirtiesContext - public void getPublicRecipes() throws RecipeException { + public void getPublicRecipes() { final User owner = this.createTestUser("recipeOwner"); - Recipe r0 = this.createTestRecipe(owner); - Recipe r1 = this.createTestRecipe(owner); - - r0 = this.recipeService.setPublic(r0, owner, true); - r1 = this.recipeService.setPublic(r1, owner, true); + Recipe r0 = this.createTestRecipe(owner, true); + Recipe r1 = this.createTestRecipe(owner, true); final List publicRecipes = this.recipeService.getPublicRecipes(); assertThat(publicRecipes.size(), is(2)); @@ -251,7 +258,7 @@ public class RecipeServiceTests { final User viewer = this.createTestUser("recipeViewer"); Recipe r0 = this.createTestRecipe(owner); - r0 = this.recipeService.addViewer(r0, viewer); + r0 = this.recipeService.addViewer(r0.getId(), owner, viewer); final List viewableRecipes = this.recipeService.getRecipesViewableBy(viewer); assertThat(viewableRecipes.size(), is(1)); assertThat(viewableRecipes, ContainsRecipesMatcher.containsRecipes(r0)); @@ -269,33 +276,16 @@ public class RecipeServiceTests { @Test @DirtiesContext - public void getRenderedMarkdown() { + public void updateRawText() throws RecipeException { final User owner = this.createTestUser("recipeOwner"); - final Recipe recipe = this.recipeService.create( - owner, "My Recipe", "# A Heading" - ); - final String rendered = this.recipeService.getRenderedMarkdown(recipe, owner); - assertThat(rendered, is("

A Heading

")); - } - - @Test - @DirtiesContext - public void getRenderedMarkThrowsIfNotViewable() { - final User owner = this.createTestUser("recipeOwner"); - final User notViewer = this.createTestUser("notViewer"); - final Recipe recipe = this.createTestRecipe(owner); - assertThrows(AccessDeniedException.class, () -> this.recipeService.getRenderedMarkdown(recipe, notViewer)); - } - - @Test - @DirtiesContext - public void updateRawText() { - final User owner = this.createTestUser("recipeOwner"); - Recipe recipe = this.recipeService.create( - owner, "My Recipe", "# A Heading" - ); + final RecipeCreateSpec createSpec = new RecipeCreateSpec(); + createSpec.setTitle("My Recipe"); + createSpec.setRawText("# A Heading"); + Recipe recipe = this.recipeService.create(owner, createSpec); final String newRawText = "# A Heading\n## A Subheading"; - recipe = this.recipeService.updateRawText(recipe, owner, newRawText); + final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(); + updateSpec.setRawText(newRawText); + recipe = this.recipeService.update(recipe.getId(), updateSpec, owner); assertThat(recipe.getRawText(), is(newRawText)); } @@ -305,71 +295,20 @@ public class RecipeServiceTests { final User owner = this.createTestUser("recipeOwner"); final User notOwner = this.createTestUser("notOwner"); final Recipe recipe = this.createTestRecipe(owner); + final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(); + updateSpec.setRawText("should fail"); assertThrows( AccessDeniedException.class, - () -> this.recipeService.updateRawText(recipe, notOwner, "should fail") + () -> this.recipeService.update(recipe.getId(), updateSpec, notOwner) ); } - @Test - @DirtiesContext - public void updateOwnerViaUser() throws RecipeException { - final User firstOwner = this.createTestUser("firstOwner"); - final User secondOwner = this.createTestUser("secondOwner"); - Recipe recipe = this.createTestRecipe(firstOwner); - recipe = this.recipeService.updateOwner(recipe, firstOwner, secondOwner); - assertThat(recipe.getOwner(), IsUserMatcher.isUser(secondOwner)); - } - - @Test - @DirtiesContext - public void updateOwnerViaUserThrowsIfNotOwner() { - final User actualOwner = this.createTestUser("u0"); - final User notOwner = this.createTestUser("u1"); - final User target = this.createTestUser("u2"); - final Recipe recipe = this.createTestRecipe(actualOwner); - assertThrows(AccessDeniedException.class, () -> this.recipeService.updateOwner(recipe, notOwner, target)); - } - - @Test - @DirtiesContext - public void addStar() throws RecipeException { - final User owner = this.createTestUser("recipeOwner"); - final User starer = this.createTestUser("starer"); - Recipe recipe = this.createTestRecipe(owner); - recipe = this.recipeService.addViewer(recipe, starer); - final RecipeStar star = this.recipeService.addStar(recipe, starer); - assertThat(star.getRecipe(), IsRecipeMatcher.isRecipe(recipe)); - assertThat(star.getOwner(), IsUserMatcher.isUser(starer)); - } - - @Test - @DirtiesContext - public void addStarWhenNotViewableThrows() { - final User notViewer = this.createTestUser("notViewer"); - final Recipe recipe = this.createTestRecipe(this.createTestUser("recipeOwner")); - assertThrows(AccessDeniedException.class, () -> this.recipeService.addStar(recipe, notViewer)); - } - - @Test - @DirtiesContext - public void deleteStar() throws RecipeException { - final User owner = this.createTestUser("recipeOwner"); - final User starer = this.createTestUser("starer"); - Recipe recipe = this.createTestRecipe(owner); - recipe = this.recipeService.addViewer(recipe, starer); - final RecipeStar star = this.recipeService.addStar(recipe, starer); - this.recipeService.deleteStar(star); - recipe = this.recipeService.getByIdWithStars(recipe.getId(), owner); - assertThat(recipe.getStars(), is(empty())); - } - @Test @DirtiesContext public void deleteRecipe() { final User owner = this.createTestUser("recipeOwner"); final Recipe toDelete = this.createTestRecipe(owner); - this.recipeService.deleteRecipe(toDelete, owner); + this.recipeService.deleteRecipe(toDelete.getId(), owner); assertThrows(RecipeException.class, () -> this.recipeService.getById(toDelete.getId(), owner)); } @@ -379,7 +318,7 @@ public class RecipeServiceTests { final User owner = this.createTestUser("recipeOwner"); final User notOwner = this.createTestUser("notOwner"); final Recipe toDelete = this.createTestRecipe(owner); - assertThrows(AccessDeniedException.class, () -> this.recipeService.deleteRecipe(toDelete, notOwner)); + assertThrows(AccessDeniedException.class, () -> this.recipeService.deleteRecipe(toDelete.getId(), notOwner)); } } diff --git a/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java b/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java index d3e5328..27e98ac 100644 --- a/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java +++ b/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java @@ -6,6 +6,7 @@ import app.mealsmadeeasy.api.image.ImageService; import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; 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.UserService; import org.slf4j.Logger; @@ -43,19 +44,22 @@ public class DevConfiguration { ); logger.info("Created {}", testUser); - final Recipe recipe = this.recipeService.create(testUser, "Test Recipe", "Hello, World!"); - this.recipeService.setPublic(recipe, testUser, true); + final RecipeCreateSpec recipeCreateSpec = new RecipeCreateSpec(); + recipeCreateSpec.setTitle("Test Recipe"); + recipeCreateSpec.setRawText("Hello, World!"); + recipeCreateSpec.setPublic(true); + final Recipe recipe = this.recipeService.create(testUser, recipeCreateSpec); logger.info("Created {}", recipe); try (final InputStream inputStream = DevConfiguration.class.getResourceAsStream("HAL9000.svg")) { - final ImageCreateInfoSpec spec = new ImageCreateInfoSpec(); - spec.setPublic(true); + final ImageCreateInfoSpec imageCreateSpec = new ImageCreateInfoSpec(); + imageCreateSpec.setPublic(true); final Image image = this.imageService.create( testUser, "HAL9000.svg", inputStream, 27881L, - spec + imageCreateSpec ); logger.info("Created {}", image); } catch (IOException | ImageException e) { diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java b/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java index 5e0de08..f171bd8 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java @@ -1,5 +1,6 @@ package app.mealsmadeeasy.api.recipe; +import app.mealsmadeeasy.api.image.Image; import app.mealsmadeeasy.api.recipe.comment.RecipeComment; import app.mealsmadeeasy.api.recipe.star.RecipeStar; import app.mealsmadeeasy.api.user.User; @@ -19,4 +20,5 @@ public interface Recipe { boolean isPublic(); Set getViewers(); Set getComments(); + Image getMainImage(); } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java index e0074b7..2699548 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java @@ -1,8 +1,8 @@ package app.mealsmadeeasy.api.recipe; +import app.mealsmadeeasy.api.recipe.view.FullRecipeView; import app.mealsmadeeasy.api.recipe.view.RecipeExceptionView; import app.mealsmadeeasy.api.recipe.view.RecipeInfoView; -import app.mealsmadeeasy.api.recipe.view.RecipePageView; import app.mealsmadeeasy.api.user.User; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -32,9 +32,9 @@ public class RecipeController { } @GetMapping("/{id}") - public ResponseEntity getById(@PathVariable long id, @AuthenticationPrincipal User user) + public ResponseEntity getById(@PathVariable long id, @AuthenticationPrincipal User user) throws RecipeException { - return ResponseEntity.ok(this.recipeService.getPageViewById(id, user)); + return ResponseEntity.ok(this.recipeService.getFullViewById(id, user)); } @GetMapping diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeEntity.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeEntity.java index 4044173..34651ee 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeEntity.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeEntity.java @@ -1,5 +1,6 @@ package app.mealsmadeeasy.api.recipe; +import app.mealsmadeeasy.api.image.S3ImageEntity; import app.mealsmadeeasy.api.recipe.comment.RecipeComment; import app.mealsmadeeasy.api.recipe.comment.RecipeCommentEntity; import app.mealsmadeeasy.api.recipe.star.RecipeStar; @@ -42,7 +43,8 @@ public final class RecipeEntity implements Recipe { @JoinColumn(name = "owner_id", nullable = false) private UserEntity owner; - @OneToMany(mappedBy = "recipe") + @OneToMany + @JoinColumn(name = "recipeId") private Set stars = new HashSet<>(); @OneToMany(mappedBy = "recipe") @@ -54,6 +56,9 @@ public final class RecipeEntity implements Recipe { @ManyToMany private Set viewers = new HashSet<>(); + @ManyToOne + private S3ImageEntity mainImage; + @Override public Long getId() { return this.id; @@ -169,4 +174,13 @@ public final class RecipeEntity implements Recipe { return "RecipeEntity(" + this.id + ", " + this.title + ")"; } + @Override + public S3ImageEntity getMainImage() { + return this.mainImage; + } + + public void setMainImage(S3ImageEntity image) { + this.mainImage = image; + } + } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeException.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeException.java index 12f6de8..5026bde 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeException.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeException.java @@ -3,7 +3,7 @@ package app.mealsmadeeasy.api.recipe; public class RecipeException extends Exception { public enum Type { - INVALID_OWNER_USERNAME, INVALID_STAR, NOT_VIEWABLE, INVALID_ID + INVALID_OWNER_USERNAME, INVALID_STAR, NOT_VIEWABLE, INVALID_COMMENT_ID, INVALID_ID } private final Type type; diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeRepository.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeRepository.java index 4c9366d..cd93267 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeRepository.java @@ -13,18 +13,17 @@ import java.util.Optional; public interface RecipeRepository extends JpaRepository { List findAllByIsPublicIsTrue(); - List findAllByViewersContaining(UserEntity viewer); - List findAllByOwner(UserEntity owner); - @Query("SELECT r FROM Recipe r WHERE size(r.stars) >= ?1 AND r.isPublic") - List findAllPublicByStarsGreaterThanEqual(long stars); + List findAllByViewersContaining(UserEntity viewer); + + List findAllByOwner(UserEntity owner); @Query("SELECT r FROM Recipe r WHERE size(r.stars) >= ?1 AND (r.isPublic OR ?2 MEMBER OF r.viewers)") List findAllViewableByStarsGreaterThanEqual(long stars, UserEntity viewer); @Query("SELECT r FROM Recipe r WHERE r.id = ?1") @EntityGraph(attributePaths = { "viewers" }) - RecipeEntity getByIdWithViewers(long id); + Optional findByIdWithViewers(long id); @Query("SELECT r FROM Recipe r WHERE r.id = ?1") @EntityGraph(attributePaths = { "stars" }) diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java index 13eb23d..bc7e3c6 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java @@ -6,6 +6,6 @@ import org.jetbrains.annotations.Nullable; public interface RecipeSecurity { boolean isOwner(Recipe recipe, User user); boolean isOwner(long recipeId, User user) throws RecipeException; - boolean isViewableBy(Recipe recipe, @Nullable User user); + boolean isViewableBy(Recipe recipe, @Nullable User user) throws RecipeException; boolean isViewableBy(long recipeId, @Nullable User user) throws RecipeException; } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java index 6c2bcbd..435a01c 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java @@ -30,7 +30,7 @@ public class RecipeSecurityImpl implements RecipeSecurity { } @Override - public boolean isViewableBy(Recipe recipe, @Nullable User user) { + public boolean isViewableBy(Recipe recipe, @Nullable User user) throws RecipeException { if (recipe.isPublic()) { // public recipe return true; @@ -42,7 +42,10 @@ public class RecipeSecurityImpl implements RecipeSecurity { return true; } else { // check if viewer - final RecipeEntity withViewers = this.recipeRepository.getByIdWithViewers(recipe.getId()); + final RecipeEntity withViewers = this.recipeRepository.findByIdWithViewers(recipe.getId()) + .orElseThrow(() -> new RecipeException( + RecipeException.Type.INVALID_ID, "No such Recipe with id: " + recipe.getId() + )); for (final User viewer : withViewers.getViewers()) { if (viewer.getId() != null && viewer.getId().equals(user.getId())) { return true; @@ -57,7 +60,7 @@ public class RecipeSecurityImpl implements RecipeSecurity { public boolean isViewableBy(long recipeId, @Nullable User user) throws RecipeException { final Recipe recipe = this.recipeRepository.findById(recipeId).orElseThrow(() -> new RecipeException( RecipeException.Type.INVALID_ID, - "No such Recipe with id " + recipeId + "No such Recipe with id: " + recipeId )); return this.isViewableBy(recipe, user); } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java index a8692cb..ac1c63e 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java @@ -1,9 +1,9 @@ package app.mealsmadeeasy.api.recipe; -import app.mealsmadeeasy.api.recipe.comment.RecipeComment; -import app.mealsmadeeasy.api.recipe.star.RecipeStar; +import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; +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.recipe.view.RecipePageView; import app.mealsmadeeasy.api.user.User; import org.jetbrains.annotations.Nullable; import org.springframework.data.domain.Pageable; @@ -13,51 +13,24 @@ import java.util.List; public interface RecipeService { - Recipe create(String ownerUsername, String title, String rawText) throws RecipeException; - Recipe create(User user, String title, String rawText); + Recipe create(@Nullable User owner, RecipeCreateSpec spec); - Recipe getById(long id) throws RecipeException; - Recipe getById(long id, User viewer) throws RecipeException; + Recipe getById(long id, @Nullable User viewer) throws RecipeException; + Recipe getByIdWithStars(long id, @Nullable User viewer) throws RecipeException; + FullRecipeView getFullViewById(long id, @Nullable User viewer) throws RecipeException; - Recipe getByIdWithStars(long id) throws RecipeException; - Recipe getByIdWithStars(long id, User viewer) throws RecipeException; - - RecipePageView getPageViewById(long id, @Nullable User viewer) throws RecipeException; Slice getInfoViewsViewableBy(Pageable pageable, @Nullable User viewer); - - List getByMinimumStars(long minimumStars); - List getByMinimumStars(long minimumStars, User viewer); - + List getByMinimumStars(long minimumStars, @Nullable User viewer); List getPublicRecipes(); - List getRecipesViewableBy(User user); - List getRecipesOwnedBy(User user); + List getRecipesViewableBy(User viewer); + List getRecipesOwnedBy(User owner); - String getRenderedMarkdown(Recipe recipe, User viewer); + Recipe update(long id, RecipeUpdateSpec spec, User modifier) throws RecipeException; - Recipe updateRawText(Recipe recipe, User owner, String newRawText); + Recipe addViewer(long id, User modifier, User viewer) throws RecipeException; + Recipe removeViewer(long id, User modifier, User viewer) throws RecipeException; + Recipe clearAllViewers(long id, User modifier) throws RecipeException; - Recipe updateOwner(Recipe recipe, User oldOwner, User newOwner) throws RecipeException; - - RecipeStar addStar(Recipe recipe, User giver) throws RecipeException; - void deleteStarByUser(Recipe recipe, User giver) throws RecipeException; - void deleteStar(RecipeStar recipeStar); - int getStarCount(Recipe recipe, @Nullable User viewer); - - Recipe setPublic(Recipe recipe, User owner, boolean isPublic); - - Recipe addViewer(Recipe recipe, User user); - Recipe removeViewer(Recipe recipe, User user); - Recipe clearViewers(Recipe recipe); - int getViewerCount(Recipe recipe, @Nullable User viewer); - - RecipeComment getCommentById(long id) throws RecipeException; - RecipeComment addComment(Recipe recipe, String rawCommentText, User commenter); - RecipeComment updateComment(RecipeComment comment, String newRawCommentText); - String getRenderedMarkdown(RecipeComment recipeComment); - void deleteComment(RecipeComment comment); - Recipe clearComments(Recipe recipe); - - void deleteRecipe(Recipe recipe, User owner); - void deleteById(long id, User owner); + void deleteRecipe(long id, User modifier); } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java index 0b25452..97e49c6 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java @@ -1,16 +1,12 @@ package app.mealsmadeeasy.api.recipe; -import app.mealsmadeeasy.api.recipe.comment.RecipeComment; -import app.mealsmadeeasy.api.recipe.comment.RecipeCommentEntity; -import app.mealsmadeeasy.api.recipe.comment.RecipeCommentRepository; -import app.mealsmadeeasy.api.recipe.star.RecipeStar; -import app.mealsmadeeasy.api.recipe.star.RecipeStarEntity; -import app.mealsmadeeasy.api.recipe.star.RecipeStarRepository; +import app.mealsmadeeasy.api.image.S3ImageEntity; +import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; +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.recipe.view.RecipePageView; import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.UserEntity; -import app.mealsmadeeasy.api.user.UserRepository; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.jetbrains.annotations.Nullable; @@ -18,10 +14,12 @@ import org.jsoup.Jsoup; import org.jsoup.safety.Safelist; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -38,95 +36,79 @@ public class RecipeServiceImpl implements RecipeService { } private final RecipeRepository recipeRepository; - private final UserRepository userRepository; - private final RecipeStarRepository recipeStarRepository; - private final RecipeCommentRepository recipeCommentRepository; - public RecipeServiceImpl( - RecipeRepository recipeRepository, - UserRepository userRepository, - RecipeStarRepository recipeStarRepository, - RecipeCommentRepository recipeCommentRepository - ) { + public RecipeServiceImpl(RecipeRepository recipeRepository) { this.recipeRepository = recipeRepository; - this.userRepository = userRepository; - this.recipeStarRepository = recipeStarRepository; - this.recipeCommentRepository = recipeCommentRepository; } @Override - public Recipe create(String ownerUsername, String title, String rawText) throws RecipeException { + public Recipe create(@Nullable User owner, RecipeCreateSpec spec) { + if (owner == null) { + throw new AccessDeniedException("Must be logged in."); + } final RecipeEntity draft = new RecipeEntity(); - final UserEntity userEntity = this.userRepository.findByUsername(ownerUsername) - .orElseThrow(() -> new RecipeException( - RecipeException.Type.INVALID_OWNER_USERNAME, - "No such ownerUsername " + ownerUsername - )); - draft.setOwner(userEntity); - draft.setTitle(title); - draft.setRawText(rawText); + draft.setCreated(LocalDateTime.now()); + draft.setOwner((UserEntity) owner); + draft.setTitle(spec.getTitle()); + draft.setRawText(spec.getRawText()); + draft.setMainImage((S3ImageEntity) spec.getMainImage()); + draft.setPublic(spec.isPublic()); return this.recipeRepository.save(draft); } - @Override - public Recipe create(User user, String title, String rawText) { - final RecipeEntity draft = new RecipeEntity(); - draft.setOwner((UserEntity) user); - draft.setTitle(title); - draft.setRawText(rawText); - return this.recipeRepository.save(draft); - } - - @Override - @PostAuthorize("returnObject.isPublic") - public Recipe getById(long id) throws RecipeException { + private RecipeEntity findRecipeEntity(long id) throws RecipeException { return this.recipeRepository.findById(id).orElseThrow(() -> new RecipeException( - RecipeException.Type.INVALID_ID, - "No such recipe for id " + id + RecipeException.Type.INVALID_ID, "No such Recipe with id: " + id )); } @Override @PostAuthorize("@recipeSecurity.isViewableBy(returnObject, #viewer)") public Recipe getById(long id, User viewer) throws RecipeException { - return this.recipeRepository.findById(id).orElseThrow(() -> new RecipeException( - RecipeException.Type.INVALID_ID, - "No such recipe for id " + id - )); - } - - @Override - @PostAuthorize("returnObject.isPublic") - public Recipe getByIdWithStars(long id) throws RecipeException { - return this.recipeRepository.findByIdWithStars(id).orElseThrow(() -> new RecipeException( - RecipeException.Type.INVALID_ID, - "No such recipe for id " + id - )); + return this.findRecipeEntity(id); } @Override @PostAuthorize("@recipeSecurity.isViewableBy(returnObject, #viewer)") - public Recipe getByIdWithStars(long id, User viewer) throws RecipeException { + public Recipe getByIdWithStars(long id, @Nullable User viewer) throws RecipeException { return this.recipeRepository.findByIdWithStars(id).orElseThrow(() -> new RecipeException( RecipeException.Type.INVALID_ID, - "No such recipe for id " + id + "No such Recipe with id: " + id )); } + private String getRenderedMarkdown(RecipeEntity entity) { + if (entity.getCachedRenderedText() == null) { + entity.setCachedRenderedText(renderAndCleanMarkdown(entity.getRawText())); + entity = this.recipeRepository.save(entity); + } + return entity.getCachedRenderedText(); + } + + private int getStarCount(Recipe recipe) { + return this.recipeRepository.getStarCount(recipe.getId()); + } + + private int getViewerCount(long recipeId) { + return this.recipeRepository.getViewerCount(recipeId); + } + @Override @PostAuthorize("@recipeSecurity.isViewableBy(#id, #viewer)") - public RecipePageView getPageViewById(long id, @Nullable User viewer) throws RecipeException { - final Recipe recipe = this.recipeRepository.getReferenceById(id); - final RecipePageView view = new RecipePageView(); + public FullRecipeView getFullViewById(long id, @Nullable User viewer) throws RecipeException { + final RecipeEntity recipe = this.recipeRepository.findById(id).orElseThrow(() -> new RecipeException( + RecipeException.Type.INVALID_ID, "No such Recipe for id: " + id + )); + final FullRecipeView view = new FullRecipeView(); view.setId(recipe.getId()); view.setCreated(recipe.getCreated()); view.setModified(recipe.getModified()); view.setTitle(recipe.getTitle()); - view.setText(this.getRenderedMarkdown(recipe, viewer)); + view.setText(this.getRenderedMarkdown(recipe)); view.setOwnerId(recipe.getOwner().getId()); view.setOwnerUsername(recipe.getOwner().getUsername()); - view.setStarCount(this.getStarCount(recipe, viewer)); - view.setViewerCount(this.getViewerCount(recipe, viewer)); + view.setStarCount(this.getStarCount(recipe)); + view.setViewerCount(this.getViewerCount(recipe.getId())); return view; } @@ -144,16 +126,11 @@ public class RecipeServiceImpl implements RecipeService { view.setOwnerId(entity.getOwner().getId()); view.setOwnerUsername(entity.getOwner().getUsername()); view.setPublic(entity.isPublic()); - view.setStarCount(this.getStarCount(entity, viewer)); + view.setStarCount(this.getStarCount(entity)); return view; }); } - @Override - public List getByMinimumStars(long minimumStars) { - return List.copyOf(this.recipeRepository.findAllPublicByStarsGreaterThanEqual(minimumStars)); - } - @Override public List getByMinimumStars(long minimumStars, User viewer) { return List.copyOf( @@ -167,169 +144,75 @@ public class RecipeServiceImpl implements RecipeService { } @Override - public List getRecipesViewableBy(User user) { - return List.copyOf(this.recipeRepository.findAllByViewersContaining((UserEntity) user)); + public List getRecipesViewableBy(User viewer) { + return List.copyOf(this.recipeRepository.findAllByViewersContaining((UserEntity) viewer)); } @Override - public List getRecipesOwnedBy(User user) { - return List.copyOf(this.recipeRepository.findAllByOwner((UserEntity) user)); + public List getRecipesOwnedBy(User owner) { + return List.copyOf(this.recipeRepository.findAllByOwner((UserEntity) owner)); } @Override - @PreAuthorize("@recipeSecurity.isViewableBy(#recipe, #viewer)") - public String getRenderedMarkdown(Recipe recipe, User viewer) { - RecipeEntity entity = (RecipeEntity) recipe; - if (entity.getCachedRenderedText() == null) { - entity.setCachedRenderedText(renderAndCleanMarkdown(entity.getRawText())); - entity = this.recipeRepository.save(entity); + @PreAuthorize("@recipeSecurity.isOwner(#id, #modifier)") + public Recipe update(long id, RecipeUpdateSpec spec, User modifier) throws RecipeException { + final RecipeEntity entity = this.findRecipeEntity(id); + boolean didModify = false; + if (spec.getTitle() != null) { + entity.setTitle(spec.getTitle()); + didModify = true; + } + if (spec.getRawText() != null) { + entity.setRawText(spec.getRawText()); + didModify = true; + } + if (spec.getPublic() != null) { + entity.setPublic(spec.getPublic()); + didModify = true; + } + if (spec.getMainImage() != null) { + entity.setMainImage((S3ImageEntity) spec.getMainImage()); + didModify = true; + } + if (didModify) { + entity.setModified(LocalDateTime.now()); } - return entity.getCachedRenderedText(); - } - - @Override - @PreAuthorize("@recipeSecurity.isOwner(#recipe, #owner)") - public Recipe updateRawText(Recipe recipe, User owner, String newRawText) { - final RecipeEntity entity = (RecipeEntity) recipe; - entity.setCachedRenderedText(null); - entity.setRawText(newRawText); return this.recipeRepository.save(entity); } @Override - @PreAuthorize("@recipeSecurity.isOwner(#recipe, #oldOwner)") - public Recipe updateOwner(Recipe recipe, User oldOwner, User newOwner) { - final RecipeEntity entity = (RecipeEntity) recipe; - entity.setOwner((UserEntity) newOwner); - return this.recipeRepository.save(entity); - } - - @Override - @PreAuthorize("@recipeSecurity.isViewableBy(#recipe, #giver)") - public RecipeStar addStar(Recipe recipe, User giver) { - final RecipeStarEntity star = new RecipeStarEntity(); - star.setOwner((UserEntity) giver); - star.setRecipe((RecipeEntity) recipe); - return this.recipeStarRepository.save(star); - } - - @Override - public void deleteStar(RecipeStar recipeStar) { - this.recipeStarRepository.delete((RecipeStarEntity) recipeStar); - } - - @Override - public void deleteStarByUser(Recipe recipe, User giver) throws RecipeException { - final RecipeStarEntity star = this.recipeStarRepository.findByOwnerAndRecipe( - (UserEntity) giver, - (RecipeEntity) recipe - ).orElseThrow(() -> new RecipeException( - RecipeException.Type.INVALID_STAR, - "No such star for user " + giver.getUsername() + " and recipe " + recipe.getId() - )); - this.recipeStarRepository.delete(star); - } - - @Override - @PreAuthorize("@recipeSecurity.isViewableBy(#recipe, #viewer)") - public int getStarCount(Recipe recipe, @Nullable User viewer) { - return this.recipeRepository.getStarCount(recipe.getId()); - } - - @Override - @PreAuthorize("@recipeSecurity.isOwner(#recipe, #owner)") - public Recipe setPublic(Recipe recipe, User owner, boolean isPublic) { - final RecipeEntity entity = (RecipeEntity) recipe; - entity.setPublic(isPublic); - return this.recipeRepository.save(entity); - } - - @Override - public Recipe addViewer(Recipe recipe, User user) { - final RecipeEntity entity = (RecipeEntity) recipe; + @PreAuthorize("@recipeSecurity.isOwner(#id, #modifier)") + public Recipe addViewer(long id, User modifier, User viewer) throws RecipeException { + final RecipeEntity entity = this.recipeRepository.findByIdWithViewers(id).orElseThrow(() -> new RecipeException( + RecipeException.Type.INVALID_ID, "No such Recipe with id: " + id + )); final Set viewers = new HashSet<>(entity.getViewerEntities()); - viewers.add((UserEntity) user); + viewers.add((UserEntity) viewer); entity.setViewers(viewers); return this.recipeRepository.save(entity); } @Override - public Recipe removeViewer(Recipe recipe, User user) { - final RecipeEntity entity = (RecipeEntity) recipe; + @PreAuthorize("@recipeSecurity.isOwner(#id, #modifier)") + public Recipe removeViewer(long id, User modifier, User viewer) throws RecipeException { + final RecipeEntity entity = this.findRecipeEntity(id); final Set viewers = new HashSet<>(entity.getViewerEntities()); - viewers.remove((UserEntity) user); + viewers.remove((UserEntity) viewer); entity.setViewers(viewers); return this.recipeRepository.save(entity); } @Override - public Recipe clearViewers(Recipe recipe) { - final RecipeEntity entity = (RecipeEntity) recipe; + @PreAuthorize("@recipeSecurity.isOwner(#id, #modifier)") + public Recipe clearAllViewers(long id, User modifier) throws RecipeException { + final RecipeEntity entity = this.findRecipeEntity(id); entity.setViewers(new HashSet<>()); return this.recipeRepository.save(entity); } @Override - @PreAuthorize("@recipeSecurity.isViewableBy(#recipe, #viewer)") - public int getViewerCount(Recipe recipe, User viewer) { - return this.recipeRepository.getViewerCount(recipe.getId()); - } - - @Override - public RecipeComment getCommentById(long id) throws RecipeException { - return this.recipeCommentRepository.findById(id) - .orElseThrow(() -> new RecipeException( - RecipeException.Type.INVALID_ID, - "No such RecipeComment for id " + id - )); - } - - @Override - public RecipeComment addComment(Recipe recipe, String rawCommentText, User commenter) { - final RecipeCommentEntity draft = new RecipeCommentEntity(); - draft.setRawText(rawCommentText); - draft.setOwner((UserEntity) commenter); - return this.recipeCommentRepository.save(draft); - } - - @Override - public RecipeComment updateComment(RecipeComment comment, String newRawCommentText) { - final RecipeCommentEntity entity = (RecipeCommentEntity) comment; - entity.setCachedRenderedText(null); - entity.setRawText(newRawCommentText); - return this.recipeCommentRepository.save(entity); - } - - @Override - public String getRenderedMarkdown(RecipeComment recipeComment) { - RecipeCommentEntity entity = (RecipeCommentEntity) recipeComment; - if (entity.getCachedRenderedText() == null) { - entity.setCachedRenderedText(renderAndCleanMarkdown(entity.getRawText())); - entity = this.recipeCommentRepository.save(entity); - } - return entity.getCachedRenderedText(); - } - - @Override - public void deleteComment(RecipeComment comment) { - this.recipeCommentRepository.delete((RecipeCommentEntity) comment); - } - - @Override - public Recipe clearComments(Recipe recipe) { - this.recipeCommentRepository.deleteAllByRecipe((RecipeEntity) recipe); - return this.recipeRepository.getReferenceById(recipe.getId()); - } - - @Override - @PreAuthorize("@recipeSecurity.isOwner(#recipe, #owner)") - public void deleteRecipe(Recipe recipe, User owner) { - this.recipeRepository.delete((RecipeEntity) recipe); - } - - @Override - @PreAuthorize("@recipeSecurity.isOwner(#id, #owner)") - public void deleteById(long id, User owner) { + @PreAuthorize("@recipeSecurity.isOwner(#id, #modifier)") + public void deleteRecipe(long id, User modifier) { this.recipeRepository.deleteById(id); } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentCreateSpec.java b/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentCreateSpec.java new file mode 100644 index 0000000..577e019 --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentCreateSpec.java @@ -0,0 +1,15 @@ +package app.mealsmadeeasy.api.recipe.comment; + +public class RecipeCommentCreateSpec { + + private String rawText; + + public String getRawText() { + return this.rawText; + } + + public void setRawText(String rawText) { + this.rawText = rawText; + } + +} diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentService.java b/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentService.java new file mode 100644 index 0000000..a42a4a9 --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentService.java @@ -0,0 +1,11 @@ +package app.mealsmadeeasy.api.recipe.comment; + +import app.mealsmadeeasy.api.recipe.RecipeException; +import app.mealsmadeeasy.api.user.User; + +public interface RecipeCommentService { + RecipeComment create(long recipeId, User owner, RecipeCommentCreateSpec spec) throws RecipeException; + RecipeComment get(long commentId, User viewer) throws RecipeException; + RecipeComment update(long commentId, User viewer, RecipeCommentUpdateSpec spec) throws RecipeException; + void delete(long commentId, User modifier) throws RecipeException; +} diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentServiceImpl.java new file mode 100644 index 0000000..bfab2a5 --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentServiceImpl.java @@ -0,0 +1,75 @@ +package app.mealsmadeeasy.api.recipe.comment; + +import app.mealsmadeeasy.api.recipe.RecipeEntity; +import app.mealsmadeeasy.api.recipe.RecipeException; +import app.mealsmadeeasy.api.recipe.RecipeRepository; +import app.mealsmadeeasy.api.user.User; +import app.mealsmadeeasy.api.user.UserEntity; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +import static java.util.Objects.requireNonNull; + +@Service +public class RecipeCommentServiceImpl implements RecipeCommentService { + + private final RecipeCommentRepository recipeCommentRepository; + private final RecipeRepository recipeRepository; + + public RecipeCommentServiceImpl( + RecipeCommentRepository recipeCommentRepository, + RecipeRepository recipeRepository + ) { + this.recipeCommentRepository = recipeCommentRepository; + this.recipeRepository = recipeRepository; + } + + @Override + public RecipeComment create(long recipeId, User owner, RecipeCommentCreateSpec spec) throws RecipeException { + requireNonNull(owner); + final RecipeCommentEntity draft = new RecipeCommentEntity(); + draft.setCreated(LocalDateTime.now()); + draft.setRawText(spec.getRawText()); + draft.setOwner((UserEntity) owner); + final RecipeEntity recipe = this.recipeRepository.findById(recipeId).orElseThrow(() -> new RecipeException( + RecipeException.Type.INVALID_ID, "No such Recipe for id: " + recipeId + )); + draft.setRecipe(recipe); + return this.recipeCommentRepository.save(draft); + } + + @PostAuthorize("@recipeSecurity.isViewableBy(returnObject.recipe, #viewer)") + private RecipeCommentEntity loadCommentEntity(long commentId, User viewer) throws RecipeException { + return this.recipeCommentRepository.findById(commentId).orElseThrow(() -> new RecipeException( + RecipeException.Type.INVALID_COMMENT_ID, "No such RecipeComment for id: " + commentId + )); + } + + @Override + public RecipeComment get(long commentId, User viewer) throws RecipeException { + return this.loadCommentEntity(commentId, viewer); + } + + @Override + public RecipeComment update(long commentId, User viewer, RecipeCommentUpdateSpec spec) throws RecipeException { + final RecipeCommentEntity entity = this.loadCommentEntity(commentId, viewer); + entity.setRawText(spec.getRawText()); + return this.recipeCommentRepository.save(entity); + } + + @PostAuthorize("@recipeSecurity.isOwner(returnObject.recipe, #modifier)") + private RecipeCommentEntity loadForDelete(long commentId, User modifier) throws RecipeException { + return this.recipeCommentRepository.findById(commentId).orElseThrow(() -> new RecipeException( + RecipeException.Type.INVALID_COMMENT_ID, "No such RecipeComment for id: " + commentId + )); + } + + @Override + public void delete(long commentId, User modifier) throws RecipeException { + final RecipeCommentEntity entityToDelete = this.loadForDelete(commentId, modifier); + this.recipeCommentRepository.delete(entityToDelete); + } + +} diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentUpdateSpec.java b/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentUpdateSpec.java new file mode 100644 index 0000000..028c219 --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/recipe/comment/RecipeCommentUpdateSpec.java @@ -0,0 +1,15 @@ +package app.mealsmadeeasy.api.recipe.comment; + +public class RecipeCommentUpdateSpec { + + private String rawText; + + public String getRawText() { + return this.rawText; + } + + public void setRawText(String rawText) { + this.rawText = rawText; + } + +} diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeCreateSpec.java b/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeCreateSpec.java new file mode 100644 index 0000000..149606a --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeCreateSpec.java @@ -0,0 +1,45 @@ +package app.mealsmadeeasy.api.recipe.spec; + +import app.mealsmadeeasy.api.image.Image; +import org.jetbrains.annotations.Nullable; + +public class RecipeCreateSpec { + + private String title; + private String rawText; + private boolean isPublic; + private @Nullable Image mainImage; + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getRawText() { + return this.rawText; + } + + public void setRawText(String rawText) { + this.rawText = rawText; + } + + public boolean isPublic() { + return this.isPublic; + } + + public void setPublic(boolean isPublic) { + this.isPublic = isPublic; + } + + public @Nullable Image getMainImage() { + return this.mainImage; + } + + public void setMainImage(@Nullable Image mainImage) { + this.mainImage = mainImage; + } + +} diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java b/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java new file mode 100644 index 0000000..5af41af --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java @@ -0,0 +1,45 @@ +package app.mealsmadeeasy.api.recipe.spec; + +import app.mealsmadeeasy.api.image.Image; +import org.jetbrains.annotations.Nullable; + +public class RecipeUpdateSpec { + + private @Nullable String title; + private @Nullable String rawText; + private @Nullable Boolean isPublic; + private @Nullable Image mainImage; + + public @Nullable String getTitle() { + return this.title; + } + + public void setTitle(@Nullable String title) { + this.title = title; + } + + public @Nullable String getRawText() { + return this.rawText; + } + + public void setRawText(@Nullable String rawText) { + this.rawText = rawText; + } + + public @Nullable Boolean getPublic() { + return this.isPublic; + } + + public void setPublic(@Nullable Boolean isPublic) { + this.isPublic = isPublic; + } + + public @Nullable Image getMainImage() { + return this.mainImage; + } + + public void setMainImage(@Nullable Image mainImage) { + this.mainImage = mainImage; + } + +} diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStar.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStar.java index e850568..d83f482 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStar.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStar.java @@ -1,13 +1,7 @@ package app.mealsmadeeasy.api.recipe.star; -import app.mealsmadeeasy.api.recipe.Recipe; -import app.mealsmadeeasy.api.user.User; - import java.time.LocalDateTime; public interface RecipeStar { - Long getId(); - User getOwner(); LocalDateTime getDate(); - Recipe getRecipe(); } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarEntity.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarEntity.java index f3d2a4f..c1290d9 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarEntity.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarEntity.java @@ -1,48 +1,28 @@ package app.mealsmadeeasy.api.recipe.star; -import app.mealsmadeeasy.api.recipe.RecipeEntity; -import app.mealsmadeeasy.api.user.UserEntity; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; import java.time.LocalDateTime; @Entity(name = "RecipeStar") public final class RecipeStarEntity implements RecipeStar { - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(nullable = false) - private Long id; - - @ManyToOne - @JoinColumn(name = "owner_id", nullable = false, updatable = false) - private UserEntity owner; + @EmbeddedId + private RecipeStarId id; @Column(nullable = false, updatable = false) private LocalDateTime date = LocalDateTime.now(); - @ManyToOne - @JoinColumn(name = "recipe_id", nullable = false, updatable = false) - private RecipeEntity recipe; - - @Override - public Long getId() { + public RecipeStarId getId() { return this.id; } - public void setId(long id) { + public void setId(RecipeStarId id) { this.id = id; } - @Override - public UserEntity getOwner() { - return this.owner; - } - - public void setOwner(UserEntity owner) { - this.owner = owner; - } - @Override public LocalDateTime getDate() { return this.date; @@ -52,15 +32,6 @@ public final class RecipeStarEntity implements RecipeStar { this.date = date; } - @Override - public RecipeEntity getRecipe() { - return this.recipe; - } - - public void setRecipe(RecipeEntity recipe) { - this.recipe = recipe; - } - @Override public String toString() { return "RecipeStarEntity(" + this.id + ")"; diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarId.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarId.java new file mode 100644 index 0000000..2fd49da --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarId.java @@ -0,0 +1,36 @@ +package app.mealsmadeeasy.api.recipe.star; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; + +@Embeddable +public class RecipeStarId { + + @Column(nullable = false) + private String ownerUsername; + + @Column(nullable = false) + private Long recipeId; + + public String getOwnerUsername() { + return this.ownerUsername; + } + + public void setOwnerUsername(String ownerUsername) { + this.ownerUsername = ownerUsername; + } + + public Long getRecipeId() { + return this.recipeId; + } + + public void setRecipeId(Long recipeId) { + this.recipeId = recipeId; + } + + @Override + public String toString() { + return "RecipeStarId(" + this.recipeId + ", " + this.ownerUsername + ")"; + } + +} 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 515a659..e699be8 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java @@ -1,14 +1,16 @@ package app.mealsmadeeasy.api.recipe.star; -import app.mealsmadeeasy.api.recipe.RecipeEntity; -import app.mealsmadeeasy.api.user.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; -import java.util.List; import java.util.Optional; public interface RecipeStarRepository extends JpaRepository { - List findAllByOwner(UserEntity user); - long countAllByOwner(UserEntity user); - Optional findByOwnerAndRecipe(UserEntity user, RecipeEntity recipe); + + @Query("SELECT star FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerUsername = ?2") + Optional findByRecipeIdAndOwnerUsername(Long recipeId, String username); + + @Query("DELETE FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerUsername = ?2") + void deleteByRecipeIdAndOwnerUsername(Long recipeId, String username); + } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java new file mode 100644 index 0000000..d63694e --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java @@ -0,0 +1,9 @@ +package app.mealsmadeeasy.api.recipe.star; + +import app.mealsmadeeasy.api.recipe.RecipeException; + +public interface RecipeStarService { + RecipeStar create(long recipeId, String ownerUsername); + 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 new file mode 100644 index 0000000..add871c --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceImpl.java @@ -0,0 +1,43 @@ +package app.mealsmadeeasy.api.recipe.star; + +import app.mealsmadeeasy.api.recipe.RecipeException; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +public class RecipeStarServiceImpl implements RecipeStarService { + + private final RecipeStarRepository recipeStarRepository; + + public RecipeStarServiceImpl(RecipeStarRepository recipeStarRepository) { + this.recipeStarRepository = recipeStarRepository; + } + + @Override + public RecipeStar create(long recipeId, String ownerUsername) { + final RecipeStarEntity draft = new RecipeStarEntity(); + final RecipeStarId id = new RecipeStarId(); + id.setRecipeId(recipeId); + id.setOwnerUsername(ownerUsername); + draft.setId(id); + draft.setDate(LocalDateTime.now()); + return this.recipeStarRepository.save(draft); + } + + @Override + public RecipeStar get(long recipeId, String ownerUsername) throws RecipeException { + return this.recipeStarRepository.findByRecipeIdAndOwnerUsername(recipeId, ownerUsername).orElseThrow( + () -> new RecipeException( + RecipeException.Type.INVALID_ID, + "No such RecipeStar for recipeId: " + recipeId + " and ownerUsername: " + ownerUsername + ) + ); + } + + @Override + public void delete(long recipeId, String ownerUsername) { + this.recipeStarRepository.deleteByRecipeIdAndOwnerUsername(recipeId, ownerUsername); + } + +} diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/view/RecipePageView.java b/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java similarity index 98% rename from src/main/java/app/mealsmadeeasy/api/recipe/view/RecipePageView.java rename to src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java index cc6a676..5bea338 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/view/RecipePageView.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java @@ -4,7 +4,7 @@ import org.jetbrains.annotations.Nullable; import java.time.LocalDateTime; -public class RecipePageView { +public class FullRecipeView { private long id; private LocalDateTime created; diff --git a/src/testFixtures/java/app/mealsmadeeasy/api/matchers/ContainsItemsMatcher.java b/src/testFixtures/java/app/mealsmadeeasy/api/matchers/ContainsItemsMatcher.java index d690005..df94cae 100644 --- a/src/testFixtures/java/app/mealsmadeeasy/api/matchers/ContainsItemsMatcher.java +++ b/src/testFixtures/java/app/mealsmadeeasy/api/matchers/ContainsItemsMatcher.java @@ -5,6 +5,7 @@ import org.hamcrest.Description; import java.util.List; import java.util.Objects; +import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; @@ -13,11 +14,26 @@ public class ContainsItemsMatcher extends BaseMatcher> { private final List allExpected; private final Predicate isT; private final Function idFunction; + private final BiPredicate equalsFunction; - public ContainsItemsMatcher(List allExpected, Predicate isT, Function idFunction) { + public ContainsItemsMatcher( + List allExpected, + Predicate isT, + Function idFunction, + BiPredicate equalsFunction + ) { this.allExpected = allExpected; this.isT = isT; this.idFunction = idFunction; + this.equalsFunction = equalsFunction; + } + + public ContainsItemsMatcher( + List allExpected, + Predicate isT, + Function idFunction + ) { + this(allExpected, isT, idFunction, Objects::equals); } @SuppressWarnings("unchecked") @@ -28,7 +44,7 @@ public class ContainsItemsMatcher extends BaseMatcher> { for (final T expected : this.allExpected) { for (final Object item : iterable) { if ( - this.isT.test(item) && Objects.equals( + this.isT.test(item) && this.equalsFunction.test( this.idFunction.apply((T) item), this.idFunction.apply(expected) ) diff --git a/src/testFixtures/java/app/mealsmadeeasy/api/recipe/ContainsRecipeStarsMatcher.java b/src/testFixtures/java/app/mealsmadeeasy/api/recipe/ContainsRecipeStarsMatcher.java index c802ed8..fb29c9e 100644 --- a/src/testFixtures/java/app/mealsmadeeasy/api/recipe/ContainsRecipeStarsMatcher.java +++ b/src/testFixtures/java/app/mealsmadeeasy/api/recipe/ContainsRecipeStarsMatcher.java @@ -2,17 +2,26 @@ package app.mealsmadeeasy.api.recipe; import app.mealsmadeeasy.api.matchers.ContainsItemsMatcher; import app.mealsmadeeasy.api.recipe.star.RecipeStar; +import app.mealsmadeeasy.api.recipe.star.RecipeStarEntity; +import app.mealsmadeeasy.api.recipe.star.RecipeStarId; import java.util.List; +import java.util.Objects; -public class ContainsRecipeStarsMatcher extends ContainsItemsMatcher { +public class ContainsRecipeStarsMatcher extends ContainsItemsMatcher { public static ContainsRecipeStarsMatcher containsStars(RecipeStar... expected) { return new ContainsRecipeStarsMatcher(expected); } private ContainsRecipeStarsMatcher(RecipeStar[] allExpected) { - super(List.of(allExpected), o -> o instanceof RecipeStar, RecipeStar::getId); + super( + List.of(allExpected), + o -> o instanceof RecipeStar, + recipeStar -> ((RecipeStarEntity) recipeStar).getId(), + (id0, id1) -> Objects.equals(id0.getRecipeId(), id1.getRecipeId()) + && Objects.equals(id0.getOwnerUsername(), id1.getOwnerUsername()) + ); } }