RecipeController for getting RecipePageView.

This commit is contained in:
JesseBrault0709 2024-07-09 15:11:05 +02:00
parent 97bbab3cf0
commit 019210d334
8 changed files with 172 additions and 18 deletions

View File

@ -35,7 +35,7 @@ public class RecipeControllerTests {
}
private Recipe createTestRecipe(User owner) {
return this.recipeService.create(owner, "Test Recipe", "Hello, World!");
return this.recipeService.create(owner, "Test Recipe", "# Hello, World!");
}
@Test
@ -46,7 +46,12 @@ public class RecipeControllerTests {
this.mockMvc.perform(get("/recipe/{id}", recipe.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.title").value("Test Recipe"));
.andExpect(jsonPath("$.title").value("Test Recipe"))
.andExpect(jsonPath("$.text").value("<h1>Hello, World!</h1>"))
.andExpect(jsonPath("$.ownerId").value(owner.getId()))
.andExpect(jsonPath("$.ownerUsername").value(owner.getUsername()))
.andExpect(jsonPath("$.starCount").value(0))
.andExpect(jsonPath("$.viewerCount").value(0));
}
}

View File

@ -1,7 +1,7 @@
package app.mealsmadeeasy.api.recipe;
import app.mealsmadeeasy.api.recipe.view.RecipeExceptionView;
import app.mealsmadeeasy.api.recipe.view.RecipeGetView;
import app.mealsmadeeasy.api.recipe.view.RecipePageView;
import app.mealsmadeeasy.api.user.User;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@ -26,15 +26,9 @@ public class RecipeController {
}
@GetMapping("/{id}")
public ResponseEntity<RecipeGetView> getById(@PathVariable long id, @AuthenticationPrincipal User user)
public ResponseEntity<RecipePageView> getById(@PathVariable long id, @AuthenticationPrincipal User user)
throws RecipeException {
final Recipe recipe;
if (user != null) {
recipe = this.recipeService.getById(id, user);
} else {
recipe = this.recipeService.getById(id);
}
return ResponseEntity.ok(new RecipeGetView(recipe.getId(), recipe.getTitle()));
return ResponseEntity.ok(this.recipeService.getPageViewById(id, user));
}
}

View File

@ -28,4 +28,10 @@ public interface RecipeRepository extends JpaRepository<RecipeEntity, Long> {
@EntityGraph(attributePaths = { "stars" })
Optional<RecipeEntity> findByIdWithStars(long id);
@Query("SELECT size(r.stars) FROM Recipe r WHERE r.id = ?1")
int getStarCount(long recipeId);
@Query("SELECT size(r.viewers) FROM Recipe r WHERE r.id = ?1")
int getViewerCount(long recipeId);
}

View File

@ -1,9 +1,11 @@
package app.mealsmadeeasy.api.recipe;
import app.mealsmadeeasy.api.user.User;
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, User user);
boolean isViewableBy(Recipe recipe, @Nullable User user);
boolean isViewableBy(long recipeId, @Nullable User user) throws RecipeException;
}

View File

@ -1,6 +1,7 @@
package app.mealsmadeeasy.api.recipe;
import app.mealsmadeeasy.api.user.User;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;
import java.util.Objects;
@ -29,10 +30,18 @@ public class RecipeSecurityImpl implements RecipeSecurity {
}
@Override
public boolean isViewableBy(Recipe recipe, User user) {
if (Objects.equals(recipe.getOwner().getId(), user.getId())) {
public boolean isViewableBy(Recipe recipe, @Nullable User user) {
if (recipe.isPublic()) {
// public recipe
return true;
} else if (user == null) {
// a non-public recipe with no principal
return false;
} else if (Objects.equals(recipe.getOwner().getId(), user.getId())) {
// is owner
return true;
} else {
// check if viewer
final RecipeEntity withViewers = this.recipeRepository.getByIdWithViewers(recipe.getId());
for (final User viewer : withViewers.getViewers()) {
if (viewer.getId() != null && viewer.getId().equals(user.getId())) {
@ -40,7 +49,17 @@ public class RecipeSecurityImpl implements RecipeSecurity {
}
}
}
// non-public recipe and not viewer
return false;
}
@Override
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
));
return this.isViewableBy(recipe, user);
}
}

View File

