MME-8 Add endpoints necessary for recipe main image selection. Refactor SliceView.
This commit is contained in:
parent
02c7e8887e
commit
f9c1d41501
@ -130,6 +130,24 @@ public class ImageControllerTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getOwnedImages() throws Exception {
|
||||||
|
final User owner = this.seedUser();
|
||||||
|
this.seedImage(owner);
|
||||||
|
this.mockMvc.perform(
|
||||||
|
get("/images")
|
||||||
|
.header("Authorization", "Bearer " + this.getAccessToken(owner))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.count").value(1))
|
||||||
|
.andExpect(jsonPath("$.content").isArray())
|
||||||
|
.andExpect(jsonPath("$.content").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.content[0].url").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.slice.size").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.slice.number", is(0)))
|
||||||
|
.andExpect(jsonPath("$.slice.hasNext", is(false)));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getImageForOwner() throws Exception {
|
public void getImageForOwner() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.seedUser();
|
||||||
@ -204,10 +222,6 @@ public class ImageControllerTests {
|
|||||||
.param("caption", "HAL 9000, from 2001: A Space Odyssey")
|
.param("caption", "HAL 9000, from 2001: A Space Odyssey")
|
||||||
.param("isPublic", "true")
|
.param("isPublic", "true")
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
.with(req -> {
|
|
||||||
req.setMethod("PUT");
|
|
||||||
return req;
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
.andExpect(jsonPath("$.created").exists())
|
.andExpect(jsonPath("$.created").exists())
|
||||||
@ -231,7 +245,7 @@ public class ImageControllerTests {
|
|||||||
final ImageUpdateBody body = new ImageUpdateBody();
|
final ImageUpdateBody body = new ImageUpdateBody();
|
||||||
body.setAlt("HAL 9000");
|
body.setAlt("HAL 9000");
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(owner, image))
|
put(getImageUrl(owner, image))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -249,7 +263,7 @@ public class ImageControllerTests {
|
|||||||
final ImageUpdateBody body = new ImageUpdateBody();
|
final ImageUpdateBody body = new ImageUpdateBody();
|
||||||
body.setCaption("HAL 9000 from 2001: A Space Odyssey");
|
body.setCaption("HAL 9000 from 2001: A Space Odyssey");
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(owner, image))
|
put(getImageUrl(owner, image))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -267,7 +281,7 @@ public class ImageControllerTests {
|
|||||||
final ImageUpdateBody body = new ImageUpdateBody();
|
final ImageUpdateBody body = new ImageUpdateBody();
|
||||||
body.setIsPublic(true);
|
body.setIsPublic(true);
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(owner, image))
|
put(getImageUrl(owner, image))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -289,7 +303,7 @@ public class ImageControllerTests {
|
|||||||
body.setViewersToAdd(viewerUsernames);
|
body.setViewersToAdd(viewerUsernames);
|
||||||
|
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(owner, image))
|
put(getImageUrl(owner, image))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -322,7 +336,7 @@ public class ImageControllerTests {
|
|||||||
body.setViewersToRemove(Set.of(ownerViewerImage.viewer().getUsername()));
|
body.setViewersToRemove(Set.of(ownerViewerImage.viewer().getUsername()));
|
||||||
|
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
|
put(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -339,7 +353,7 @@ public class ImageControllerTests {
|
|||||||
final ImageUpdateBody body = new ImageUpdateBody();
|
final ImageUpdateBody body = new ImageUpdateBody();
|
||||||
body.setClearAllViewers(true);
|
body.setClearAllViewers(true);
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
|
put(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -355,7 +369,7 @@ public class ImageControllerTests {
|
|||||||
final String accessToken = this.getAccessToken(ownerViewerImage.viewer()); // viewer
|
final String accessToken = this.getAccessToken(ownerViewerImage.viewer()); // viewer
|
||||||
final ImageUpdateBody body = new ImageUpdateBody();
|
final ImageUpdateBody body = new ImageUpdateBody();
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
|
put(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -389,4 +403,42 @@ public class ImageControllerTests {
|
|||||||
.andExpect(status().isForbidden());
|
.andExpect(status().isForbidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenImageExists_existsIsTrue() throws Exception {
|
||||||
|
final User owner = this.seedUser();
|
||||||
|
final Image image = this.seedImage(owner);
|
||||||
|
|
||||||
|
this.mockMvc.perform(
|
||||||
|
get("/images/{username}/{filename}/exists", image.getOwner().getUsername(), image.getUserFilename())
|
||||||
|
.header("Authorization", "Bearer " + this.getAccessToken(owner))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.exists").value(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenImageDoesNotExist_existsIsFalse() throws Exception {
|
||||||
|
final User owner = this.seedUser();
|
||||||
|
this.mockMvc.perform(
|
||||||
|
get("/images/{username}/fake-filename.jpeg/exists", owner.getUsername())
|
||||||
|
.header("Authorization", "Bearer " + this.getAccessToken(owner))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.exists").value(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cannotDoExistsOnInaccessibleImage() throws Exception {
|
||||||
|
final User owner = this.seedUser();
|
||||||
|
final User viewer = this.seedUser();
|
||||||
|
final Image image = this.seedImage(owner);
|
||||||
|
final String viewerAccessToken = this.getAccessToken(viewer);
|
||||||
|
|
||||||
|
this.mockMvc.perform(
|
||||||
|
get("/images/{username}/{filename}/exists", owner.getUsername(), image.getUserFilename())
|
||||||
|
.header("Authorization", "Bearer " + viewerAccessToken)
|
||||||
|
)
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -182,6 +182,7 @@ public class RecipesControllerTests {
|
|||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
this.mockMvc.perform(get("/recipes"))
|
this.mockMvc.perform(get("/recipes"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.count").isNotEmpty())
|
||||||
.andExpect(jsonPath("$.slice.number").value(0))
|
.andExpect(jsonPath("$.slice.number").value(0))
|
||||||
.andExpect(jsonPath("$.slice.size").value(20))
|
.andExpect(jsonPath("$.slice.size").value(20))
|
||||||
.andExpect(jsonPath("$.content").isArray())
|
.andExpect(jsonPath("$.content").isArray())
|
||||||
@ -200,6 +201,7 @@ public class RecipesControllerTests {
|
|||||||
.header("Authorization", "Bearer " + loginDetails.getAccessToken().getToken())
|
.header("Authorization", "Bearer " + loginDetails.getAccessToken().getToken())
|
||||||
)
|
)
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.count").isNotEmpty())
|
||||||
.andExpect(jsonPath("$.slice.number").value(0))
|
.andExpect(jsonPath("$.slice.number").value(0))
|
||||||
.andExpect(jsonPath("$.slice.size").value(20))
|
.andExpect(jsonPath("$.slice.size").value(20))
|
||||||
.andExpect(jsonPath("$.content").isArray())
|
.andExpect(jsonPath("$.content").isArray())
|
||||||
|
|||||||
@ -5,12 +5,16 @@ import app.mealsmadeeasy.api.image.converter.ImageToViewConverter;
|
|||||||
import app.mealsmadeeasy.api.image.converter.ImageUpdateBodyToSpecConverter;
|
import app.mealsmadeeasy.api.image.converter.ImageUpdateBodyToSpecConverter;
|
||||||
import app.mealsmadeeasy.api.image.spec.ImageCreateSpec;
|
import app.mealsmadeeasy.api.image.spec.ImageCreateSpec;
|
||||||
import app.mealsmadeeasy.api.image.view.ImageView;
|
import app.mealsmadeeasy.api.image.view.ImageView;
|
||||||
|
import app.mealsmadeeasy.api.sliceview.SliceView;
|
||||||
|
import app.mealsmadeeasy.api.sliceview.SliceViewService;
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import app.mealsmadeeasy.api.user.UserService;
|
import app.mealsmadeeasy.api.user.UserService;
|
||||||
import app.mealsmadeeasy.api.util.AccessDeniedView;
|
import app.mealsmadeeasy.api.util.AccessDeniedView;
|
||||||
import app.mealsmadeeasy.api.util.ResourceExistsView;
|
import app.mealsmadeeasy.api.util.ResourceExistsView;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.core.io.InputStreamResource;
|
import org.springframework.core.io.InputStreamResource;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Slice;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -34,6 +38,7 @@ public class ImageController {
|
|||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final ImageToViewConverter imageToViewConverter;
|
private final ImageToViewConverter imageToViewConverter;
|
||||||
private final ImageUpdateBodyToSpecConverter imageUpdateBodyToSpecConverter;
|
private final ImageUpdateBodyToSpecConverter imageUpdateBodyToSpecConverter;
|
||||||
|
private final SliceViewService sliceViewService;
|
||||||
|
|
||||||
@ExceptionHandler
|
@ExceptionHandler
|
||||||
public ResponseEntity<AccessDeniedView> onAccessDenied(AccessDeniedException e) {
|
public ResponseEntity<AccessDeniedView> onAccessDenied(AccessDeniedException e) {
|
||||||
@ -48,6 +53,19 @@ public class ImageController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<SliceView<ImageView>> getOwnedImages(
|
||||||
|
@AuthenticationPrincipal User principal,
|
||||||
|
Pageable pageable
|
||||||
|
) {
|
||||||
|
final Slice<Image> images = this.imageService.getOwnedImages(principal, pageable);
|
||||||
|
final Slice<ImageView> imageViews = images.map(image ->
|
||||||
|
this.imageToViewConverter.convert(image, principal, false)
|
||||||
|
);
|
||||||
|
final int count = this.imageService.countOwnedImages(principal);
|
||||||
|
return ResponseEntity.ok(this.sliceViewService.getSliceView(imageViews, count));
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/{username}/{filename}")
|
@GetMapping("/{username}/{filename}")
|
||||||
public ResponseEntity<InputStreamResource> getImage(
|
public ResponseEntity<InputStreamResource> getImage(
|
||||||
@AuthenticationPrincipal User principal,
|
@AuthenticationPrincipal User principal,
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package app.mealsmadeeasy.api.image;
|
package app.mealsmadeeasy.api.image;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Slice;
|
||||||
import org.springframework.data.jpa.repository.EntityGraph;
|
import org.springframework.data.jpa.repository.EntityGraph;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
@ -20,4 +22,9 @@ public interface ImageRepository extends JpaRepository<Image, Integer> {
|
|||||||
@Query("SELECT image from Image image WHERE image.owner.username = ?1 AND image.userFilename = ?2")
|
@Query("SELECT image from Image image WHERE image.owner.username = ?1 AND image.userFilename = ?2")
|
||||||
Optional<Image> findByOwnerUsernameAndFilename(String username, String filename);
|
Optional<Image> findByOwnerUsernameAndFilename(String username, String filename);
|
||||||
|
|
||||||
|
@Query("SELECT i FROM Image i WHERE i.owner = ?1")
|
||||||
|
Slice<Image> findAllOwnedBy(User user, Pageable pageable);
|
||||||
|
|
||||||
|
int countAllByOwner(User owner);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import app.mealsmadeeasy.api.image.spec.ImageCreateSpec;
|
|||||||
import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec;
|
import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec;
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Slice;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -20,6 +22,8 @@ public interface ImageService {
|
|||||||
|
|
||||||
InputStream getImageContent(Image image, @Nullable User viewer) throws IOException;
|
InputStream getImageContent(Image image, @Nullable User viewer) throws IOException;
|
||||||
List<Image> getImagesOwnedBy(User user);
|
List<Image> getImagesOwnedBy(User user);
|
||||||
|
Slice<Image> getOwnedImages(User user, Pageable pageable);
|
||||||
|
int countOwnedImages(User user);
|
||||||
|
|
||||||
boolean exists(User viewer, String username, String filename);
|
boolean exists(User viewer, String username, String filename);
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import app.mealsmadeeasy.api.util.NoSuchEntityWithIdException;
|
|||||||
import app.mealsmadeeasy.api.util.NoSuchEntityWithUsernameAndFilenameException;
|
import app.mealsmadeeasy.api.util.NoSuchEntityWithUsernameAndFilenameException;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Slice;
|
||||||
import org.springframework.security.access.prepost.PostAuthorize;
|
import org.springframework.security.access.prepost.PostAuthorize;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -196,6 +198,16 @@ public class S3ImageService implements ImageService {
|
|||||||
return new ArrayList<>(this.imageRepository.findAllByOwner(user));
|
return new ArrayList<>(this.imageRepository.findAllByOwner(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Slice<Image> getOwnedImages(User user, Pageable pageable) {
|
||||||
|
return this.imageRepository.findAllOwnedBy(user, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int countOwnedImages(User user) {
|
||||||
|
return this.imageRepository.countAllByOwner(user);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PreAuthorize("@imageSecurity.canSeeExists(#viewer, #username, #filename)")
|
@PreAuthorize("@imageSecurity.canSeeExists(#viewer, #username, #filename)")
|
||||||
public boolean exists(User viewer, String username, String filename) {
|
public boolean exists(User viewer, String username, String filename) {
|
||||||
|
|||||||
@ -69,4 +69,9 @@ public interface RecipeRepository extends JpaRepository<Recipe, Integer> {
|
|||||||
)
|
)
|
||||||
List<Recipe> searchByEmbeddingAndIsPublic(float[] queryEmbedding, float similarity);
|
List<Recipe> searchByEmbeddingAndIsPublic(float[] queryEmbedding, float similarity);
|
||||||
|
|
||||||
|
int countByIsPublicIsTrue();
|
||||||
|
|
||||||
|
@Query("SELECT count(r) FROM Recipe r WHERE ?1 MEMBER OF r.viewers OR r.isPublic IS TRUE")
|
||||||
|
int countViewableBy(User viewer);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -114,6 +114,10 @@ public class RecipeService {
|
|||||||
return this.recipeRepository.findAllViewableBy(viewer, pageable);
|
return this.recipeRepository.findAllViewableBy(viewer, pageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int countViewableBy(User viewer) {
|
||||||
|
return this.recipeRepository.countViewableBy(viewer);
|
||||||
|
}
|
||||||
|
|
||||||
public List<Recipe> getByMinimumStars(long minimumStars, @Nullable User viewer) {
|
public List<Recipe> getByMinimumStars(long minimumStars, @Nullable User viewer) {
|
||||||
return List.copyOf(
|
return List.copyOf(
|
||||||
this.recipeRepository.findAllViewableByStarsGreaterThanEqual(minimumStars, viewer)
|
this.recipeRepository.findAllViewableByStarsGreaterThanEqual(minimumStars, viewer)
|
||||||
@ -124,6 +128,10 @@ public class RecipeService {
|
|||||||
return this.recipeRepository.findAllByIsPublicIsTrue(pageable);
|
return this.recipeRepository.findAllByIsPublicIsTrue(pageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int countPublicRecipes() {
|
||||||
|
return this.recipeRepository.countByIsPublicIsTrue();
|
||||||
|
}
|
||||||
|
|
||||||
public List<Recipe> aiSearch(RecipeAiSearchBody searchSpec, @Nullable User viewer) {
|
public List<Recipe> aiSearch(RecipeAiSearchBody searchSpec, @Nullable User viewer) {
|
||||||
final float[] queryEmbedding = this.embeddingModel.embed(searchSpec.getPrompt());
|
final float[] queryEmbedding = this.embeddingModel.embed(searchSpec.getPrompt());
|
||||||
final List<Recipe> results;
|
final List<Recipe> results;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import app.mealsmadeeasy.api.recipe.star.RecipeStar;
|
|||||||
import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
|
import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
|
||||||
import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
|
import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
|
||||||
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
|
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
|
||||||
|
import app.mealsmadeeasy.api.sliceview.SliceView;
|
||||||
import app.mealsmadeeasy.api.sliceview.SliceViewService;
|
import app.mealsmadeeasy.api.sliceview.SliceViewService;
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@ -78,7 +79,7 @@ public class RecipesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Map<String, Object>> getRecipeInfoViews(
|
public ResponseEntity<SliceView<RecipeInfoView>> getRecipeInfoViews(
|
||||||
Pageable pageable,
|
Pageable pageable,
|
||||||
@AuthenticationPrincipal User user
|
@AuthenticationPrincipal User user
|
||||||
) {
|
) {
|
||||||
@ -87,13 +88,15 @@ public class RecipesController {
|
|||||||
final Slice<RecipeInfoView> publicRecipeInfoViews = publicRecipes.map(
|
final Slice<RecipeInfoView> publicRecipeInfoViews = publicRecipes.map(
|
||||||
recipe -> this.recipeToInfoViewConverter.convert(recipe, null)
|
recipe -> this.recipeToInfoViewConverter.convert(recipe, null)
|
||||||
);
|
);
|
||||||
return ResponseEntity.ok(this.sliceViewService.getSliceView(publicRecipeInfoViews));
|
final int count = this.recipeService.countPublicRecipes();
|
||||||
|
return ResponseEntity.ok(this.sliceViewService.getSliceView(publicRecipeInfoViews, count));
|
||||||
} else {
|
} else {
|
||||||
final Slice<Recipe> recipes = this.recipeService.getViewableBy(pageable, user);
|
final Slice<Recipe> recipes = this.recipeService.getViewableBy(pageable, user);
|
||||||
final Slice<RecipeInfoView> recipeInfoViews = recipes.map(
|
final Slice<RecipeInfoView> recipeInfoViews = recipes.map(
|
||||||
recipe -> this.recipeToInfoViewConverter.convert(recipe, user)
|
recipe -> this.recipeToInfoViewConverter.convert(recipe, user)
|
||||||
);
|
);
|
||||||
return ResponseEntity.ok(this.sliceViewService.getSliceView(recipeInfoViews));
|
final int count = this.recipeService.countViewableBy(user);
|
||||||
|
return ResponseEntity.ok(this.sliceViewService.getSliceView(recipeInfoViews, count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +154,7 @@ public class RecipesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{username}/{slug}/comments")
|
@GetMapping("/{username}/{slug}/comments")
|
||||||
public ResponseEntity<Map<String, Object>> getComments(
|
public ResponseEntity<SliceView<RecipeCommentView>> getComments(
|
||||||
@PathVariable String username,
|
@PathVariable String username,
|
||||||
@PathVariable String slug,
|
@PathVariable String slug,
|
||||||
Pageable pageable,
|
Pageable pageable,
|
||||||
@ -163,7 +166,8 @@ public class RecipesController {
|
|||||||
pageable,
|
pageable,
|
||||||
principal
|
principal
|
||||||
);
|
);
|
||||||
return ResponseEntity.ok(this.sliceViewService.getSliceView(slice));
|
final int count = this.recipeCommentService.countComments(username, slug);
|
||||||
|
return ResponseEntity.ok(this.sliceViewService.getSliceView(slice, count));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/{username}/{slug}/comments")
|
@PostMapping("/{username}/{slug}/comments")
|
||||||
|
|||||||
@ -4,8 +4,12 @@ import app.mealsmadeeasy.api.recipe.Recipe;
|
|||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.domain.Slice;
|
import org.springframework.data.domain.Slice;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
|
||||||
public interface RecipeCommentRepository extends JpaRepository<RecipeComment, Integer> {
|
public interface RecipeCommentRepository extends JpaRepository<RecipeComment, Integer> {
|
||||||
void deleteAllByRecipe(Recipe recipe);
|
void deleteAllByRecipe(Recipe recipe);
|
||||||
Slice<RecipeComment> findAllByRecipe(Recipe recipe, Pageable pageable);
|
Slice<RecipeComment> findAllByRecipe(Recipe recipe, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT count(rc) FROM RecipeComment rc WHERE rc.recipe.owner.username = ?1 AND rc.recipe.slug = ?2")
|
||||||
|
int countByUsernameAndSlug(String username, String slug);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,5 +9,6 @@ public interface RecipeCommentService {
|
|||||||
RecipeComment get(Integer commentId, User viewer) ;
|
RecipeComment get(Integer commentId, User viewer) ;
|
||||||
Slice<RecipeCommentView> getComments(String recipeUsername, String recipeSlug, Pageable pageable, User viewer);
|
Slice<RecipeCommentView> getComments(String recipeUsername, String recipeSlug, Pageable pageable, User viewer);
|
||||||
RecipeComment update(Integer commentId, User viewer, RecipeCommentUpdateSpec spec) ;
|
RecipeComment update(Integer commentId, User viewer, RecipeCommentUpdateSpec spec) ;
|
||||||
void delete(Integer commentId, User modifier) ;
|
void delete(Integer commentId, User modifier);
|
||||||
|
int countComments(String recipeUsername, String recipeSlug);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,4 +105,9 @@ public class RecipeCommentServiceImpl implements RecipeCommentService {
|
|||||||
this.recipeCommentRepository.delete(entityToDelete);
|
this.recipeCommentRepository.delete(entityToDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int countComments(String recipeUsername, String recipeSlug) {
|
||||||
|
return this.recipeCommentRepository.countByUsernameAndSlug(recipeUsername, recipeSlug);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
package app.mealsmadeeasy.api.sliceview;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record SliceView<T>(List<T> content, SliceViewMeta slice, Integer count) {
|
||||||
|
|
||||||
|
public record SliceViewMeta(Integer size, Integer number, Boolean hasNext) {}
|
||||||
|
|
||||||
|
}
|
||||||
@ -3,21 +3,16 @@ package app.mealsmadeeasy.api.sliceview;
|
|||||||
import org.springframework.data.domain.Slice;
|
import org.springframework.data.domain.Slice;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class SliceViewService {
|
public class SliceViewService {
|
||||||
|
|
||||||
public Map<String, Object> getSliceView(Slice<?> slice) {
|
public <T> SliceView<T> getSliceView(Slice<T> slice, int count) {
|
||||||
final Map<String, Object> view = new HashMap<>();
|
final SliceView.SliceViewMeta meta = new SliceView.SliceViewMeta(
|
||||||
view.put("content", slice.getContent());
|
slice.getSize(),
|
||||||
final Map<String, Object> sliceInfo = new HashMap<>();
|
slice.getNumber(),
|
||||||
sliceInfo.put("size", slice.getSize());
|
slice.hasNext()
|
||||||
sliceInfo.put("number", slice.getNumber());
|
);
|
||||||
sliceInfo.put("hasNext", slice.hasNext());
|
return new SliceView<>(slice.getContent(), meta, count);
|
||||||
view.put("slice", sliceInfo);
|
|
||||||
return view;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user