Added ImageService.getImageContentById and ImageService.*viewer* methods.

This commit is contained in:
Jesse Brault 2024-07-22 18:21:07 -05:00
parent bb65ec7d24
commit 24db93111f
5 changed files with 140 additions and 13 deletions

View File

@ -41,6 +41,10 @@ public class S3ImageServiceTests {
registry.add("app.mealsmadeeasy.api.minio.secretKey", container::getPassword); registry.add("app.mealsmadeeasy.api.minio.secretKey", container::getPassword);
} }
private static InputStream getHal9000() {
return S3ImageServiceTests.class.getResourceAsStream("HAL9000.svg");
}
@Autowired @Autowired
private UserService userService; private UserService userService;
@ -61,7 +65,7 @@ public class S3ImageServiceTests {
@Test @Test
@DirtiesContext @DirtiesContext
public void simpleCreate() { public void simpleCreate() {
try (final InputStream hal9000 = S3ImageServiceTests.class.getResourceAsStream("HAL9000.svg")) { try (final InputStream hal9000 = getHal9000()) {
final User owner = this.createTestUser("imageOwner"); final User owner = this.createTestUser("imageOwner");
final Image image = this.imageService.create( final Image image = this.imageService.create(
owner, 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);
}
}
} }

View File

@ -7,3 +7,4 @@ app.mealsmadeeasy.api.security.refresh-token-lifetime=120
app.mealsmadeeasy.api.minio.endpoint=http://localhost:9000 app.mealsmadeeasy.api.minio.endpoint=http://localhost:9000
app.mealsmadeeasy.api.minio.accessKey=minio-root app.mealsmadeeasy.api.minio.accessKey=minio-root
app.mealsmadeeasy.api.minio.secretKey=test0123 app.mealsmadeeasy.api.minio.secretKey=test0123
app.mealsmadeeasy.api.images.bucketName=images

View File

@ -11,8 +11,11 @@ public interface ImageService {
Image create(User owner, String userFilename, InputStream inputStream, String mimeType, long objectSize) Image create(User owner, String userFilename, InputStream inputStream, String mimeType, long objectSize)
throws IOException, ImageException; throws IOException, ImageException;
Image getById(long id); Image getById(long id) throws ImageException;
Image getById(long id, User viewer); 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<Image> getImagesOwnedBy(User user); List<Image> getImagesOwnedBy(User user);
@ -22,6 +25,10 @@ public interface ImageService {
Image setCaption(Image image, User owner, String caption); Image setCaption(Image image, User owner, String caption);
Image setPublic(Image image, User owner, boolean isPublic); 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 deleteImage(Image image, User owner) throws IOException;
void deleteById(long id, User owner) throws IOException; void deleteById(long id, User owner) throws IOException;

View File

@ -3,6 +3,7 @@ package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.s3.S3Manager; import app.mealsmadeeasy.api.s3.S3Manager;
import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserEntity; 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.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -17,10 +18,16 @@ public class S3ImageService implements ImageService {
private final S3Manager s3Manager; private final S3Manager s3Manager;
private final ImageRepository imageRepository; 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.s3Manager = s3Manager;
this.imageRepository = imageRepository; this.imageRepository = imageRepository;
this.imageBucketName = imageBucketName;
} }
private String getExtension(String mimeType) throws ImageException { private String getExtension(String mimeType) throws ImageException {
@ -39,27 +46,45 @@ public class S3ImageService implements ImageService {
final String uuid = UUID.randomUUID().toString(); final String uuid = UUID.randomUUID().toString();
final String extension = this.getExtension(mimeType); final String extension = this.getExtension(mimeType);
final String filename = uuid + "." + extension; 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(); final ImageEntity draft = new ImageEntity();
draft.setOwner((UserEntity) owner); draft.setOwner((UserEntity) owner);
draft.setUserFilename(userFilename); draft.setUserFilename(userFilename);
draft.setMimeType(mimeType); draft.setMimeType(mimeType);
draft.setObjectName(objectName); draft.setObjectName(objectName);
draft.setInternalUrl(this.s3Manager.getUrl("images", objectName)); draft.setInternalUrl(this.s3Manager.getUrl(this.imageBucketName, objectName));
return this.imageRepository.save(draft); return this.imageRepository.save(draft);
} }
@Override @Override
@PostAuthorize("returnObject.isPublic") @PostAuthorize("returnObject.isPublic")
public Image getById(long id) { public Image getById(long id) throws ImageException {
return this.imageRepository.getReferenceById(id); return this.imageRepository.findById(id).orElseThrow(() -> new ImageException(
ImageException.Type.INVALID_ID, "No such image with id " + id
));
} }
@Override @Override
@PostAuthorize("@imageSecurity.isViewableBy(returnObject, #viewer)") @PostAuthorize("@imageSecurity.isViewableBy(returnObject, #viewer)")
public Image getById(long id, User viewer) { public Image getById(long id, User viewer) throws ImageException {
return this.imageRepository.getReferenceById(id); 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 @Override
@ -83,12 +108,36 @@ public class S3ImageService implements ImageService {
} }
@Override @Override
@PreAuthorize("@imageSecurity.isOwner(#image, #owner)")
public Image setPublic(Image image, User owner, boolean isPublic) { 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 @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 { public void deleteImage(Image image, User owner) throws IOException {
this.imageRepository.delete((ImageEntity) image); this.imageRepository.delete((ImageEntity) image);
this.s3Manager.delete("images", image.getObjectName()); // TODO this.s3Manager.delete("images", image.getObjectName()); // TODO

View File

@ -8,4 +8,5 @@ app.mealsmadeeasy.api.security.access-token-lifetime=60
app.mealsmadeeasy.api.security.refresh-token-lifetime=120 app.mealsmadeeasy.api.security.refresh-token-lifetime=120
app.mealsmadeeasy.api.minio.endpoint=http://localhost:9000 app.mealsmadeeasy.api.minio.endpoint=http://localhost:9000
app.mealsmadeeasy.api.minio.accessKey=minio-root app.mealsmadeeasy.api.minio.accessKey=minio-root
app.mealsmadeeasy.api.minio.secretKey=test0123 app.mealsmadeeasy.api.minio.secretKey=test0123
app.mealsmadeeasy.api.images.bucketName=images