From 70c560f0cb59f42237d7274e67a2556f062335e7 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Thu, 15 Jan 2026 16:17:08 -0600 Subject: [PATCH] Clean up image and auth classes. --- .../api/image/ImageControllerTests.java | 50 +++--- .../api/image/S3ImageServiceTests.java | 52 +++--- .../api/recipe/RecipeControllerTests.java | 4 +- .../mealsmadeeasy/api/DevConfiguration.java | 11 +- .../api/auth/RefreshTokenRepository.java | 2 +- .../api/image/ImageController.java | 49 +++--- .../api/image/ImageRepository.java | 4 +- .../mealsmadeeasy/api/image/ImageService.java | 10 +- .../api/image/S3ImageService.java | 86 ++++++---- .../api/image/body/ImageUpdateBody.java | 16 ++ .../api/image/body/ImageUpdateInfoBody.java | 64 ------- .../api/image/spec/ImageCreateInfoSpec.java | 48 ------ .../api/image/spec/ImageCreateSpec.java | 17 ++ .../api/image/spec/ImageUpdateInfoSpec.java | 29 ---- .../api/image/spec/ImageUpdateSpec.java | 19 +++ .../api/image/view/ImageExceptionView.java | 6 - .../api/image/view/ImageView.java | 157 ++++-------------- 17 files changed, 231 insertions(+), 393 deletions(-) create mode 100644 src/main/java/app/mealsmadeeasy/api/image/body/ImageUpdateBody.java delete mode 100644 src/main/java/app/mealsmadeeasy/api/image/body/ImageUpdateInfoBody.java delete mode 100644 src/main/java/app/mealsmadeeasy/api/image/spec/ImageCreateInfoSpec.java create mode 100644 src/main/java/app/mealsmadeeasy/api/image/spec/ImageCreateSpec.java delete mode 100644 src/main/java/app/mealsmadeeasy/api/image/spec/ImageUpdateInfoSpec.java create mode 100644 src/main/java/app/mealsmadeeasy/api/image/spec/ImageUpdateSpec.java delete mode 100644 src/main/java/app/mealsmadeeasy/api/image/view/ImageExceptionView.java diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/image/ImageControllerTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/image/ImageControllerTests.java index bceef5b..1473168 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/image/ImageControllerTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/image/ImageControllerTests.java @@ -3,9 +3,9 @@ package app.mealsmadeeasy.api.image; import app.mealsmadeeasy.api.IntegrationTestsExtension; import app.mealsmadeeasy.api.auth.AuthService; import app.mealsmadeeasy.api.auth.LoginException; -import app.mealsmadeeasy.api.image.body.ImageUpdateInfoBody; -import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; -import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec; +import app.mealsmadeeasy.api.image.body.ImageUpdateBody; +import app.mealsmadeeasy.api.image.spec.ImageCreateSpec; +import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec; import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.UserCreateException; import app.mealsmadeeasy.api.user.UserService; @@ -89,7 +89,7 @@ public class ImageControllerTests { } } - private Image seedImage(User owner, ImageCreateInfoSpec spec) { + private Image seedImage(User owner, ImageCreateSpec spec) { try { return this.imageService.create( owner, @@ -104,7 +104,7 @@ public class ImageControllerTests { } private Image seedImage(User owner) { - return this.seedImage(owner, new ImageCreateInfoSpec()); + return this.seedImage(owner, ImageCreateSpec.builder().build()); } private static String getImageUrl(User owner, Image image) { @@ -122,8 +122,9 @@ public class ImageControllerTests { @Test public void getPublicImageNoPrincipal() throws Exception { final User owner = this.seedUser(); - final ImageCreateInfoSpec spec = new ImageCreateInfoSpec(); - spec.setPublic(true); + final ImageCreateSpec spec = ImageCreateSpec.builder() + .isPublic(true) + .build(); final Image image = this.seedImage(owner, spec); // Assert bytes the same and proper mime type @@ -161,8 +162,9 @@ public class ImageControllerTests { final Image image = this.seedImage(owner); // add viewer - final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); - spec.setViewersToAdd(Set.of(viewer)); + final ImageUpdateSpec spec = ImageUpdateSpec.builder() + .viewersToAdd(Set.of(viewer)) + .build(); this.imageService.update(image, owner, spec); this.doGetImageTestWithAccessToken(owner, image, this.getAccessToken(viewer)); @@ -195,8 +197,9 @@ public class ImageControllerTests { final User viewer = this.seedUser(); // add viewer - final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); - spec.setViewersToAdd(Set.of(viewer)); + final ImageUpdateSpec spec = ImageUpdateSpec.builder() + .viewersToAdd(Set.of(viewer)) + .build(); this.imageService.update(image, owner, spec); this.mockMvc.perform(get(getImageUrl(owner, image))).andExpect(status().isForbidden()); @@ -231,7 +234,7 @@ public class ImageControllerTests { .andExpect(jsonPath("$.mimeType").value("image/svg+xml")) .andExpect(jsonPath("$.alt").value("HAL 9000")) .andExpect(jsonPath("$.caption").value("HAL 9000, from 2001: A Space Odyssey")) - .andExpect(jsonPath("$.isPublic").value(true)) + .andExpect(jsonPath("$.public").value(true)) .andExpect(jsonPath("$.owner.username").value(owner.getUsername())) .andExpect(jsonPath("$.owner.id").value(owner.getId())) .andExpect(jsonPath("$.viewers").value(empty())); @@ -243,7 +246,7 @@ public class ImageControllerTests { final User owner = this.seedUser(); final Image image = this.seedImage(owner); final String accessToken = this.getAccessToken(owner); - final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); + final ImageUpdateBody body = new ImageUpdateBody(); body.setAlt("HAL 9000"); this.mockMvc.perform( post(getImageUrl(owner, image)) @@ -261,7 +264,7 @@ public class ImageControllerTests { final User owner = this.seedUser(); final Image image = this.seedImage(owner); final String accessToken = this.getAccessToken(owner); - final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); + final ImageUpdateBody body = new ImageUpdateBody(); body.setCaption("HAL 9000 from 2001: A Space Odyssey"); this.mockMvc.perform( post(getImageUrl(owner, image)) @@ -279,8 +282,8 @@ public class ImageControllerTests { final User owner = this.seedUser(); final Image image = this.seedImage(owner); final String accessToken = this.getAccessToken(owner); - final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); - body.setPublic(true); + final ImageUpdateBody body = new ImageUpdateBody(); + body.setIsPublic(true); this.mockMvc.perform( post(getImageUrl(owner, image)) .contentType(MediaType.APPLICATION_JSON) @@ -289,7 +292,7 @@ public class ImageControllerTests { ) .andExpect(status().isOk()) .andExpect(jsonPath("$.modified").value(notNullValue())) - .andExpect(jsonPath("$.isPublic").value(true)); + .andExpect(jsonPath("$.public").value(true)); } @Test @@ -299,7 +302,7 @@ public class ImageControllerTests { final Image image = this.seedImage(owner); final String accessToken = this.getAccessToken(owner); - final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); + final ImageUpdateBody body = new ImageUpdateBody(); final Set viewerUsernames = Set.of(viewerToAdd.getUsername()); body.setViewersToAdd(viewerUsernames); @@ -321,8 +324,9 @@ public class ImageControllerTests { final User owner = this.seedUser(); final User viewer = this.seedUser(); final Image image = this.seedImage(owner); - final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); - spec.setViewersToAdd(Set.of(viewer)); + final ImageUpdateSpec spec = ImageUpdateSpec.builder() + .viewersToAdd(Set.of(viewer)) + .build(); this.imageService.update(image, owner, spec); return new OwnerViewerImage(owner, viewer, image); } @@ -332,7 +336,7 @@ public class ImageControllerTests { final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage(); final String accessToken = this.getAccessToken(ownerViewerImage.owner()); - final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); + final ImageUpdateBody body = new ImageUpdateBody(); body.setViewersToRemove(Set.of(ownerViewerImage.viewer().getUsername())); this.mockMvc.perform( @@ -350,7 +354,7 @@ public class ImageControllerTests { public void clearAllViewers() throws Exception { final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage(); final String accessToken = this.getAccessToken(ownerViewerImage.owner()); - final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); + final ImageUpdateBody body = new ImageUpdateBody(); body.setClearAllViewers(true); this.mockMvc.perform( post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image())) @@ -367,7 +371,7 @@ public class ImageControllerTests { public void updateInfoByViewerForbidden() throws Exception { final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage(); final String accessToken = this.getAccessToken(ownerViewerImage.viewer()); // viewer - final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); + final ImageUpdateBody body = new ImageUpdateBody(); this.mockMvc.perform( post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image())) .contentType(MediaType.APPLICATION_JSON) diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java index ed34673..e52a8af 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java @@ -1,8 +1,8 @@ package app.mealsmadeeasy.api.image; import app.mealsmadeeasy.api.IntegrationTestsExtension; -import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; -import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec; +import app.mealsmadeeasy.api.image.spec.ImageCreateSpec; +import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec; import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.UserCreateException; import app.mealsmadeeasy.api.user.UserService; @@ -77,7 +77,7 @@ public class S3ImageServiceTests { return UUID.randomUUID() + ".svg"; } - private Image seedImage(User owner, ImageCreateInfoSpec spec) { + private Image seedImage(User owner, ImageCreateSpec spec) { try (final InputStream hal9000 = getHal9000InputStream()) { return this.imageService.create( owner, @@ -92,12 +92,13 @@ public class S3ImageServiceTests { } private Image seedImage(User owner) { - return this.seedImage(owner, new ImageCreateInfoSpec()); + return this.seedImage(owner, ImageCreateSpec.builder().build()); } private Image makePublic(Image image, User modifier) { - final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); - spec.setPublic(true); + final ImageUpdateSpec spec = ImageUpdateSpec.builder() + .isPublic(true) + .build(); return this.imageService.update(image, modifier, spec); } @@ -124,7 +125,6 @@ public class S3ImageServiceTests { final User owner = this.seedUser(); final Image image = this.seedImage(owner); final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(image, owner)); - //noinspection DataFlowIssue final byte[] contentBytes = content.readAllBytes(); assertThat(contentBytes.length, is((int) HAL_LENGTH)); content.close(); @@ -135,7 +135,6 @@ public class S3ImageServiceTests { final User owner = this.seedUser(); final Image image = this.seedImage(owner); final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(image, owner)); - //noinspection DataFlowIssue content.close(); } @@ -145,7 +144,6 @@ public class S3ImageServiceTests { final Image seedImage = this.seedImage(owner); final Image publicImage = this.makePublic(seedImage, owner); final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(publicImage, null)); - //noinspection DataFlowIssue content.close(); } @@ -154,11 +152,11 @@ public class S3ImageServiceTests { final User owner = this.seedUser(); final User viewer = this.seedUser(); Image seedImage = this.seedImage(owner); - final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); - spec.setViewersToAdd(Set.of(viewer)); + final ImageUpdateSpec spec = ImageUpdateSpec.builder() + .viewersToAdd(Set.of(viewer)) + .build(); final Image imageWithViewer = this.imageService.update(seedImage, owner, spec); final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(imageWithViewer, viewer)); - //noinspection DataFlowIssue content.close(); } @@ -183,8 +181,9 @@ public class S3ImageServiceTests { public void updateAlt() { final User owner = this.seedUser(); Image image = this.seedImage(owner); - final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); - spec.setAlt("HAL 9000"); + final ImageUpdateSpec spec = ImageUpdateSpec.builder() + .alt("HAL 9000") + .build(); image = this.imageService.update(image, owner, spec); assertThat(image.getAlt(), is("HAL 9000")); } @@ -193,8 +192,9 @@ public class S3ImageServiceTests { public void updateCaption() { final User owner = this.seedUser(); Image image = this.seedImage(owner); - final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); - spec.setCaption("HAL 9000 from 2001: A Space Odyssey"); + final ImageUpdateSpec spec = ImageUpdateSpec.builder() + .caption("HAL 9000 from 2001: A Space Odyssey") + .build(); image = this.imageService.update(image, owner, spec); assertThat(image.getCaption(), is("HAL 9000 from 2001: A Space Odyssey")); } @@ -203,15 +203,17 @@ public class S3ImageServiceTests { public void updateIsPublic() { final User owner = this.seedUser(); Image image = this.seedImage(owner); - final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); - spec.setPublic(true); + final ImageUpdateSpec spec = ImageUpdateSpec.builder() + .isPublic(true) + .build(); image = this.imageService.update(image, owner, spec); assertThat(image.getIsPublic(), is(true)); } private Image addViewer(Image image, User owner, User viewer) { - final ImageUpdateInfoSpec spec0 = new ImageUpdateInfoSpec(); - spec0.setViewersToAdd(Set.of(viewer)); + final ImageUpdateSpec spec0 = ImageUpdateSpec.builder() + .viewersToAdd(Set.of(viewer)) + .build(); return this.imageService.update(image, owner, spec0); } @@ -232,8 +234,9 @@ public class S3ImageServiceTests { image = this.addViewer(image, owner, viewer); assertThat(image.getViewers(), containsUsers(viewer)); - final ImageUpdateInfoSpec spec1 = new ImageUpdateInfoSpec(); - spec1.setViewersToRemove(Set.of(viewer)); + final ImageUpdateSpec spec1 = ImageUpdateSpec.builder() + .viewersToRemove(Set.of(viewer)) + .build(); image = this.imageService.update(image, owner, spec1); assertThat(image.getViewers(), empty()); } @@ -246,8 +249,9 @@ public class S3ImageServiceTests { image = this.addViewer(image, owner, viewer); assertThat(image.getViewers(), containsUsers(viewer)); - final ImageUpdateInfoSpec spec1 = new ImageUpdateInfoSpec(); - spec1.setClearAllViewers(true); + final ImageUpdateSpec spec1 = ImageUpdateSpec.builder() + .clearAllViewers(true) + .build(); image = this.imageService.update(image, owner, spec1); assertThat(image.getViewers(), empty()); } diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java index f712519..958727d 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java @@ -7,7 +7,7 @@ import app.mealsmadeeasy.api.auth.LoginException; import app.mealsmadeeasy.api.image.Image; import app.mealsmadeeasy.api.image.ImageService; import app.mealsmadeeasy.api.image.S3ImageServiceTests; -import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; +import app.mealsmadeeasy.api.image.spec.ImageCreateSpec; import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; import app.mealsmadeeasy.api.recipe.star.RecipeStarService; @@ -117,7 +117,7 @@ public class RecipeControllerTests { UUID.randomUUID() + ".svg", hal9000, 27881L, - new ImageCreateInfoSpec() + ImageCreateSpec.builder().build() ); } catch (Exception e) { throw new RuntimeException(e); diff --git a/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java b/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java index 43d39d9..5216d6a 100644 --- a/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java +++ b/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java @@ -2,7 +2,7 @@ package app.mealsmadeeasy.api; import app.mealsmadeeasy.api.image.Image; import app.mealsmadeeasy.api.image.ImageService; -import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; +import app.mealsmadeeasy.api.image.spec.ImageCreateSpec; import app.mealsmadeeasy.api.recipe.Recipe; import app.mealsmadeeasy.api.recipe.RecipeService; import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; @@ -82,10 +82,11 @@ public class DevConfiguration { rawFrontMatter, RecipeFrontMatter.class ); - final ImageCreateInfoSpec imageCreateSpec = new ImageCreateInfoSpec(); - imageCreateSpec.setAlt(frontMatter.mainImage.alt); - imageCreateSpec.setCaption(frontMatter.mainImage.caption); - imageCreateSpec.setPublic(frontMatter.mainImage.isPublic); + final ImageCreateSpec imageCreateSpec = ImageCreateSpec.builder() + .alt(frontMatter.mainImage.alt) + .caption(frontMatter.mainImage.caption) + .isPublic(frontMatter.mainImage.isPublic) + .build(); final Path givenPath = Path.of(frontMatter.mainImage.src); final Path resolvedPath = Path.of("dev-data", "images").resolve(givenPath); final Image mainImage; diff --git a/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenRepository.java b/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenRepository.java index 45108a9..642a413 100644 --- a/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenRepository.java @@ -8,7 +8,7 @@ import org.springframework.data.jpa.repository.Query; import java.util.Optional; import java.util.UUID; -public interface RefreshTokenRepository extends JpaRepository { +public interface RefreshTokenRepository extends JpaRepository { Optional findByToken(UUID token); diff --git a/src/main/java/app/mealsmadeeasy/api/image/ImageController.java b/src/main/java/app/mealsmadeeasy/api/image/ImageController.java index 54f78ad..88e5703 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/ImageController.java +++ b/src/main/java/app/mealsmadeeasy/api/image/ImageController.java @@ -1,8 +1,8 @@ package app.mealsmadeeasy.api.image; -import app.mealsmadeeasy.api.image.body.ImageUpdateInfoBody; -import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; -import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec; +import app.mealsmadeeasy.api.image.body.ImageUpdateBody; +import app.mealsmadeeasy.api.image.spec.ImageCreateSpec; +import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec; import app.mealsmadeeasy.api.image.view.ImageView; import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.UserService; @@ -34,27 +34,25 @@ public class ImageController { this.userService = userService; } - private ImageUpdateInfoSpec getImageUpdateSpec(ImageUpdateInfoBody body) { - final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); - spec.setAlt(body.getAlt()); - spec.setCaption(body.getCaption()); - spec.setPublic(body.getPublic()); + private ImageUpdateSpec getImageUpdateSpec(ImageUpdateBody body) { + final var builder = ImageUpdateSpec.builder() + .alt(body.getAlt()) + .caption(body.getCaption()) + .isPublic(body.getIsPublic()) + .clearAllViewers(body.getClearAllViewers()); if (body.getViewersToAdd() != null) { - spec.setViewersToAdd( - body.getViewersToAdd().stream() - .map(this.userService::getUser) - .collect(Collectors.toSet()) + builder.viewersToAdd(body.getViewersToAdd().stream() + .map(this.userService::getUser) + .collect(Collectors.toSet()) ); } if (body.getViewersToRemove() != null) { - spec.setViewersToRemove( - body.getViewersToRemove().stream() - .map(this.userService::getUser) - .collect(Collectors.toSet()) + builder.viewersToRemove(body.getViewersToRemove().stream() + .map(this.userService::getUser) + .collect(Collectors.toSet()) ); } - spec.setClearAllViewers(body.getClearAllViewers()); - return spec; + return builder.build(); } @ExceptionHandler @@ -97,19 +95,20 @@ public class ImageController { if (principal == null) { throw new AccessDeniedException("Must be logged in."); } - final ImageCreateInfoSpec createSpec = new ImageCreateInfoSpec(); - createSpec.setAlt(alt); - createSpec.setCaption(caption); - createSpec.setPublic(isPublic); + final var specBuilder = ImageCreateSpec.builder() + .alt(alt) + .caption(caption) + .isPublic(isPublic); + if (viewers != null) { - createSpec.setViewersToAdd(viewers.stream().map(this.userService::getUser).collect(Collectors.toSet())); + specBuilder.viewersToAdd(viewers.stream().map(this.userService::getUser).collect(Collectors.toSet())); } final Image saved = this.imageService.create( principal, filename, image.getInputStream(), image.getSize(), - createSpec + specBuilder.build() ); return ResponseEntity.status(201).body(this.imageService.toImageView(saved, principal)); } @@ -119,7 +118,7 @@ public class ImageController { @AuthenticationPrincipal User principal, @PathVariable String username, @PathVariable String filename, - @RequestBody ImageUpdateInfoBody body + @RequestBody ImageUpdateBody body ) throws ImageException { if (principal == null) { throw new AccessDeniedException("Must be logged in."); diff --git a/src/main/java/app/mealsmadeeasy/api/image/ImageRepository.java b/src/main/java/app/mealsmadeeasy/api/image/ImageRepository.java index 2009468..9c317b1 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/ImageRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/image/ImageRepository.java @@ -8,11 +8,11 @@ import org.springframework.data.jpa.repository.Query; import java.util.List; import java.util.Optional; -public interface ImageRepository extends JpaRepository { +public interface ImageRepository extends JpaRepository { @Query("SELECT image FROM Image image WHERE image.id = ?1") @EntityGraph(attributePaths = { "viewers" }) - Image getByIdWithViewers(long id); + Image getByIdWithViewers(Integer id); List findAllByOwner(User owner); Optional findByOwnerAndUserFilename(User owner, String filename); diff --git a/src/main/java/app/mealsmadeeasy/api/image/ImageService.java b/src/main/java/app/mealsmadeeasy/api/image/ImageService.java index a69d422..55553b2 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/ImageService.java +++ b/src/main/java/app/mealsmadeeasy/api/image/ImageService.java @@ -1,7 +1,7 @@ package app.mealsmadeeasy.api.image; -import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; -import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec; +import app.mealsmadeeasy.api.image.spec.ImageCreateSpec; +import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec; import app.mealsmadeeasy.api.image.view.ImageView; import app.mealsmadeeasy.api.user.User; import org.jetbrains.annotations.Nullable; @@ -12,17 +12,17 @@ import java.util.List; public interface ImageService { - Image create(User owner, String userFilename, InputStream inputStream, long objectSize, ImageCreateInfoSpec infoSpec) + Image create(User owner, String userFilename, InputStream inputStream, long objectSize, ImageCreateSpec infoSpec) throws IOException, ImageException; - Image getById(long id, @Nullable User viewer) throws ImageException; + Image getById(Integer id, @Nullable User viewer) throws ImageException; Image getByOwnerAndFilename(User owner, String filename, User viewer) throws ImageException; Image getByUsernameAndFilename(String username, String filename, User viewer) throws ImageException; InputStream getImageContent(Image image, @Nullable User viewer) throws IOException; List getImagesOwnedBy(User user); - Image update(Image image, User modifier, ImageUpdateInfoSpec spec); + Image update(Image image, User modifier, ImageUpdateSpec spec); void deleteImage(Image image, User modifier) throws IOException; diff --git a/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java b/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java index b23d91b..60cc6c9 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java +++ b/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java @@ -1,7 +1,7 @@ package app.mealsmadeeasy.api.image; -import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; -import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec; +import app.mealsmadeeasy.api.image.spec.ImageCreateSpec; +import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec; import app.mealsmadeeasy.api.image.view.ImageView; import app.mealsmadeeasy.api.s3.S3Manager; import app.mealsmadeeasy.api.user.User; @@ -78,7 +78,7 @@ public class S3ImageService implements ImageService { }; } - private boolean transferFromSpec(Image entity, ImageCreateInfoSpec spec) { + private boolean transferFromCreateSpec(Image entity, ImageCreateSpec spec) { boolean didTransfer = false; if (spec.getAlt() != null) { entity.setAlt(spec.getAlt()); @@ -88,22 +88,55 @@ public class S3ImageService implements ImageService { entity.setCaption(spec.getCaption()); didTransfer = true; } - if (spec.getPublic() != null) { - entity.setIsPublic(spec.getPublic()); + if (spec.getIsPublic() != null) { + entity.setIsPublic(spec.getIsPublic()); didTransfer = true; } final @Nullable Set viewersToAdd = spec.getViewersToAdd(); if (viewersToAdd != null) { final Set viewers = new HashSet<>(entity.getViewers()); - for (final User viewerToAdd : spec.getViewersToAdd()) { - viewers.add((User) viewerToAdd); - } + viewers.addAll(spec.getViewersToAdd()); entity.setViewers(viewers); didTransfer = true; } return didTransfer; } + private boolean transferFromUpdateSpec(Image entity, ImageUpdateSpec spec) { + boolean didTransfer = false; + if (spec.getAlt() != null) { + entity.setAlt(spec.getAlt()); + didTransfer = true; + } + if (spec.getCaption() != null) { + entity.setCaption(spec.getCaption()); + didTransfer = true; + } + if (spec.getIsPublic() != null) { + entity.setIsPublic(spec.getIsPublic()); + didTransfer = true; + } + final @Nullable Set viewersToAdd = spec.getViewersToAdd(); + if (viewersToAdd != null) { + final Set viewers = new HashSet<>(entity.getViewers()); + viewers.addAll(spec.getViewersToAdd()); + entity.setViewers(viewers); + didTransfer = true; + } + final @Nullable Set viewersToRemove = spec.getViewersToRemove(); + if (viewersToRemove != null) { + final Set viewers = new HashSet<>(entity.getViewers()); + viewers.removeAll(spec.getViewersToRemove()); + entity.setViewers(viewers); + didTransfer = true; + } + if (spec.getClearAllViewers() != null && spec.getClearAllViewers()) { + entity.setViewers(new HashSet<>()); + didTransfer = true; + } + return didTransfer; + } + /** * @apiNote Consumes and closes the {@link java.io.InputStream}. * @@ -122,7 +155,7 @@ public class S3ImageService implements ImageService { String userFilename, InputStream inputStream, long objectSize, - ImageCreateInfoSpec createSpec + ImageCreateSpec createSpec ) throws IOException, ImageException { final String mimeType = this.getMimeType(userFilename); final String uuid = UUID.randomUUID().toString(); @@ -153,19 +186,19 @@ public class S3ImageService implements ImageService { inputStream.close(); final Image draft = new Image(); - draft.setOwner((User) owner); + draft.setOwner(owner); draft.setUserFilename(userFilename); draft.setMimeType(mimeType); draft.setObjectName(objectName); draft.setHeight(height); draft.setWidth(width); - this.transferFromSpec(draft, createSpec); + this.transferFromCreateSpec(draft, createSpec); return this.imageRepository.save(draft); } @Override @PostAuthorize("@imageSecurity.isViewableBy(returnObject, #viewer)") - public Image getById(long id, @Nullable User viewer) throws ImageException { + public Image getById(Integer id, @Nullable User viewer) throws ImageException { return this.imageRepository.findById(id).orElseThrow(() -> new ImageException( ImageException.Type.INVALID_ID, "No Image with id: " + id )); @@ -205,36 +238,19 @@ public class S3ImageService implements ImageService { @Override @PreAuthorize("@imageSecurity.isOwner(#image, #modifier)") - public Image update(final Image image, User modifier, ImageUpdateInfoSpec updateSpec) { - Image entity = (Image) image; - boolean didUpdate = this.transferFromSpec(entity, updateSpec); - final @Nullable Boolean clearAllViewers = updateSpec.getClearAllViewers(); - if (clearAllViewers != null && clearAllViewers) { - entity.setViewers(new HashSet<>()); - didUpdate = true; - } else { - final @Nullable Set viewersToRemove = updateSpec.getViewersToRemove(); - if (viewersToRemove != null) { - final Set currentViewers = new HashSet<>(entity.getViewers()); - for (final User toRemove : updateSpec.getViewersToRemove()) { - currentViewers.remove((User) toRemove); - } - entity.setViewers(currentViewers); - didUpdate = true; - } - } + public Image update(final Image image, User modifier, ImageUpdateSpec updateSpec) { + final boolean didUpdate = this.transferFromUpdateSpec(image, updateSpec); if (didUpdate) { - entity.setModified(OffsetDateTime.now()); + image.setModified(OffsetDateTime.now()); } - return this.imageRepository.save(entity); + return this.imageRepository.save(image); } @Override @PreAuthorize("@imageSecurity.isOwner(#image, #modifier)") public void deleteImage(Image image, User modifier) throws IOException { - final Image imageEntity = (Image) image; - this.imageRepository.delete(imageEntity); - this.s3Manager.delete("images", imageEntity.getObjectName()); + this.imageRepository.delete(image); + this.s3Manager.delete("images", image.getObjectName()); } private String getImageUrl(Image image) { diff --git a/src/main/java/app/mealsmadeeasy/api/image/body/ImageUpdateBody.java b/src/main/java/app/mealsmadeeasy/api/image/body/ImageUpdateBody.java new file mode 100644 index 0000000..9a4cdff --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/image/body/ImageUpdateBody.java @@ -0,0 +1,16 @@ +package app.mealsmadeeasy.api.image.body; + +import lombok.Data; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +@Data +public class ImageUpdateBody { + private @Nullable String alt; + private @Nullable String caption; + private @Nullable Boolean isPublic; + private @Nullable Set viewersToAdd; + private @Nullable Set viewersToRemove; + private @Nullable Boolean clearAllViewers; +} diff --git a/src/main/java/app/mealsmadeeasy/api/image/body/ImageUpdateInfoBody.java b/src/main/java/app/mealsmadeeasy/api/image/body/ImageUpdateInfoBody.java deleted file mode 100644 index 1eea9e4..0000000 --- a/src/main/java/app/mealsmadeeasy/api/image/body/ImageUpdateInfoBody.java +++ /dev/null @@ -1,64 +0,0 @@ -package app.mealsmadeeasy.api.image.body; - -import org.jetbrains.annotations.Nullable; - -import java.util.Set; - -public class ImageUpdateInfoBody { - - private @Nullable String alt; - private @Nullable String caption; - private @Nullable Boolean isPublic; - private @Nullable Set viewersToAdd; - private @Nullable Set viewersToRemove; - private @Nullable Boolean clearAllViewers; - - public @Nullable String getAlt() { - return this.alt; - } - - public void setAlt(@Nullable String alt) { - this.alt = alt; - } - - public @Nullable String getCaption() { - return this.caption; - } - - public void setCaption(@Nullable String caption) { - this.caption = caption; - } - - public @Nullable Boolean getPublic() { - return this.isPublic; - } - - public void setPublic(@Nullable Boolean aPublic) { - isPublic = aPublic; - } - - public @Nullable Set getViewersToAdd() { - return this.viewersToAdd; - } - - public void setViewersToAdd(@Nullable Set viewersToAdd) { - this.viewersToAdd = viewersToAdd; - } - - public @Nullable Set getViewersToRemove() { - return this.viewersToRemove; - } - - public void setViewersToRemove(@Nullable Set viewersToRemove) { - this.viewersToRemove = viewersToRemove; - } - - public @Nullable Boolean getClearAllViewers() { - return this.clearAllViewers; - } - - public void setClearAllViewers(@Nullable Boolean clearAllViewers) { - this.clearAllViewers = clearAllViewers; - } - -} diff --git a/src/main/java/app/mealsmadeeasy/api/image/spec/ImageCreateInfoSpec.java b/src/main/java/app/mealsmadeeasy/api/image/spec/ImageCreateInfoSpec.java deleted file mode 100644 index dccd197..0000000 --- a/src/main/java/app/mealsmadeeasy/api/image/spec/ImageCreateInfoSpec.java +++ /dev/null @@ -1,48 +0,0 @@ -package app.mealsmadeeasy.api.image.spec; - -import app.mealsmadeeasy.api.user.User; -import org.jetbrains.annotations.Nullable; - -import java.util.HashSet; -import java.util.Set; - -public class ImageCreateInfoSpec { - - private @Nullable String alt; - private @Nullable String caption; - private @Nullable Boolean isPublic; - private @Nullable Set viewersToAdd = new HashSet<>(); - - public @Nullable String getAlt() { - return this.alt; - } - - public void setAlt(@Nullable String alt) { - this.alt = alt; - } - - public @Nullable String getCaption() { - return this.caption; - } - - public void setCaption(@Nullable String caption) { - this.caption = caption; - } - - public @Nullable Boolean getPublic() { - return this.isPublic; - } - - public void setPublic(@Nullable Boolean aPublic) { - isPublic = aPublic; - } - - public @Nullable Set getViewersToAdd() { - return this.viewersToAdd; - } - - public void setViewersToAdd(@Nullable Set viewersToAdd) { - this.viewersToAdd = viewersToAdd; - } - -} diff --git a/src/main/java/app/mealsmadeeasy/api/image/spec/ImageCreateSpec.java b/src/main/java/app/mealsmadeeasy/api/image/spec/ImageCreateSpec.java new file mode 100644 index 0000000..ef351db --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/image/spec/ImageCreateSpec.java @@ -0,0 +1,17 @@ +package app.mealsmadeeasy.api.image.spec; + +import app.mealsmadeeasy.api.user.User; +import lombok.Builder; +import lombok.Value; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +@Value +@Builder +public class ImageCreateSpec { + @Nullable String alt; + @Nullable String caption; + @Nullable Boolean isPublic; + @Nullable Set viewersToAdd; +} diff --git a/src/main/java/app/mealsmadeeasy/api/image/spec/ImageUpdateInfoSpec.java b/src/main/java/app/mealsmadeeasy/api/image/spec/ImageUpdateInfoSpec.java deleted file mode 100644 index c6c45d8..0000000 --- a/src/main/java/app/mealsmadeeasy/api/image/spec/ImageUpdateInfoSpec.java +++ /dev/null @@ -1,29 +0,0 @@ -package app.mealsmadeeasy.api.image.spec; - -import app.mealsmadeeasy.api.user.User; -import org.jetbrains.annotations.Nullable; - -import java.util.Set; - -public class ImageUpdateInfoSpec extends ImageCreateInfoSpec { - - private @Nullable Set viewersToRemove; - private @Nullable Boolean clearAllViewers; - - public @Nullable Set getViewersToRemove() { - return this.viewersToRemove; - } - - public void setViewersToRemove(@Nullable Set viewersToRemove) { - this.viewersToRemove = viewersToRemove; - } - - public @Nullable Boolean getClearAllViewers() { - return this.clearAllViewers; - } - - public void setClearAllViewers(@Nullable Boolean clearAllViewers) { - this.clearAllViewers = clearAllViewers; - } - -} diff --git a/src/main/java/app/mealsmadeeasy/api/image/spec/ImageUpdateSpec.java b/src/main/java/app/mealsmadeeasy/api/image/spec/ImageUpdateSpec.java new file mode 100644 index 0000000..e032c81 --- /dev/null +++ b/src/main/java/app/mealsmadeeasy/api/image/spec/ImageUpdateSpec.java @@ -0,0 +1,19 @@ +package app.mealsmadeeasy.api.image.spec; + +import app.mealsmadeeasy.api.user.User; +import lombok.Builder; +import lombok.Value; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +@Value +@Builder +public class ImageUpdateSpec { + @Nullable String alt; + @Nullable String caption; + @Nullable Boolean isPublic; + @Nullable Set viewersToAdd; + @Nullable Set viewersToRemove; + @Nullable Boolean clearAllViewers; +} diff --git a/src/main/java/app/mealsmadeeasy/api/image/view/ImageExceptionView.java b/src/main/java/app/mealsmadeeasy/api/image/view/ImageExceptionView.java deleted file mode 100644 index c252964..0000000 --- a/src/main/java/app/mealsmadeeasy/api/image/view/ImageExceptionView.java +++ /dev/null @@ -1,6 +0,0 @@ -package app.mealsmadeeasy.api.image.view; - -public class ImageExceptionView { - - -} diff --git a/src/main/java/app/mealsmadeeasy/api/image/view/ImageView.java b/src/main/java/app/mealsmadeeasy/api/image/view/ImageView.java index 5d00ce4..ae50c84 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/view/ImageView.java +++ b/src/main/java/app/mealsmadeeasy/api/image/view/ImageView.java @@ -2,143 +2,52 @@ package app.mealsmadeeasy.api.image.view; import app.mealsmadeeasy.api.image.Image; import app.mealsmadeeasy.api.user.view.UserInfoView; +import lombok.Builder; +import lombok.Value; import org.jetbrains.annotations.Nullable; import java.time.OffsetDateTime; import java.util.Set; import java.util.stream.Collectors; +@Value +@Builder public class ImageView { public static ImageView from(Image image, String url, boolean includeViewers) { - final ImageView view = new ImageView(); - view.setUrl(url); - view.setCreated(image.getCreated()); - view.setModified(image.getModified()); - view.setFilename(image.getUserFilename()); - view.setMimeType(image.getMimeType()); - view.setAlt(image.getAlt()); - view.setCaption(image.getCaption()); - view.setOwner(UserInfoView.from(image.getOwner())); - view.setIsPublic(image.getIsPublic()); - view.setHeight(image.getHeight()); - view.setWidth(image.getWidth()); + final var builder = ImageView.builder() + .url(url) + .created(image.getCreated()) + .modified(image.getModified()) + .filename(image.getUserFilename()) + .mimeType(image.getMimeType()) + .alt(image.getAlt()) + .caption(image.getCaption()) + .owner(UserInfoView.from(image.getOwner())) + .isPublic(image.getIsPublic()) + .height(image.getHeight()) + .width(image.getWidth()); if (includeViewers) { - view.setViewers(image.getViewers().stream() - .map(UserInfoView::from) - .collect(Collectors.toSet()) + builder.viewers( + image.getViewers().stream() + .map(UserInfoView::from) + .collect(Collectors.toSet()) ); } - return view; + return builder.build(); } - private String url; - private OffsetDateTime created; - private @Nullable OffsetDateTime modified; - private String filename; - private String mimeType; - private @Nullable String alt; - private @Nullable String caption; - private UserInfoView owner; - private boolean isPublic; - private @Nullable Integer height; - private @Nullable Integer width; - private @Nullable Set viewers; - - public String getUrl() { - return this.url; - } - - public void setUrl(String url) { - this.url = url; - } - - public OffsetDateTime getCreated() { - return this.created; - } - - public void setCreated(OffsetDateTime created) { - this.created = created; - } - - public @Nullable OffsetDateTime getModified() { - return this.modified; - } - - public void setModified(@Nullable OffsetDateTime modified) { - this.modified = modified; - } - - public String getFilename() { - return this.filename; - } - - public void setFilename(String filename) { - this.filename = filename; - } - - public String getMimeType() { - return this.mimeType; - } - - public void setMimeType(String mimeType) { - this.mimeType = mimeType; - } - - public @Nullable String getAlt() { - return this.alt; - } - - public void setAlt(@Nullable String alt) { - this.alt = alt; - } - - public @Nullable String getCaption() { - return this.caption; - } - - public void setCaption(@Nullable String caption) { - this.caption = caption; - } - - public UserInfoView getOwner() { - return this.owner; - } - - public void setOwner(UserInfoView owner) { - this.owner = owner; - } - - public boolean getIsPublic() { - return this.isPublic; - } - - public void setIsPublic(boolean isPublic) { - this.isPublic = isPublic; - } - - public @Nullable Integer getHeight() { - return this.height; - } - - public void setHeight(Integer height) { - this.height = height; - } - - public @Nullable Integer getWidth() { - return this.width; - } - - public void setWidth(Integer width) { - this.width = width; - } - - public @Nullable Set getViewers() { - return this.viewers; - } - - public void setViewers(@Nullable Set viewers) { - this.viewers = viewers; - } + String url; + OffsetDateTime created; + @Nullable OffsetDateTime modified; + String filename; + String mimeType; + @Nullable String alt; + @Nullable String caption; + UserInfoView owner; + boolean isPublic; + @Nullable Integer height; + @Nullable Integer width; + @Nullable Set viewers; }