Clean up image and auth classes.

This commit is contained in:
Jesse Brault 2026-01-15 16:17:08 -06:00
parent 14c911c283
commit 70c560f0cb
17 changed files with 231 additions and 393 deletions

View File

@ -3,9 +3,9 @@ package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.IntegrationTestsExtension; import app.mealsmadeeasy.api.IntegrationTestsExtension;
import app.mealsmadeeasy.api.auth.AuthService; import app.mealsmadeeasy.api.auth.AuthService;
import app.mealsmadeeasy.api.auth.LoginException; import app.mealsmadeeasy.api.auth.LoginException;
import app.mealsmadeeasy.api.image.body.ImageUpdateInfoBody; import app.mealsmadeeasy.api.image.body.ImageUpdateBody;
import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; import app.mealsmadeeasy.api.image.spec.ImageCreateSpec;
import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec; import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec;
import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserCreateException; import app.mealsmadeeasy.api.user.UserCreateException;
import app.mealsmadeeasy.api.user.UserService; 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 { try {
return this.imageService.create( return this.imageService.create(
owner, owner,
@ -104,7 +104,7 @@ public class ImageControllerTests {
} }
private Image seedImage(User owner) { 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) { private static String getImageUrl(User owner, Image image) {
@ -122,8 +122,9 @@ public class ImageControllerTests {
@Test @Test
public void getPublicImageNoPrincipal() throws Exception { public void getPublicImageNoPrincipal() throws Exception {
final User owner = this.seedUser(); final User owner = this.seedUser();
final ImageCreateInfoSpec spec = new ImageCreateInfoSpec(); final ImageCreateSpec spec = ImageCreateSpec.builder()
spec.setPublic(true); .isPublic(true)
.build();
final Image image = this.seedImage(owner, spec); final Image image = this.seedImage(owner, spec);
// Assert bytes the same and proper mime type // Assert bytes the same and proper mime type
@ -161,8 +162,9 @@ public class ImageControllerTests {
final Image image = this.seedImage(owner); final Image image = this.seedImage(owner);
// add viewer // add viewer
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec = ImageUpdateSpec.builder()
spec.setViewersToAdd(Set.of(viewer)); .viewersToAdd(Set.of(viewer))
.build();
this.imageService.update(image, owner, spec); this.imageService.update(image, owner, spec);
this.doGetImageTestWithAccessToken(owner, image, this.getAccessToken(viewer)); this.doGetImageTestWithAccessToken(owner, image, this.getAccessToken(viewer));
@ -195,8 +197,9 @@ public class ImageControllerTests {
final User viewer = this.seedUser(); final User viewer = this.seedUser();
// add viewer // add viewer
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec = ImageUpdateSpec.builder()
spec.setViewersToAdd(Set.of(viewer)); .viewersToAdd(Set.of(viewer))
.build();
this.imageService.update(image, owner, spec); this.imageService.update(image, owner, spec);
this.mockMvc.perform(get(getImageUrl(owner, image))).andExpect(status().isForbidden()); 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("$.mimeType").value("image/svg+xml"))
.andExpect(jsonPath("$.alt").value("HAL 9000")) .andExpect(jsonPath("$.alt").value("HAL 9000"))
.andExpect(jsonPath("$.caption").value("HAL 9000, from 2001: A Space Odyssey")) .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.username").value(owner.getUsername()))
.andExpect(jsonPath("$.owner.id").value(owner.getId())) .andExpect(jsonPath("$.owner.id").value(owner.getId()))
.andExpect(jsonPath("$.viewers").value(empty())); .andExpect(jsonPath("$.viewers").value(empty()));
@ -243,7 +246,7 @@ public class ImageControllerTests {
final User owner = this.seedUser(); final User owner = this.seedUser();
final Image image = this.seedImage(owner); final Image image = this.seedImage(owner);
final String accessToken = this.getAccessToken(owner); final String accessToken = this.getAccessToken(owner);
final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); final ImageUpdateBody body = new ImageUpdateBody();
body.setAlt("HAL 9000"); body.setAlt("HAL 9000");
this.mockMvc.perform( this.mockMvc.perform(
post(getImageUrl(owner, image)) post(getImageUrl(owner, image))
@ -261,7 +264,7 @@ public class ImageControllerTests {
final User owner = this.seedUser(); final User owner = this.seedUser();
final Image image = this.seedImage(owner); final Image image = this.seedImage(owner);
final String accessToken = this.getAccessToken(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"); body.setCaption("HAL 9000 from 2001: A Space Odyssey");
this.mockMvc.perform( this.mockMvc.perform(
post(getImageUrl(owner, image)) post(getImageUrl(owner, image))
@ -279,8 +282,8 @@ public class ImageControllerTests {
final User owner = this.seedUser(); final User owner = this.seedUser();
final Image image = this.seedImage(owner); final Image image = this.seedImage(owner);
final String accessToken = this.getAccessToken(owner); final String accessToken = this.getAccessToken(owner);
final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); final ImageUpdateBody body = new ImageUpdateBody();
body.setPublic(true); body.setIsPublic(true);
this.mockMvc.perform( this.mockMvc.perform(
post(getImageUrl(owner, image)) post(getImageUrl(owner, image))
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
@ -289,7 +292,7 @@ public class ImageControllerTests {
) )
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.modified").value(notNullValue())) .andExpect(jsonPath("$.modified").value(notNullValue()))
.andExpect(jsonPath("$.isPublic").value(true)); .andExpect(jsonPath("$.public").value(true));
} }
@Test @Test
@ -299,7 +302,7 @@ public class ImageControllerTests {
final Image image = this.seedImage(owner); final Image image = this.seedImage(owner);
final String accessToken = this.getAccessToken(owner); final String accessToken = this.getAccessToken(owner);
final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); final ImageUpdateBody body = new ImageUpdateBody();
final Set<String> viewerUsernames = Set.of(viewerToAdd.getUsername()); final Set<String> viewerUsernames = Set.of(viewerToAdd.getUsername());
body.setViewersToAdd(viewerUsernames); body.setViewersToAdd(viewerUsernames);
@ -321,8 +324,9 @@ public class ImageControllerTests {
final User owner = this.seedUser(); final User owner = this.seedUser();
final User viewer = this.seedUser(); final User viewer = this.seedUser();
final Image image = this.seedImage(owner); final Image image = this.seedImage(owner);
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec = ImageUpdateSpec.builder()
spec.setViewersToAdd(Set.of(viewer)); .viewersToAdd(Set.of(viewer))
.build();
this.imageService.update(image, owner, spec); this.imageService.update(image, owner, spec);
return new OwnerViewerImage(owner, viewer, image); return new OwnerViewerImage(owner, viewer, image);
} }
@ -332,7 +336,7 @@ public class ImageControllerTests {
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage(); final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
final String accessToken = this.getAccessToken(ownerViewerImage.owner()); final String accessToken = this.getAccessToken(ownerViewerImage.owner());
final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); final ImageUpdateBody body = new ImageUpdateBody();
body.setViewersToRemove(Set.of(ownerViewerImage.viewer().getUsername())); body.setViewersToRemove(Set.of(ownerViewerImage.viewer().getUsername()));
this.mockMvc.perform( this.mockMvc.perform(
@ -350,7 +354,7 @@ public class ImageControllerTests {
public void clearAllViewers() throws Exception { public void clearAllViewers() throws Exception {
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage(); final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
final String accessToken = this.getAccessToken(ownerViewerImage.owner()); final String accessToken = this.getAccessToken(ownerViewerImage.owner());
final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); final ImageUpdateBody body = new ImageUpdateBody();
body.setClearAllViewers(true); body.setClearAllViewers(true);
this.mockMvc.perform( this.mockMvc.perform(
post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image())) post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
@ -367,7 +371,7 @@ public class ImageControllerTests {
public void updateInfoByViewerForbidden() throws Exception { public void updateInfoByViewerForbidden() throws Exception {
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage(); final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
final String accessToken = this.getAccessToken(ownerViewerImage.viewer()); // viewer final String accessToken = this.getAccessToken(ownerViewerImage.viewer()); // viewer
final ImageUpdateInfoBody body = new ImageUpdateInfoBody(); final ImageUpdateBody body = new ImageUpdateBody();
this.mockMvc.perform( this.mockMvc.perform(
post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image())) post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)

View File

@ -1,8 +1,8 @@
package app.mealsmadeeasy.api.image; package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.IntegrationTestsExtension; import app.mealsmadeeasy.api.IntegrationTestsExtension;
import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; import app.mealsmadeeasy.api.image.spec.ImageCreateSpec;
import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec; import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec;
import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserCreateException; import app.mealsmadeeasy.api.user.UserCreateException;
import app.mealsmadeeasy.api.user.UserService; import app.mealsmadeeasy.api.user.UserService;
@ -77,7 +77,7 @@ public class S3ImageServiceTests {
return UUID.randomUUID() + ".svg"; return UUID.randomUUID() + ".svg";
} }
private Image seedImage(User owner, ImageCreateInfoSpec spec) { private Image seedImage(User owner, ImageCreateSpec spec) {
try (final InputStream hal9000 = getHal9000InputStream()) { try (final InputStream hal9000 = getHal9000InputStream()) {
return this.imageService.create( return this.imageService.create(
owner, owner,
@ -92,12 +92,13 @@ public class S3ImageServiceTests {
} }
private Image seedImage(User owner) { 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) { private Image makePublic(Image image, User modifier) {
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec = ImageUpdateSpec.builder()
spec.setPublic(true); .isPublic(true)
.build();
return this.imageService.update(image, modifier, spec); return this.imageService.update(image, modifier, spec);
} }
@ -124,7 +125,6 @@ public class S3ImageServiceTests {
final User owner = this.seedUser(); final User owner = this.seedUser();
final Image image = this.seedImage(owner); final Image image = this.seedImage(owner);
final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(image, owner)); final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(image, owner));
//noinspection DataFlowIssue
final byte[] contentBytes = content.readAllBytes(); final byte[] contentBytes = content.readAllBytes();
assertThat(contentBytes.length, is((int) HAL_LENGTH)); assertThat(contentBytes.length, is((int) HAL_LENGTH));
content.close(); content.close();
@ -135,7 +135,6 @@ public class S3ImageServiceTests {
final User owner = this.seedUser(); final User owner = this.seedUser();
final Image image = this.seedImage(owner); final Image image = this.seedImage(owner);
final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(image, owner)); final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(image, owner));
//noinspection DataFlowIssue
content.close(); content.close();
} }
@ -145,7 +144,6 @@ public class S3ImageServiceTests {
final Image seedImage = this.seedImage(owner); final Image seedImage = this.seedImage(owner);
final Image publicImage = this.makePublic(seedImage, owner); final Image publicImage = this.makePublic(seedImage, owner);
final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(publicImage, null)); final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(publicImage, null));
//noinspection DataFlowIssue
content.close(); content.close();
} }
@ -154,11 +152,11 @@ public class S3ImageServiceTests {
final User owner = this.seedUser(); final User owner = this.seedUser();
final User viewer = this.seedUser(); final User viewer = this.seedUser();
Image seedImage = this.seedImage(owner); Image seedImage = this.seedImage(owner);
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec = ImageUpdateSpec.builder()
spec.setViewersToAdd(Set.of(viewer)); .viewersToAdd(Set.of(viewer))
.build();
final Image imageWithViewer = this.imageService.update(seedImage, owner, spec); final Image imageWithViewer = this.imageService.update(seedImage, owner, spec);
final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(imageWithViewer, viewer)); final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(imageWithViewer, viewer));
//noinspection DataFlowIssue
content.close(); content.close();
} }
@ -183,8 +181,9 @@ public class S3ImageServiceTests {
public void updateAlt() { public void updateAlt() {
final User owner = this.seedUser(); final User owner = this.seedUser();
Image image = this.seedImage(owner); Image image = this.seedImage(owner);
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec = ImageUpdateSpec.builder()
spec.setAlt("HAL 9000"); .alt("HAL 9000")
.build();
image = this.imageService.update(image, owner, spec); image = this.imageService.update(image, owner, spec);
assertThat(image.getAlt(), is("HAL 9000")); assertThat(image.getAlt(), is("HAL 9000"));
} }
@ -193,8 +192,9 @@ public class S3ImageServiceTests {
public void updateCaption() { public void updateCaption() {
final User owner = this.seedUser(); final User owner = this.seedUser();
Image image = this.seedImage(owner); Image image = this.seedImage(owner);
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec = ImageUpdateSpec.builder()
spec.setCaption("HAL 9000 from 2001: A Space Odyssey"); .caption("HAL 9000 from 2001: A Space Odyssey")
.build();
image = this.imageService.update(image, owner, spec); image = this.imageService.update(image, owner, spec);
assertThat(image.getCaption(), is("HAL 9000 from 2001: A Space Odyssey")); assertThat(image.getCaption(), is("HAL 9000 from 2001: A Space Odyssey"));
} }
@ -203,15 +203,17 @@ public class S3ImageServiceTests {
public void updateIsPublic() { public void updateIsPublic() {
final User owner = this.seedUser(); final User owner = this.seedUser();
Image image = this.seedImage(owner); Image image = this.seedImage(owner);
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec = ImageUpdateSpec.builder()
spec.setPublic(true); .isPublic(true)
.build();
image = this.imageService.update(image, owner, spec); image = this.imageService.update(image, owner, spec);
assertThat(image.getIsPublic(), is(true)); assertThat(image.getIsPublic(), is(true));
} }
private Image addViewer(Image image, User owner, User viewer) { private Image addViewer(Image image, User owner, User viewer) {
final ImageUpdateInfoSpec spec0 = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec0 = ImageUpdateSpec.builder()
spec0.setViewersToAdd(Set.of(viewer)); .viewersToAdd(Set.of(viewer))
.build();
return this.imageService.update(image, owner, spec0); return this.imageService.update(image, owner, spec0);
} }
@ -232,8 +234,9 @@ public class S3ImageServiceTests {
image = this.addViewer(image, owner, viewer); image = this.addViewer(image, owner, viewer);
assertThat(image.getViewers(), containsUsers(viewer)); assertThat(image.getViewers(), containsUsers(viewer));
final ImageUpdateInfoSpec spec1 = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec1 = ImageUpdateSpec.builder()
spec1.setViewersToRemove(Set.of(viewer)); .viewersToRemove(Set.of(viewer))
.build();
image = this.imageService.update(image, owner, spec1); image = this.imageService.update(image, owner, spec1);
assertThat(image.getViewers(), empty()); assertThat(image.getViewers(), empty());
} }
@ -246,8 +249,9 @@ public class S3ImageServiceTests {
image = this.addViewer(image, owner, viewer); image = this.addViewer(image, owner, viewer);
assertThat(image.getViewers(), containsUsers(viewer)); assertThat(image.getViewers(), containsUsers(viewer));
final ImageUpdateInfoSpec spec1 = new ImageUpdateInfoSpec(); final ImageUpdateSpec spec1 = ImageUpdateSpec.builder()
spec1.setClearAllViewers(true); .clearAllViewers(true)
.build();
image = this.imageService.update(image, owner, spec1); image = this.imageService.update(image, owner, spec1);
assertThat(image.getViewers(), empty()); assertThat(image.getViewers(), empty());
} }

View File

@ -7,7 +7,7 @@ import app.mealsmadeeasy.api.auth.LoginException;
import app.mealsmadeeasy.api.image.Image; import app.mealsmadeeasy.api.image.Image;
import app.mealsmadeeasy.api.image.ImageService; import app.mealsmadeeasy.api.image.ImageService;
import app.mealsmadeeasy.api.image.S3ImageServiceTests; 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.RecipeCreateSpec;
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
import app.mealsmadeeasy.api.recipe.star.RecipeStarService; import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
@ -117,7 +117,7 @@ public class RecipeControllerTests {
UUID.randomUUID() + ".svg", UUID.randomUUID() + ".svg",
hal9000, hal9000,
27881L, 27881L,
new ImageCreateInfoSpec() ImageCreateSpec.builder().build()
); );
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);

View File

@ -2,7 +2,7 @@ package app.mealsmadeeasy.api;
import app.mealsmadeeasy.api.image.Image; import app.mealsmadeeasy.api.image.Image;
import app.mealsmadeeasy.api.image.ImageService; 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.Recipe;
import app.mealsmadeeasy.api.recipe.RecipeService; import app.mealsmadeeasy.api.recipe.RecipeService;
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
@ -82,10 +82,11 @@ public class DevConfiguration {
rawFrontMatter, RecipeFrontMatter.class rawFrontMatter, RecipeFrontMatter.class
); );
final ImageCreateInfoSpec imageCreateSpec = new ImageCreateInfoSpec(); final ImageCreateSpec imageCreateSpec = ImageCreateSpec.builder()
imageCreateSpec.setAlt(frontMatter.mainImage.alt); .alt(frontMatter.mainImage.alt)
imageCreateSpec.setCaption(frontMatter.mainImage.caption); .caption(frontMatter.mainImage.caption)
imageCreateSpec.setPublic(frontMatter.mainImage.isPublic); .isPublic(frontMatter.mainImage.isPublic)
.build();
final Path givenPath = Path.of(frontMatter.mainImage.src); final Path givenPath = Path.of(frontMatter.mainImage.src);
final Path resolvedPath = Path.of("dev-data", "images").resolve(givenPath); final Path resolvedPath = Path.of("dev-data", "images").resolve(givenPath);
final Image mainImage; final Image mainImage;

View File

@ -8,7 +8,7 @@ import org.springframework.data.jpa.repository.Query;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> { public interface RefreshTokenRepository extends JpaRepository<RefreshToken, UUID> {
Optional<RefreshToken> findByToken(UUID token); Optional<RefreshToken> findByToken(UUID token);

View File

@ -1,8 +1,8 @@
package app.mealsmadeeasy.api.image; package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.image.body.ImageUpdateInfoBody; import app.mealsmadeeasy.api.image.body.ImageUpdateBody;
import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; import app.mealsmadeeasy.api.image.spec.ImageCreateSpec;
import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec; import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec;
import app.mealsmadeeasy.api.image.view.ImageView; import app.mealsmadeeasy.api.image.view.ImageView;
import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserService; import app.mealsmadeeasy.api.user.UserService;
@ -34,27 +34,25 @@ public class ImageController {
this.userService = userService; this.userService = userService;
} }
private ImageUpdateInfoSpec getImageUpdateSpec(ImageUpdateInfoBody body) { private ImageUpdateSpec getImageUpdateSpec(ImageUpdateBody body) {
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); final var builder = ImageUpdateSpec.builder()
spec.setAlt(body.getAlt()); .alt(body.getAlt())
spec.setCaption(body.getCaption()); .caption(body.getCaption())
spec.setPublic(body.getPublic()); .isPublic(body.getIsPublic())
.clearAllViewers(body.getClearAllViewers());
if (body.getViewersToAdd() != null) { if (body.getViewersToAdd() != null) {
spec.setViewersToAdd( builder.viewersToAdd(body.getViewersToAdd().stream()
body.getViewersToAdd().stream() .map(this.userService::getUser)
.map(this.userService::getUser) .collect(Collectors.toSet())
.collect(Collectors.toSet())
); );
} }
if (body.getViewersToRemove() != null) { if (body.getViewersToRemove() != null) {
spec.setViewersToRemove( builder.viewersToRemove(body.getViewersToRemove().stream()
body.getViewersToRemove().stream() .map(this.userService::getUser)
.map(this.userService::getUser) .collect(Collectors.toSet())
.collect(Collectors.toSet())
); );
} }
spec.setClearAllViewers(body.getClearAllViewers()); return builder.build();
return spec;
} }
@ExceptionHandler @ExceptionHandler
@ -97,19 +95,20 @@ public class ImageController {
if (principal == null) { if (principal == null) {
throw new AccessDeniedException("Must be logged in."); throw new AccessDeniedException("Must be logged in.");
} }
final ImageCreateInfoSpec createSpec = new ImageCreateInfoSpec(); final var specBuilder = ImageCreateSpec.builder()
createSpec.setAlt(alt); .alt(alt)
createSpec.setCaption(caption); .caption(caption)
createSpec.setPublic(isPublic); .isPublic(isPublic);
if (viewers != null) { 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( final Image saved = this.imageService.create(
principal, principal,
filename, filename,
image.getInputStream(), image.getInputStream(),
image.getSize(), image.getSize(),
createSpec specBuilder.build()
); );
return ResponseEntity.status(201).body(this.imageService.toImageView(saved, principal)); return ResponseEntity.status(201).body(this.imageService.toImageView(saved, principal));
} }
@ -119,7 +118,7 @@ public class ImageController {
@AuthenticationPrincipal User principal, @AuthenticationPrincipal User principal,
@PathVariable String username, @PathVariable String username,
@PathVariable String filename, @PathVariable String filename,
@RequestBody ImageUpdateInfoBody body @RequestBody ImageUpdateBody body
) throws ImageException { ) throws ImageException {
if (principal == null) { if (principal == null) {
throw new AccessDeniedException("Must be logged in."); throw new AccessDeniedException("Must be logged in.");

View File

@ -8,11 +8,11 @@ import org.springframework.data.jpa.repository.Query;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface ImageRepository extends JpaRepository<Image, Long> { public interface ImageRepository extends JpaRepository<Image, Integer> {
@Query("SELECT image FROM Image image WHERE image.id = ?1") @Query("SELECT image FROM Image image WHERE image.id = ?1")
@EntityGraph(attributePaths = { "viewers" }) @EntityGraph(attributePaths = { "viewers" })
Image getByIdWithViewers(long id); Image getByIdWithViewers(Integer id);
List<Image> findAllByOwner(User owner); List<Image> findAllByOwner(User owner);
Optional<Image> findByOwnerAndUserFilename(User owner, String filename); Optional<Image> findByOwnerAndUserFilename(User owner, String filename);

View File

@ -1,7 +1,7 @@
package app.mealsmadeeasy.api.image; package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; import app.mealsmadeeasy.api.image.spec.ImageCreateSpec;
import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec; import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec;
import app.mealsmadeeasy.api.image.view.ImageView; import app.mealsmadeeasy.api.image.view.ImageView;
import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.User;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -12,17 +12,17 @@ import java.util.List;
public interface ImageService { 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; 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 getByOwnerAndFilename(User owner, String filename, User viewer) throws ImageException;
Image getByUsernameAndFilename(String username, 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; InputStream getImageContent(Image image, @Nullable User viewer) throws IOException;
List<Image> getImagesOwnedBy(User user); List<Image> 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; void deleteImage(Image image, User modifier) throws IOException;

View File

@ -1,7 +1,7 @@
package app.mealsmadeeasy.api.image; package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec; import app.mealsmadeeasy.api.image.spec.ImageCreateSpec;
import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec; import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec;
import app.mealsmadeeasy.api.image.view.ImageView; import app.mealsmadeeasy.api.image.view.ImageView;
import app.mealsmadeeasy.api.s3.S3Manager; import app.mealsmadeeasy.api.s3.S3Manager;
import app.mealsmadeeasy.api.user.User; 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; boolean didTransfer = false;
if (spec.getAlt() != null) { if (spec.getAlt() != null) {
entity.setAlt(spec.getAlt()); entity.setAlt(spec.getAlt());
@ -88,22 +88,55 @@ public class S3ImageService implements ImageService {
entity.setCaption(spec.getCaption()); entity.setCaption(spec.getCaption());
didTransfer = true; didTransfer = true;
} }
if (spec.getPublic() != null) { if (spec.getIsPublic() != null) {
entity.setIsPublic(spec.getPublic()); entity.setIsPublic(spec.getIsPublic());
didTransfer = true; didTransfer = true;
} }
final @Nullable Set<User> viewersToAdd = spec.getViewersToAdd(); final @Nullable Set<User> viewersToAdd = spec.getViewersToAdd();
if (viewersToAdd != null) { if (viewersToAdd != null) {
final Set<User> viewers = new HashSet<>(entity.getViewers()); final Set<User> viewers = new HashSet<>(entity.getViewers());
for (final User viewerToAdd : spec.getViewersToAdd()) { viewers.addAll(spec.getViewersToAdd());
viewers.add((User) viewerToAdd);
}
entity.setViewers(viewers); entity.setViewers(viewers);
didTransfer = true; didTransfer = true;
} }
return didTransfer; 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<User> viewersToAdd = spec.getViewersToAdd();
if (viewersToAdd != null) {
final Set<User> viewers = new HashSet<>(entity.getViewers());
viewers.addAll(spec.getViewersToAdd());
entity.setViewers(viewers);
didTransfer = true;
}
final @Nullable Set<User> viewersToRemove = spec.getViewersToRemove();
if (viewersToRemove != null) {
final Set<User> 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}. * @apiNote Consumes and closes the {@link java.io.InputStream}.
* *
@ -122,7 +155,7 @@ public class S3ImageService implements ImageService {
String userFilename, String userFilename,
InputStream inputStream, InputStream inputStream,
long objectSize, long objectSize,
ImageCreateInfoSpec createSpec ImageCreateSpec createSpec
) throws IOException, ImageException { ) throws IOException, ImageException {
final String mimeType = this.getMimeType(userFilename); final String mimeType = this.getMimeType(userFilename);
final String uuid = UUID.randomUUID().toString(); final String uuid = UUID.randomUUID().toString();
@ -153,19 +186,19 @@ public class S3ImageService implements ImageService {
inputStream.close(); inputStream.close();
final Image draft = new Image(); final Image draft = new Image();
draft.setOwner((User) owner); draft.setOwner(owner);
draft.setUserFilename(userFilename); draft.setUserFilename(userFilename);
draft.setMimeType(mimeType); draft.setMimeType(mimeType);
draft.setObjectName(objectName); draft.setObjectName(objectName);
draft.setHeight(height); draft.setHeight(height);
draft.setWidth(width); draft.setWidth(width);
this.transferFromSpec(draft, createSpec); this.transferFromCreateSpec(draft, createSpec);
return this.imageRepository.save(draft); return this.imageRepository.save(draft);
} }
@Override @Override
@PostAuthorize("@imageSecurity.isViewableBy(returnObject, #viewer)") @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( return this.imageRepository.findById(id).orElseThrow(() -> new ImageException(
ImageException.Type.INVALID_ID, "No Image with id: " + id ImageException.Type.INVALID_ID, "No Image with id: " + id
)); ));
@ -205,36 +238,19 @@ public class S3ImageService implements ImageService {
@Override @Override
@PreAuthorize("@imageSecurity.isOwner(#image, #modifier)") @PreAuthorize("@imageSecurity.isOwner(#image, #modifier)")
public Image update(final Image image, User modifier, ImageUpdateInfoSpec updateSpec) { public Image update(final Image image, User modifier, ImageUpdateSpec updateSpec) {
Image entity = (Image) image; final boolean didUpdate = this.transferFromUpdateSpec(image, updateSpec);
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<User> viewersToRemove = updateSpec.getViewersToRemove();
if (viewersToRemove != null) {
final Set<User> currentViewers = new HashSet<>(entity.getViewers());
for (final User toRemove : updateSpec.getViewersToRemove()) {
currentViewers.remove((User) toRemove);
}
entity.setViewers(currentViewers);
didUpdate = true;
}
}
if (didUpdate) { if (didUpdate) {
entity.setModified(OffsetDateTime.now()); image.setModified(OffsetDateTime.now());
} }
return this.imageRepository.save(entity); return this.imageRepository.save(image);
} }
@Override @Override
@PreAuthorize("@imageSecurity.isOwner(#image, #modifier)") @PreAuthorize("@imageSecurity.isOwner(#image, #modifier)")
public void deleteImage(Image image, User modifier) throws IOException { public void deleteImage(Image image, User modifier) throws IOException {
final Image imageEntity = (Image) image; this.imageRepository.delete(image);
this.imageRepository.delete(imageEntity); this.s3Manager.delete("images", image.getObjectName());
this.s3Manager.delete("images", imageEntity.getObjectName());
} }
private String getImageUrl(Image image) { private String getImageUrl(Image image) {

View File

@ -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<String> viewersToAdd;
private @Nullable Set<String> viewersToRemove;
private @Nullable Boolean clearAllViewers;
}

View File

@ -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<String> viewersToAdd;
private @Nullable Set<String> 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<String> getViewersToAdd() {
return this.viewersToAdd;
}
public void setViewersToAdd(@Nullable Set<String> viewersToAdd) {
this.viewersToAdd = viewersToAdd;
}
public @Nullable Set<String> getViewersToRemove() {
return this.viewersToRemove;
}
public void setViewersToRemove(@Nullable Set<String> viewersToRemove) {
this.viewersToRemove = viewersToRemove;
}
public @Nullable Boolean getClearAllViewers() {
return this.clearAllViewers;
}
public void setClearAllViewers(@Nullable Boolean clearAllViewers) {
this.clearAllViewers = clearAllViewers;
}
}

View File

@ -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<User> 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<User> getViewersToAdd() {
return this.viewersToAdd;
}
public void setViewersToAdd(@Nullable Set<User> viewersToAdd) {
this.viewersToAdd = viewersToAdd;
}
}

View File

@ -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<User> viewersToAdd;
}

View File

@ -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<User> viewersToRemove;
private @Nullable Boolean clearAllViewers;
public @Nullable Set<User> getViewersToRemove() {
return this.viewersToRemove;
}
public void setViewersToRemove(@Nullable Set<User> viewersToRemove) {
this.viewersToRemove = viewersToRemove;
}
public @Nullable Boolean getClearAllViewers() {
return this.clearAllViewers;
}
public void setClearAllViewers(@Nullable Boolean clearAllViewers) {
this.clearAllViewers = clearAllViewers;
}
}

View File

@ -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<User> viewersToAdd;
@Nullable Set<User> viewersToRemove;
@Nullable Boolean clearAllViewers;
}

View File

@ -1,6 +0,0 @@
package app.mealsmadeeasy.api.image.view;
public class ImageExceptionView {
}

View File

@ -2,143 +2,52 @@ package app.mealsmadeeasy.api.image.view;
import app.mealsmadeeasy.api.image.Image; import app.mealsmadeeasy.api.image.Image;
import app.mealsmadeeasy.api.user.view.UserInfoView; import app.mealsmadeeasy.api.user.view.UserInfoView;
import lombok.Builder;
import lombok.Value;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Value
@Builder
public class ImageView { public class ImageView {
public static ImageView from(Image image, String url, boolean includeViewers) { public static ImageView from(Image image, String url, boolean includeViewers) {
final ImageView view = new ImageView(); final var builder = ImageView.builder()
view.setUrl(url); .url(url)
view.setCreated(image.getCreated()); .created(image.getCreated())
view.setModified(image.getModified()); .modified(image.getModified())
view.setFilename(image.getUserFilename()); .filename(image.getUserFilename())
view.setMimeType(image.getMimeType()); .mimeType(image.getMimeType())
view.setAlt(image.getAlt()); .alt(image.getAlt())
view.setCaption(image.getCaption()); .caption(image.getCaption())
view.setOwner(UserInfoView.from(image.getOwner())); .owner(UserInfoView.from(image.getOwner()))
view.setIsPublic(image.getIsPublic()); .isPublic(image.getIsPublic())
view.setHeight(image.getHeight()); .height(image.getHeight())
view.setWidth(image.getWidth()); .width(image.getWidth());
if (includeViewers) { if (includeViewers) {
view.setViewers(image.getViewers().stream() builder.viewers(
.map(UserInfoView::from) image.getViewers().stream()
.collect(Collectors.toSet()) .map(UserInfoView::from)
.collect(Collectors.toSet())
); );
} }
return view; return builder.build();
} }
private String url; String url;
private OffsetDateTime created; OffsetDateTime created;
private @Nullable OffsetDateTime modified; @Nullable OffsetDateTime modified;
private String filename; String filename;
private String mimeType; String mimeType;
private @Nullable String alt; @Nullable String alt;
private @Nullable String caption; @Nullable String caption;
private UserInfoView owner; UserInfoView owner;
private boolean isPublic; boolean isPublic;
private @Nullable Integer height; @Nullable Integer height;
private @Nullable Integer width; @Nullable Integer width;
private @Nullable Set<UserInfoView> viewers; @Nullable Set<UserInfoView> 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<UserInfoView> getViewers() {
return this.viewers;
}
public void setViewers(@Nullable Set<UserInfoView> viewers) {
this.viewers = viewers;
}
} }