From 24db93111fb3b25ddbec10b66a019abe5522456c Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Mon, 22 Jul 2024 18:21:07 -0500 Subject: [PATCH] Added ImageService.getImageContentById and ImageService.*viewer* methods. --- .../api/image/S3ImageServiceTests.java | 71 ++++++++++++++++++- .../resources/application.properties | 1 + .../mealsmadeeasy/api/image/ImageService.java | 11 ++- .../api/image/S3ImageService.java | 67 ++++++++++++++--- src/main/resources/application.properties | 3 +- 5 files changed, 140 insertions(+), 13 deletions(-) diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java index 69feb5e..327d10c 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java @@ -41,6 +41,10 @@ public class S3ImageServiceTests { registry.add("app.mealsmadeeasy.api.minio.secretKey", container::getPassword); } + private static InputStream getHal9000() { + return S3ImageServiceTests.class.getResourceAsStream("HAL9000.svg"); + } + @Autowired private UserService userService; @@ -61,7 +65,7 @@ public class S3ImageServiceTests { @Test @DirtiesContext public void simpleCreate() { - try (final InputStream hal9000 = S3ImageServiceTests.class.getResourceAsStream("HAL9000.svg")) { + try (final InputStream hal9000 = getHal9000()) { final User owner = this.createTestUser("imageOwner"); final Image image = this.imageService.create( owner, @@ -85,4 +89,69 @@ public class S3ImageServiceTests { } } + @Test + @DirtiesContext + public void loadImageWithOwner() { + try (final InputStream hal9000 = getHal9000()) { + final User owner = this.createTestUser("imageOwner"); + final Image image = this.imageService.create( + owner, + "HAL9000.svg", + hal9000, + "image/svg+xml", + 27881L + ); + try (final InputStream stored = this.imageService.getImageContentById(image.getId(), owner)) { + final byte[] storedBytes = stored.readAllBytes(); + assertThat(storedBytes.length, is(27881)); + } + } catch (IOException | ImageException e) { + throw new RuntimeException(e); + } + } + + @Test + public void loadPublicImage() { + try (final InputStream hal9000 = getHal9000()) { + final User owner = this.createTestUser("imageOwner"); + Image image = this.imageService.create( + owner, + "HAL9000.svg", + hal9000, + "image/svg+xml", + 27881L + ); + image = this.imageService.setPublic(image, owner, true); + try (final InputStream stored = this.imageService.getImageContentById(image.getId())) { + final byte[] storedBytes = stored.readAllBytes(); + assertThat(storedBytes.length, is(27881)); + } + } catch (IOException | ImageException e) { + throw new RuntimeException(e); + } + } + + @Test + @DirtiesContext + public void loadImageWithViewer() { + try (final InputStream hal9000 = getHal9000()) { + final User owner = this.createTestUser("imageOwner"); + final User viewer = this.createTestUser("imageViewer"); + Image image = this.imageService.create( + owner, + "HAL9000.svg", + hal9000, + "image/svg+xml", + 27881L + ); + image = this.imageService.addViewer(image, owner, viewer); + try (final InputStream stored = this.imageService.getImageContentById(image.getId(), viewer)) { + final byte[] storedBytes = stored.readAllBytes(); + assertThat(storedBytes.length, is(27881)); + } + } catch (IOException | ImageException e) { + throw new RuntimeException(e); + } + } + } diff --git a/src/integrationTest/resources/application.properties b/src/integrationTest/resources/application.properties index db534b8..224bc54 100644 --- a/src/integrationTest/resources/application.properties +++ b/src/integrationTest/resources/application.properties @@ -7,3 +7,4 @@ app.mealsmadeeasy.api.security.refresh-token-lifetime=120 app.mealsmadeeasy.api.minio.endpoint=http://localhost:9000 app.mealsmadeeasy.api.minio.accessKey=minio-root app.mealsmadeeasy.api.minio.secretKey=test0123 +app.mealsmadeeasy.api.images.bucketName=images \ No newline at end of file diff --git a/src/main/java/app/mealsmadeeasy/api/image/ImageService.java b/src/main/java/app/mealsmadeeasy/api/image/ImageService.java index 4b49cf3..856e63b 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/ImageService.java +++ b/src/main/java/app/mealsmadeeasy/api/image/ImageService.java @@ -11,8 +11,11 @@ public interface ImageService { Image create(User owner, String userFilename, InputStream inputStream, String mimeType, long objectSize) throws IOException, ImageException; - Image getById(long id); - Image getById(long id, User viewer); + Image getById(long id) throws ImageException; + Image getById(long id, User viewer) throws ImageException; + + InputStream getImageContentById(long id) throws IOException, ImageException; + InputStream getImageContentById(long id, User viewer) throws IOException, ImageException; List getImagesOwnedBy(User user); @@ -22,6 +25,10 @@ public interface ImageService { Image setCaption(Image image, User owner, String caption); Image setPublic(Image image, User owner, boolean isPublic); + Image addViewer(Image image, User owner, User viewer); + Image removeViewer(Image image, User owner, User viewer); + Image clearViewers(Image image, User owner); + void deleteImage(Image image, User owner) throws IOException; void deleteById(long id, User owner) 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 1136620..ce0a4bf 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java +++ b/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java @@ -3,6 +3,7 @@ package app.mealsmadeeasy.api.image; import app.mealsmadeeasy.api.s3.S3Manager; import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.UserEntity; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @@ -17,10 +18,16 @@ public class S3ImageService implements ImageService { private final S3Manager s3Manager; private final ImageRepository imageRepository; + private final String imageBucketName; - public S3ImageService(S3Manager s3Manager, ImageRepository imageRepository) { + public S3ImageService( + S3Manager s3Manager, + ImageRepository imageRepository, + @Value("${app.mealsmadeeasy.api.images.bucketName}") String imageBucketName + ) { this.s3Manager = s3Manager; this.imageRepository = imageRepository; + this.imageBucketName = imageBucketName; } private String getExtension(String mimeType) throws ImageException { @@ -39,27 +46,45 @@ public class S3ImageService implements ImageService { final String uuid = UUID.randomUUID().toString(); final String extension = this.getExtension(mimeType); final String filename = uuid + "." + extension; - final String objectName = this.s3Manager.store("images", filename, mimeType, inputStream, objectSize); + final String objectName = this.s3Manager.store( + this.imageBucketName, filename, mimeType, inputStream, objectSize + ); final ImageEntity draft = new ImageEntity(); draft.setOwner((UserEntity) owner); draft.setUserFilename(userFilename); draft.setMimeType(mimeType); draft.setObjectName(objectName); - draft.setInternalUrl(this.s3Manager.getUrl("images", objectName)); + draft.setInternalUrl(this.s3Manager.getUrl(this.imageBucketName, objectName)); return this.imageRepository.save(draft); } @Override @PostAuthorize("returnObject.isPublic") - public Image getById(long id) { - return this.imageRepository.getReferenceById(id); + public Image getById(long id) throws ImageException { + return this.imageRepository.findById(id).orElseThrow(() -> new ImageException( + ImageException.Type.INVALID_ID, "No such image with id " + id + )); } @Override @PostAuthorize("@imageSecurity.isViewableBy(returnObject, #viewer)") - public Image getById(long id, User viewer) { - return this.imageRepository.getReferenceById(id); + public Image getById(long id, User viewer) throws ImageException { + return this.imageRepository.findById(id).orElseThrow(() -> new ImageException( + ImageException.Type.INVALID_ID, "No such image with id " + id + )); + } + + @Override + public InputStream getImageContentById(long id) throws IOException, ImageException { + final Image image = this.getById(id); + return this.s3Manager.load(this.imageBucketName, image.getObjectName()); + } + + @Override + public InputStream getImageContentById(long id, User viewer) throws IOException, ImageException { + final Image image = this.getById(id, viewer); + return this.s3Manager.load(this.imageBucketName, image.getObjectName()); } @Override @@ -83,12 +108,36 @@ public class S3ImageService implements ImageService { } @Override + @PreAuthorize("@imageSecurity.isOwner(#image, #owner)") public Image setPublic(Image image, User owner, boolean isPublic) { - return null; + final ImageEntity imageEntity = (ImageEntity) image; + imageEntity.setPublic(isPublic); + return this.imageRepository.save(imageEntity); } @Override - @PreAuthorize("@imageSecurity.isOwner(image, owner)") + public Image addViewer(Image image, User owner, User viewer) { + final ImageEntity withViewers = this.imageRepository.getByIdWithViewers(image.getId()); + withViewers.getViewers().add((UserEntity) viewer); + return this.imageRepository.save(withViewers); + } + + @Override + public Image removeViewer(Image image, User owner, User viewer) { + final ImageEntity withViewers = this.imageRepository.getByIdWithViewers(image.getId()); + withViewers.getViewers().remove((UserEntity) viewer); + return this.imageRepository.save(withViewers); + } + + @Override + public Image clearViewers(Image image, User owner) { + final ImageEntity withViewers = this.imageRepository.getByIdWithViewers(image.getId()); + withViewers.getViewers().clear(); + return this.imageRepository.save(withViewers); + } + + @Override + @PreAuthorize("@imageSecurity.isOwner(#image, #owner)") public void deleteImage(Image image, User owner) throws IOException { this.imageRepository.delete((ImageEntity) image); this.s3Manager.delete("images", image.getObjectName()); // TODO diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 23237ab..a2316b6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,4 +8,5 @@ app.mealsmadeeasy.api.security.access-token-lifetime=60 app.mealsmadeeasy.api.security.refresh-token-lifetime=120 app.mealsmadeeasy.api.minio.endpoint=http://localhost:9000 app.mealsmadeeasy.api.minio.accessKey=minio-root -app.mealsmadeeasy.api.minio.secretKey=test0123 \ No newline at end of file +app.mealsmadeeasy.api.minio.secretKey=test0123 +app.mealsmadeeasy.api.images.bucketName=images \ No newline at end of file