diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeRepositoryTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeRepositoryTests.java index 26ecf77..6d9dc34 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeRepositoryTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeRepositoryTests.java @@ -1,6 +1,12 @@ package app.mealsmadeeasy.api.recipe; import app.mealsmadeeasy.api.PostgresTestsExtension; +import app.mealsmadeeasy.api.image.Image; +import app.mealsmadeeasy.api.image.ImageRepository; +import app.mealsmadeeasy.api.recipe.comment.RecipeComment; +import app.mealsmadeeasy.api.recipe.comment.RecipeCommentRepository; +import app.mealsmadeeasy.api.recipe.star.RecipeStar; +import app.mealsmadeeasy.api.recipe.star.RecipeStarRepository; import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.UserRepository; import org.junit.jupiter.api.Test; @@ -16,6 +22,7 @@ import java.util.Set; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @ExtendWith(PostgresTestsExtension.class) @@ -27,6 +34,15 @@ public class RecipeRepositoryTests { @Autowired private UserRepository userRepository; + @Autowired + private RecipeStarRepository recipeStarRepository; + + @Autowired + private RecipeCommentRepository recipeCommentRepository; + + @Autowired + private ImageRepository imageRepository; + private User seedUser() { final String uuid = UUID.randomUUID().toString(); final User draft = User.getDefaultDraft(); @@ -36,6 +52,15 @@ public class RecipeRepositoryTests { return this.userRepository.save(draft); } + private Recipe seedRecipe(User owner) { + final Recipe recipe = new Recipe(); + recipe.setOwner(owner); + recipe.setTitle("Test Recipe"); + recipe.setSlug(UUID.randomUUID().toString()); + recipe.setRawText("Hello, World!"); + return this.recipeRepository.save(recipe); + } + @Test public void findsAllPublicRecipes() { final Recipe publicRecipe = new Recipe(); @@ -101,4 +126,59 @@ public class RecipeRepositoryTests { assertThat(viewable.size()).isEqualTo(0); } + @Test + public void deleteRecipeWithStar() { + final User user = this.seedUser(); + final Recipe recipe = this.seedRecipe(user); + final RecipeStar star = new RecipeStar(); + star.setOwner(user); + star.setRecipe(recipe); + recipe.getStars().add(star); + this.recipeStarRepository.save(star); + assertDoesNotThrow(() -> this.recipeRepository.delete(recipe)); + } + + @Test + public void deleteRecipeWithComment() { + final User user = this.seedUser(); + final Recipe recipe = this.seedRecipe(user); + final RecipeComment recipeComment = new RecipeComment(); + recipeComment.setRecipe(recipe); + recipeComment.setOwner(user); + recipeComment.setRawText("Hello, World!"); + recipe.getComments().add(recipeComment); + this.recipeCommentRepository.save(recipeComment); + assertDoesNotThrow(() -> this.recipeRepository.delete(recipe)); + } + + @Test + public void deleteRecipeWithViewer() { + final User user = this.seedUser(); + final Recipe recipe = this.seedRecipe(user); + recipe.getViewers().add(user); + this.recipeRepository.save(recipe); + assertDoesNotThrow(() -> this.recipeRepository.delete(recipe)); + } + + @Test + public void deleteRecipeWithMainImage() { + final User user = this.seedUser(); + final Recipe recipe = this.seedRecipe(user); + + final Image mainImage = new Image(); + mainImage.setUserFilename(UUID.randomUUID().toString()); + mainImage.setMimeType("image/jpeg"); + mainImage.setObjectName(UUID.randomUUID().toString()); + mainImage.setOwner(user); + final Image savedMainImage = this.imageRepository.save(mainImage); + + recipe.setSlug(UUID.randomUUID().toString()); + recipe.setTitle("Hello, World!"); + recipe.setRawText("# Hello, World!"); + recipe.setMainImage(savedMainImage); + this.recipeRepository.save(recipe); + + assertDoesNotThrow(() -> this.recipeRepository.delete(recipe)); + } + } diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java index 87783a8..6461404 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepositoryTests.java @@ -54,10 +54,8 @@ public class RecipeStarRepositoryTests { final Recipe recipe = this.getTestRecipe(owner); final RecipeStar starDraft = new RecipeStar(); - final RecipeStarId starId = new RecipeStarId(); - starId.setRecipeId(recipe.getId()); - starId.setOwnerId(owner.getId()); - starDraft.setId(starId); + starDraft.setRecipe(recipe); + starDraft.setOwner(owner); this.recipeStarRepository.save(starDraft); assertThat( diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java b/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java index 91732b7..b4051df 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java @@ -5,15 +5,20 @@ import app.mealsmadeeasy.api.recipe.comment.RecipeComment; import app.mealsmadeeasy.api.recipe.star.RecipeStar; import app.mealsmadeeasy.api.user.User; import jakarta.persistence.*; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import org.jetbrains.annotations.Nullable; import java.time.OffsetDateTime; import java.util.HashSet; +import java.util.Objects; import java.util.Set; @Entity -@Data +@Getter +@Setter +@ToString public class Recipe { @Id @@ -87,4 +92,20 @@ public class Recipe { this.modified = OffsetDateTime.now(); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof Recipe other) { + return Objects.equals(this.owner.getUsername(), other.owner.getUsername()) + && Objects.equals(this.slug, other.slug); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.owner.getUsername(), this.slug); + } + } 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 01ee5d3..8b7c84e 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStar.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStar.java @@ -2,30 +2,58 @@ package app.mealsmadeeasy.api.recipe.star; import app.mealsmadeeasy.api.recipe.Recipe; import app.mealsmadeeasy.api.user.User; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import java.time.OffsetDateTime; +import java.util.Objects; @Entity @Table(name = "recipe_star") -@Data +@Getter +@Setter +@ToString public final class RecipeStar { - @EmbeddedId - private RecipeStarId id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Basic(optional = false) + @Column(updatable = false) + @JsonIgnore + private Long id; @ManyToOne - @MapsId("ownerId") @JoinColumn(name = "owner_id", nullable = false, updatable = false) + @JsonIgnore private User owner; @ManyToOne - @MapsId("recipeId") @JoinColumn(name = "recipe_id", nullable = false, updatable = false) + @JsonIgnore private Recipe recipe; - @Column(nullable = false, updatable = false) + @Basic(optional = false) + @Column(updatable = false) private OffsetDateTime timestamp = OffsetDateTime.now(); + @PrePersist + public void prePersist() { + this.timestamp = OffsetDateTime.now(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof RecipeStar other) { + return Objects.equals(this.recipe.getOwner().getUsername(), other.getOwner().getUsername()) + && Objects.equals(this.recipe.getSlug(), other.getRecipe().getSlug()) + && Objects.equals(this.owner.getUsername(), other.getOwner().getUsername()); + } else { + return false; + } + } + } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarId.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarId.java deleted file mode 100644 index 994e393..0000000 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarId.java +++ /dev/null @@ -1,17 +0,0 @@ -package app.mealsmadeeasy.api.recipe.star; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import lombok.Data; - -@Embeddable -@Data -public class RecipeStarId { - - @Column(name = "owner_id", nullable = false) - private Integer ownerId; - - @Column(name = "recipe_id", nullable = false) - private Integer recipeId; - -} 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 cf09917..7fee276 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarRepository.java @@ -2,22 +2,18 @@ package app.mealsmadeeasy.api.recipe.star; import jakarta.transaction.Transactional; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import java.util.Optional; public interface RecipeStarRepository extends JpaRepository { - @Query("SELECT star FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerId = ?2") Optional findByRecipeIdAndOwnerId(Integer recipeId, Integer ownerId); - @Query("SELECT count(rs) > 0 FROM RecipeStar rs, Recipe r WHERE r.owner.username = ?1 AND r.slug = ?2 AND r.id = rs.id.recipeId AND rs.id.ownerId = ?3") + @Query("SELECT count(rs) > 0 FROM RecipeStar rs, Recipe r WHERE r.owner.username = ?1 AND r.slug = ?2 AND r.id = rs.recipe.id AND rs.owner.id = ?3") boolean isStarer(String ownerUsername, String slug, Integer viewerId); - @Modifying @Transactional - @Query("DELETE FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerId = ?2") void deleteByRecipeIdAndOwnerId(Integer recipeId, Integer ownerId); } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java index 43f3c18..297680c 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarService.java @@ -1,15 +1,68 @@ package app.mealsmadeeasy.api.recipe.star; +import app.mealsmadeeasy.api.recipe.Recipe; +import app.mealsmadeeasy.api.recipe.RecipeRepository; +import app.mealsmadeeasy.api.recipe.RecipeService; import app.mealsmadeeasy.api.user.User; +import app.mealsmadeeasy.api.user.UserRepository; +import app.mealsmadeeasy.api.util.NoSuchEntityWithIdException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import java.util.Optional; -public interface RecipeStarService { - RecipeStar create(Integer recipeId, Integer ownerId); - RecipeStar create(String recipeOwnerUsername, String recipeSlug, User starer); +@Service +@RequiredArgsConstructor +public class RecipeStarService { - Optional find(String recipeOwnerUsername, String recipeSlug, User starer); + private final RecipeStarRepository recipeStarRepository; + private final UserRepository userRepository; + private final RecipeRepository recipeRepository; + private final RecipeService recipeService; + + public RecipeStar create(Recipe recipe, User owner) { + final RecipeStar recipeStar = new RecipeStar(); + recipeStar.setOwner(owner); + recipeStar.setRecipe(recipe); + return this.recipeStarRepository.save(recipeStar); + } + + @Deprecated + public RecipeStar create(Integer recipeId, Integer ownerId) { + return this.recipeStarRepository.findByRecipeIdAndOwnerId(recipeId, ownerId) + .orElseGet(() -> { + final User owner = this.userRepository.findById((long) ownerId).orElseThrow( + () -> new NoSuchEntityWithIdException(User.class, ownerId) + ); + final Recipe recipe = this.recipeRepository.findById(recipeId).orElseThrow( + () -> new NoSuchEntityWithIdException(Recipe.class, recipeId) + ); + return this.create(recipe, owner); + }); + } + + @Deprecated + public RecipeStar create(String recipeOwnerUsername, String recipeSlug, User starer) { + final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer); + final Optional existing = this.recipeStarRepository.findByRecipeIdAndOwnerId( + recipe.getId(), + starer.getId() + ); + return existing.orElseGet(() -> this.create(recipe, starer)); + } + + public Optional find(String recipeOwnerUsername, String recipeSlug, User starer) { + final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer); + return this.recipeStarRepository.findByRecipeIdAndOwnerId(recipe.getId(), starer.getId()); + } + + public void delete(Integer recipeId, Integer ownerId) { + this.recipeStarRepository.deleteByRecipeIdAndOwnerId(recipeId, ownerId); + } + + public void delete(String recipeOwnerUsername, String recipeSlug, User starer) { + final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer); + this.delete(recipe.getId(), starer.getId()); + } - void delete(Integer recipeId, Integer ownerId); - void delete(String recipeOwnerUsername, String recipeSlug, User starer); } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceImpl.java deleted file mode 100644 index 7d0cae7..0000000 --- a/src/main/java/app/mealsmadeeasy/api/recipe/star/RecipeStarServiceImpl.java +++ /dev/null @@ -1,63 +0,0 @@ -package app.mealsmadeeasy.api.recipe.star; - -import app.mealsmadeeasy.api.recipe.Recipe; -import app.mealsmadeeasy.api.recipe.RecipeService; -import app.mealsmadeeasy.api.user.User; -import org.springframework.stereotype.Service; - -import java.time.OffsetDateTime; -import java.util.Optional; - -@Service -public class RecipeStarServiceImpl implements RecipeStarService { - - private final RecipeStarRepository recipeStarRepository; - private final RecipeService recipeService; - - public RecipeStarServiceImpl(RecipeStarRepository recipeStarRepository, RecipeService recipeService) { - this.recipeStarRepository = recipeStarRepository; - this.recipeService = recipeService; - } - - @Override - public RecipeStar create(Integer recipeId, Integer ownerId) { - final RecipeStar draft = new RecipeStar(); - final RecipeStarId id = new RecipeStarId(); - id.setRecipeId(recipeId); - id.setOwnerId(ownerId); - draft.setId(id); - draft.setTimestamp(OffsetDateTime.now()); - return this.recipeStarRepository.save(draft); - } - - @Override - public RecipeStar create(String recipeOwnerUsername, String recipeSlug, User starer) { - final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer); - final Optional existing = this.recipeStarRepository.findByRecipeIdAndOwnerId( - recipe.getId(), - starer.getId() - ); - if (existing.isPresent()) { - return existing.get(); - } - return this.create(recipe.getId(), starer.getId()); - } - - @Override - public Optional find(String recipeOwnerUsername, String recipeSlug, User starer) { - final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer); - return this.recipeStarRepository.findByRecipeIdAndOwnerId(recipe.getId(), starer.getId()); - } - - @Override - public void delete(Integer recipeId, Integer ownerId) { - this.recipeStarRepository.deleteByRecipeIdAndOwnerId(recipeId, ownerId); - } - - @Override - public void delete(String recipeOwnerUsername, String recipeSlug, User starer) { - final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer); - this.delete(recipe.getId(), starer.getId()); - } - -} diff --git a/src/main/resources/db/migration/V12__add_id_to_recipe_star.sql b/src/main/resources/db/migration/V12__add_id_to_recipe_star.sql new file mode 100644 index 0000000..5aae120 --- /dev/null +++ b/src/main/resources/db/migration/V12__add_id_to_recipe_star.sql @@ -0,0 +1,3 @@ +ALTER TABLE recipe_star ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY; +ALTER TABLE recipe_star DROP CONSTRAINT recipe_star_pkey; +ALTER TABLE recipe_star ADD PRIMARY KEY (id); diff --git a/src/testFixtures/java/app/mealsmadeeasy/api/recipe/ContainsRecipeStarsMatcher.java b/src/testFixtures/java/app/mealsmadeeasy/api/recipe/ContainsRecipeStarsMatcher.java index ce078c0..b59a5ce 100644 --- a/src/testFixtures/java/app/mealsmadeeasy/api/recipe/ContainsRecipeStarsMatcher.java +++ b/src/testFixtures/java/app/mealsmadeeasy/api/recipe/ContainsRecipeStarsMatcher.java @@ -2,12 +2,11 @@ package app.mealsmadeeasy.api.recipe; import app.mealsmadeeasy.api.matchers.ContainsItemsMatcher; import app.mealsmadeeasy.api.recipe.star.RecipeStar; -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); @@ -19,8 +18,7 @@ public class ContainsRecipeStarsMatcher extends ContainsItemsMatcher o instanceof RecipeStar, RecipeStar::getId, RecipeStar::getId, - (id0, id1) -> Objects.equals(id0.getRecipeId(), id1.getRecipeId()) - && Objects.equals(id0.getOwnerId(), id1.getOwnerId()) + Objects::equals ); }