MME-13 Fix hibernate delete problems with better equals/hashcode and easier ids.
This commit is contained in:
parent
6eead31193
commit
fd08a4df13
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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<RecipeStar, Long> {
|
||||
|
||||
@Query("SELECT star FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerId = ?2")
|
||||
Optional<RecipeStar> 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);
|
||||
|
||||
}
|
||||
|
||||
@ -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<RecipeStar> 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<RecipeStar> existing = this.recipeStarRepository.findByRecipeIdAndOwnerId(
|
||||
recipe.getId(),
|
||||
starer.getId()
|
||||
);
|
||||
return existing.orElseGet(() -> this.create(recipe, starer));
|
||||
}
|
||||
|
||||
public Optional<RecipeStar> 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);
|
||||
}
|
||||
|
||||
@ -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<RecipeStar> existing = this.recipeStarRepository.findByRecipeIdAndOwnerId(
|
||||
recipe.getId(),
|
||||
starer.getId()
|
||||
);
|
||||
if (existing.isPresent()) {
|
||||
return existing.get();
|
||||
}
|
||||
return this.create(recipe.getId(), starer.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RecipeStar> 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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
@ -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<RecipeStar, RecipeStar, RecipeStarId> {
|
||||
public class ContainsRecipeStarsMatcher extends ContainsItemsMatcher<RecipeStar, RecipeStar, Long> {
|
||||
|
||||
public static ContainsRecipeStarsMatcher containsStars(RecipeStar... expected) {
|
||||
return new ContainsRecipeStarsMatcher(expected);
|
||||
@ -19,8 +18,7 @@ public class ContainsRecipeStarsMatcher extends ContainsItemsMatcher<RecipeStar,
|
||||
o -> o instanceof RecipeStar,
|
||||
RecipeStar::getId,
|
||||
RecipeStar::getId,
|
||||
(id0, id1) -> Objects.equals(id0.getRecipeId(), id1.getRecipeId())
|
||||
&& Objects.equals(id0.getOwnerId(), id1.getOwnerId())
|
||||
Objects::equals
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user