Massive refactor of RecipeService and related. All tests passing.
This commit is contained in:
parent
2565e63a7d
commit
341133f779
@ -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))
|
||||
|
@ -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<Recipe> zeroStars = this.recipeService.getByMinimumStars(0);
|
||||
final List<Recipe> oneStar = this.recipeService.getByMinimumStars(1);
|
||||
final List<Recipe> twoStars = this.recipeService.getByMinimumStars(2);
|
||||
final List<Recipe> zeroStars = this.recipeService.getByMinimumStars(0, null);
|
||||
final List<Recipe> oneStar = this.recipeService.getByMinimumStars(1, null);
|
||||
final List<Recipe> 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<Recipe> zeroStarsNoneViewable = this.recipeService.getByMinimumStars(0, viewer);
|
||||
final List<Recipe> 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<Recipe> zeroStarsViewable = this.recipeService.getByMinimumStars(0, viewer);
|
||||
final List<Recipe> 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<Recipe> 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<Recipe> 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("<h1>A Heading</h1>"));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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<User> getViewers();
|
||||
Set<RecipeComment> getComments();
|
||||
Image getMainImage();
|
||||
}
|
||||
|
@ -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<RecipePageView> getById(@PathVariable long id, @AuthenticationPrincipal User user)
|
||||
public ResponseEntity<FullRecipeView> 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
|
||||
|
@ -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<RecipeStarEntity> stars = new HashSet<>();
|
||||
|
||||
@OneToMany(mappedBy = "recipe")
|
||||
@ -54,6 +56,9 @@ public final class RecipeEntity implements Recipe {
|
||||
@ManyToMany
|
||||
private Set<UserEntity> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -13,18 +13,17 @@ import java.util.Optional;
|
||||
public interface RecipeRepository extends JpaRepository<RecipeEntity, Long> {
|
||||
|
||||
List<RecipeEntity> findAllByIsPublicIsTrue();
|
||||
List<RecipeEntity> findAllByViewersContaining(UserEntity viewer);
|
||||
List<RecipeEntity> findAllByOwner(UserEntity owner);
|
||||
|
||||
@Query("SELECT r FROM Recipe r WHERE size(r.stars) >= ?1 AND r.isPublic")
|
||||
List<RecipeEntity> findAllPublicByStarsGreaterThanEqual(long stars);
|
||||
List<RecipeEntity> findAllByViewersContaining(UserEntity viewer);
|
||||
|
||||
List<RecipeEntity> findAllByOwner(UserEntity owner);
|
||||
|
||||
@Query("SELECT r FROM Recipe r WHERE size(r.stars) >= ?1 AND (r.isPublic OR ?2 MEMBER OF r.viewers)")
|
||||
List<RecipeEntity> findAllViewableByStarsGreaterThanEqual(long stars, UserEntity viewer);
|
||||
|
||||
@Query("SELECT r FROM Recipe r WHERE r.id = ?1")
|
||||
@EntityGraph(attributePaths = { "viewers" })
|
||||
RecipeEntity getByIdWithViewers(long id);
|
||||
Optional<RecipeEntity> findByIdWithViewers(long id);
|
||||
|
||||
@Query("SELECT r FROM Recipe r WHERE r.id = ?1")
|
||||
@EntityGraph(attributePaths = { "stars" })
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<RecipeInfoView> getInfoViewsViewableBy(Pageable pageable, @Nullable User viewer);
|
||||
|
||||
List<Recipe> getByMinimumStars(long minimumStars);
|
||||
List<Recipe> getByMinimumStars(long minimumStars, User viewer);
|
||||
|
||||
List<Recipe> getByMinimumStars(long minimumStars, @Nullable User viewer);
|
||||
List<Recipe> getPublicRecipes();
|
||||
List<Recipe> getRecipesViewableBy(User user);
|
||||
List<Recipe> getRecipesOwnedBy(User user);
|
||||
List<Recipe> getRecipesViewableBy(User viewer);
|
||||
List<Recipe> 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);
|
||||
|
||||
}
|
||||
|
@ -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<Recipe> getByMinimumStars(long minimumStars) {
|
||||
return List.copyOf(this.recipeRepository.findAllPublicByStarsGreaterThanEqual(minimumStars));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Recipe> getByMinimumStars(long minimumStars, User viewer) {
|
||||
return List.copyOf(
|
||||
@ -167,169 +144,75 @@ public class RecipeServiceImpl implements RecipeService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Recipe> getRecipesViewableBy(User user) {
|
||||
return List.copyOf(this.recipeRepository.findAllByViewersContaining((UserEntity) user));
|
||||
public List<Recipe> getRecipesViewableBy(User viewer) {
|
||||
return List.copyOf(this.recipeRepository.findAllByViewersContaining((UserEntity) viewer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Recipe> getRecipesOwnedBy(User user) {
|
||||
return List.copyOf(this.recipeRepository.findAllByOwner((UserEntity) user));
|
||||
public List<Recipe> 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;
|
||||
}
|
||||
return entity.getCachedRenderedText();
|
||||
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());
|
||||
}
|
||||
|
||||
@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()
|
||||
@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
|
||||
));
|
||||
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;
|
||||
final Set<UserEntity> 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<UserEntity> 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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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 + ")";
|
||||
|
@ -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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -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<RecipeStarEntity, Long> {
|
||||
List<RecipeStarEntity> findAllByOwner(UserEntity user);
|
||||
long countAllByOwner(UserEntity user);
|
||||
Optional<RecipeStarEntity> findByOwnerAndRecipe(UserEntity user, RecipeEntity recipe);
|
||||
|
||||
@Query("SELECT star FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerUsername = ?2")
|
||||
Optional<RecipeStarEntity> 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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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<T, ID> extends BaseMatcher<Iterable<T>> {
|
||||
private final List<T> allExpected;
|
||||
private final Predicate<Object> isT;
|
||||
private final Function<T, ID> idFunction;
|
||||
private final BiPredicate<ID, ID> equalsFunction;
|
||||
|
||||
public ContainsItemsMatcher(List<T> allExpected, Predicate<Object> isT, Function<T, ID> idFunction) {
|
||||
public ContainsItemsMatcher(
|
||||
List<T> allExpected,
|
||||
Predicate<Object> isT,
|
||||
Function<T, ID> idFunction,
|
||||
BiPredicate<ID, ID> equalsFunction
|
||||
) {
|
||||
this.allExpected = allExpected;
|
||||
this.isT = isT;
|
||||
this.idFunction = idFunction;
|
||||
this.equalsFunction = equalsFunction;
|
||||
}
|
||||
|
||||
public ContainsItemsMatcher(
|
||||
List<T> allExpected,
|
||||
Predicate<Object> isT,
|
||||
Function<T, ID> idFunction
|
||||
) {
|
||||
this(allExpected, isT, idFunction, Objects::equals);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -28,7 +44,7 @@ public class ContainsItemsMatcher<T, ID> extends BaseMatcher<Iterable<T>> {
|
||||
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)
|
||||
)
|
||||
|
@ -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<RecipeStar, Long> {
|
||||
public class ContainsRecipeStarsMatcher extends ContainsItemsMatcher<RecipeStar, RecipeStarId> {
|
||||
|
||||
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())
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user