meals-made-easy-api/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java
2024-07-08 18:50:22 +02:00

279 lines
10 KiB
Java

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.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.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
public class RecipeServiceImpl implements RecipeService {
private static String renderAndCleanMarkdown(String rawText) {
final var parser = Parser.builder().build();
final var node = parser.parse(rawText);
final var htmlRenderer = HtmlRenderer.builder().build();
final String unsafeHtml = htmlRenderer.render(node);
return Jsoup.clean(unsafeHtml, Safelist.relaxed());
}
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
) {
this.recipeRepository = recipeRepository;
this.userRepository = userRepository;
this.recipeStarRepository = recipeStarRepository;
this.recipeCommentRepository = recipeCommentRepository;
}
@Override
public Recipe create(String ownerUsername, String title, String rawText) throws RecipeException {
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);
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 {
return this.recipeRepository.findById(id).orElseThrow(() -> new RecipeException(
RecipeException.Type.INVALID_ID,
"No such recipe for id " + id
));
}
@Override
@PostAuthorize("returnObject.isPublic || @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
));
}
@Override
@PostAuthorize("returnObject.isPublic || @recipeSecurity.isViewableBy(returnObject, #viewer)")
public Recipe getByIdWithStars(long id, User viewer) throws RecipeException {
return this.recipeRepository.findByIdWithStars(id).orElseThrow(() -> new RecipeException(
RecipeException.Type.INVALID_ID,
"No such recipe for id " + id
));
}
@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(
this.recipeRepository.findAllViewableByStarsGreaterThanEqual(minimumStars, (UserEntity) viewer)
);
}
@Override
public List<Recipe> getPublicRecipes() {
return List.copyOf(this.recipeRepository.findAllByIsPublicIsTrue());
}
@Override
public List<Recipe> getRecipesViewableBy(User user) {
return List.copyOf(this.recipeRepository.findAllByViewersContaining((UserEntity) user));
}
@Override
public List<Recipe> getRecipesOwnedBy(User user) {
return List.copyOf(this.recipeRepository.findAllByOwner((UserEntity) user));
}
@Override
public String getRenderedMarkdown(Recipe recipe) {
RecipeEntity entity = (RecipeEntity) recipe;
if (entity.getCachedRenderedText() == null) {
entity.setCachedRenderedText(renderAndCleanMarkdown(entity.getRawText()));
entity = this.recipeRepository.save(entity);
}
return entity.getCachedRenderedText();
}
@Override
public Recipe updateRawText(Recipe recipe, 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("#recipe.isPublic || @recipeSecurity.isViewableBy(#recipe, #giver)")
public RecipeStar addStar(Recipe recipe, User giver) {
final RecipeStarEntity star = new RecipeStarEntity();
star.setOwner((UserEntity) giver);
star.setRecipe((RecipeEntity) recipe);
return this.recipeStarRepository.save(star);
}
@Override
public void deleteStar(RecipeStar recipeStar) {
this.recipeStarRepository.delete((RecipeStarEntity) recipeStar);
}
@Override
public void deleteStarByUser(Recipe recipe, User giver) throws RecipeException {
final RecipeStarEntity star = this.recipeStarRepository.findByOwnerAndRecipe(
(UserEntity) giver,
(RecipeEntity) recipe
).orElseThrow(() -> new RecipeException(
RecipeException.Type.INVALID_STAR,
"No such star for user " + giver.getUsername() + " and recipe " + recipe.getId()
));
this.recipeStarRepository.delete(star);
}
@Override
public Recipe setPublic(Recipe recipe, 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);
entity.setViewers(viewers);
return this.recipeRepository.save(entity);
}
@Override
public Recipe removeViewer(Recipe recipe, User user) {
final RecipeEntity entity = (RecipeEntity) recipe;
final Set<UserEntity> viewers = new HashSet<>(entity.getViewerEntities());
viewers.remove((UserEntity) user);
entity.setViewers(viewers);
return this.recipeRepository.save(entity);
}
@Override
public Recipe clearViewers(Recipe recipe) {
final RecipeEntity entity = (RecipeEntity) recipe;
entity.setViewers(new HashSet<>());
return this.recipeRepository.save(entity);
}
@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
public void deleteRecipe(Recipe recipe) {
this.recipeRepository.delete((RecipeEntity) recipe);
}
@Override
public void deleteById(long id) {
this.recipeRepository.deleteById(id);
}
}