@ -2,7 +2,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.view.RecipePageView;
import app.mealsmadeeasy.api.user.User;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ -17,6 +19,8 @@ public interface RecipeService {
Recipe getByIdWithStars(long id) throws RecipeException;
Recipe getByIdWithStars(long id, User viewer) throws RecipeException;
RecipePageView getPageViewById(long id, @Nullable User viewer) throws RecipeException;
List<Recipe> getByMinimumStars(long minimumStars);
List<Recipe> getByMinimumStars(long minimumStars, User viewer);
@ -33,12 +37,14 @@ public interface RecipeService {
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);

View File

@ -6,11 +6,13 @@ 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.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;
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import org.springframework.security.access.prepost.PostAuthorize;
@ -82,7 +84,7 @@ public class RecipeServiceImpl implements RecipeService {
}
@Override
@PostAuthorize("returnObject.isPublic || @recipeSecurity.isViewableBy(returnObject, #viewer)")
@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,
@ -100,7 +102,7 @@ public class RecipeServiceImpl implements RecipeService {
}
@Override
@PostAuthorize("returnObject.isPublic || @recipeSecurity.isViewableBy(returnObject, #viewer)")
@PostAuthorize("@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,
@ -108,6 +110,23 @@ public class RecipeServiceImpl implements RecipeService {
));
}
@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();
view.setId(recipe.getId());
view.setCreated(recipe.getCreated());
view.setModified(recipe.getModified());
view.setTitle(recipe.getTitle());
view.setText(this.getRenderedMarkdown(recipe, viewer));
view.setOwnerId(recipe.getOwner().getId());
view.setOwnerUsername(recipe.getOwner().getUsername());
view.setStarCount(this.getStarCount(recipe, viewer));
view.setViewerCount(this.getViewerCount(recipe, viewer));
return view;
}
@Override
public List<Recipe> getByMinimumStars(long minimumStars) {
return List.copyOf(this.recipeRepository.findAllPublicByStarsGreaterThanEqual(minimumStars));
@ -136,7 +155,7 @@ public class RecipeServiceImpl implements RecipeService {
}
@Override
@PreAuthorize("#recipe.isPublic || @recipeSecurity.isViewableBy(#recipe, #viewer)")
@PreAuthorize("@recipeSecurity.isViewableBy(#recipe, #viewer)")
public String getRenderedMarkdown(Recipe recipe, User viewer) {
RecipeEntity entity = (RecipeEntity) recipe;
if (entity.getCachedRenderedText() == null) {
@ -164,7 +183,7 @@ public class RecipeServiceImpl implements RecipeService {
}
@Override
@PreAuthorize("#recipe.isPublic || @recipeSecurity.isViewableBy(#recipe, #giver)")
@PreAuthorize("@recipeSecurity.isViewableBy(#recipe, #giver)")
public RecipeStar addStar(Recipe recipe, User giver) {
final RecipeStarEntity star = new RecipeStarEntity();
star.setOwner((UserEntity) giver);
@ -189,6 +208,12 @@ public class RecipeServiceImpl implements RecipeService {
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) {
@ -222,6 +247,12 @@ public class RecipeServiceImpl implements RecipeService {
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)

View File

@ -0,0 +1,91 @@
package app.mealsmadeeasy.api.recipe.view;
import org.jetbrains.annotations.Nullable;
import java.time.LocalDateTime;
public class RecipePageView {
private long id;
private LocalDateTime created;
private LocalDateTime modified;
private String title;
private String text;
private long ownerId;
private String ownerUsername;
private int starCount;
private int viewerCount;
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
public LocalDateTime getCreated() {
return this.created;
}
public void setCreated(LocalDateTime created) {
this.created = created;
}
public LocalDateTime getModified() {
return this.modified;
}
public void setModified(LocalDateTime modified) {
this.modified = modified;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public @Nullable String getText() {
return this.text;
}
public void setText(String text) {
this.text = text;
}
public long getOwnerId() {
return this.ownerId;
}
public void setOwnerId(long ownerId) {
this.ownerId = ownerId;
}
public String getOwnerUsername() {
return this.ownerUsername;
}
public void setOwnerUsername(String ownerUsername) {
this.ownerUsername = ownerUsername;
}
public int getStarCount() {
return this.starCount;
}
public void setStarCount(int starCount) {
this.starCount = starCount;
}
public int getViewerCount() {
return this.viewerCount;
}
public void setViewerCount(int viewerCount) {
this.viewerCount = viewerCount;
}
}