From bea8af4a0e8a32037660e4cf1dc8445e59901835 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Thu, 15 Jan 2026 15:35:08 -0600 Subject: [PATCH] Get rid of Image interface. --- .../api/image/S3ImageServiceTests.java | 4 +- .../app/mealsmadeeasy/api/image/Image.java | 64 ++++-- ...geRepository.java => ImageRepository.java} | 10 +- .../api/image/ImageSecurityImpl.java | 8 +- .../api/image/S3ImageEntity.java | 182 ------------------ .../api/image/S3ImageService.java | 20 +- .../api/image/view/ImageView.java | 2 +- .../app/mealsmadeeasy/api/recipe/Recipe.java | 4 +- .../api/recipe/RecipeServiceImpl.java | 7 +- 9 files changed, 77 insertions(+), 224 deletions(-) rename src/main/java/app/mealsmadeeasy/api/image/{S3ImageRepository.java => ImageRepository.java} (59%) delete mode 100644 src/main/java/app/mealsmadeeasy/api/image/S3ImageEntity.java diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java index 963fa12..ed34673 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/image/S3ImageServiceTests.java @@ -115,7 +115,7 @@ public class S3ImageServiceTests { assertThat(image.getMimeType(), is("image/svg+xml")); assertThat(image.getAlt(), is(nullValue())); assertThat(image.getCaption(), is(nullValue())); - assertThat(image.isPublic(), is(false)); + assertThat(image.getIsPublic(), is(false)); assertThat(image.getViewers(), is(empty())); } @@ -206,7 +206,7 @@ public class S3ImageServiceTests { final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec(); spec.setPublic(true); image = this.imageService.update(image, owner, spec); - assertThat(image.isPublic(), is(true)); + assertThat(image.getIsPublic(), is(true)); } private Image addViewer(Image image, User owner, User viewer) { diff --git a/src/main/java/app/mealsmadeeasy/api/image/Image.java b/src/main/java/app/mealsmadeeasy/api/image/Image.java index 5f655ca..0e5a967 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/Image.java +++ b/src/main/java/app/mealsmadeeasy/api/image/Image.java @@ -1,22 +1,58 @@ package app.mealsmadeeasy.api.image; import app.mealsmadeeasy.api.user.User; -import org.jetbrains.annotations.Nullable; +import jakarta.persistence.*; +import lombok.Data; import java.time.OffsetDateTime; +import java.util.HashSet; import java.util.Set; -public interface Image { - Integer getId(); - OffsetDateTime getCreated(); - @Nullable OffsetDateTime getModified(); - String getUserFilename(); - String getMimeType(); - @Nullable String getAlt(); - @Nullable String getCaption(); - User getOwner(); - boolean isPublic(); - @Nullable Integer getHeight(); - @Nullable Integer getWidth(); - Set getViewers(); +@Entity +@Table(name = "image") +@Data +public class Image { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false, updatable = false) + private Integer id; + + @Column(nullable = false) + private OffsetDateTime created = OffsetDateTime.now(); + + private OffsetDateTime modified; + + @Column(nullable = false) + private String userFilename; + + @Column(nullable = false) + private String mimeType; + + private String alt; + + private String caption; + + @Column(nullable = false) + private String objectName; + + private Integer height; + + private Integer width; + + @ManyToOne(optional = false) + @JoinColumn(name = "owner_id", nullable = false) + private User owner; + + @Column(nullable = false) + private Boolean isPublic = false; + + @ManyToMany + @JoinTable( + name = "image_viewer", + joinColumns = @JoinColumn(name = "image_id"), + inverseJoinColumns = @JoinColumn(name = "viewer_id") + ) + private Set viewers = new HashSet<>(); + } diff --git a/src/main/java/app/mealsmadeeasy/api/image/S3ImageRepository.java b/src/main/java/app/mealsmadeeasy/api/image/ImageRepository.java similarity index 59% rename from src/main/java/app/mealsmadeeasy/api/image/S3ImageRepository.java rename to src/main/java/app/mealsmadeeasy/api/image/ImageRepository.java index fe2a5d0..2009468 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/S3ImageRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/image/ImageRepository.java @@ -8,16 +8,16 @@ import org.springframework.data.jpa.repository.Query; import java.util.List; import java.util.Optional; -public interface S3ImageRepository extends JpaRepository { +public interface ImageRepository extends JpaRepository { @Query("SELECT image FROM Image image WHERE image.id = ?1") @EntityGraph(attributePaths = { "viewers" }) - S3ImageEntity getByIdWithViewers(long id); + Image getByIdWithViewers(long id); - List findAllByOwner(User owner); - Optional findByOwnerAndUserFilename(User owner, String filename); + List findAllByOwner(User owner); + Optional findByOwnerAndUserFilename(User owner, String filename); @Query("SELECT image from Image image WHERE image.owner.username = ?1 AND image.userFilename = ?2") - Optional findByOwnerUsernameAndFilename(String username, String filename); + Optional findByOwnerUsernameAndFilename(String username, String filename); } diff --git a/src/main/java/app/mealsmadeeasy/api/image/ImageSecurityImpl.java b/src/main/java/app/mealsmadeeasy/api/image/ImageSecurityImpl.java index a9adfa2..0991718 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/ImageSecurityImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/image/ImageSecurityImpl.java @@ -9,15 +9,15 @@ import java.util.Objects; @Component("imageSecurity") public class ImageSecurityImpl implements ImageSecurity { - private final S3ImageRepository imageRepository; + private final ImageRepository imageRepository; - public ImageSecurityImpl(S3ImageRepository imageRepository) { + public ImageSecurityImpl(ImageRepository imageRepository) { this.imageRepository = imageRepository; } @Override public boolean isViewableBy(Image image, @Nullable User viewer) { - if (image.isPublic()) { + if (image.getIsPublic()) { // public image return true; } else if (viewer == null) { @@ -28,7 +28,7 @@ public class ImageSecurityImpl implements ImageSecurity { return true; } else { // check if viewer - final S3ImageEntity withViewers = this.imageRepository.getByIdWithViewers(image.getId()); + final Image withViewers = this.imageRepository.getByIdWithViewers(image.getId()); for (final User user : withViewers.getViewers()) { if (user.getId() != null && user.getId().equals(viewer.getId())) { return true; diff --git a/src/main/java/app/mealsmadeeasy/api/image/S3ImageEntity.java b/src/main/java/app/mealsmadeeasy/api/image/S3ImageEntity.java deleted file mode 100644 index 9d2bd20..0000000 --- a/src/main/java/app/mealsmadeeasy/api/image/S3ImageEntity.java +++ /dev/null @@ -1,182 +0,0 @@ -package app.mealsmadeeasy.api.image; - -import app.mealsmadeeasy.api.user.User; -import jakarta.persistence.*; -import org.jetbrains.annotations.Nullable; - -import java.time.OffsetDateTime; -import java.util.HashSet; -import java.util.Set; - -@Entity(name = "Image") -@Table(name = "image") -public class S3ImageEntity implements Image { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(nullable = false, updatable = false) - private Integer id; - - @Column(nullable = false) - private OffsetDateTime created = OffsetDateTime.now(); - - private OffsetDateTime modified; - - @Column(nullable = false) - private String userFilename; - - @Column(nullable = false) - private String mimeType; - - private String alt; - - private String caption; - - @Column(nullable = false) - private String objectName; - - private Integer height; - - private Integer width; - - @ManyToOne(optional = false) - @JoinColumn(name = "owner_id", nullable = false) - private User owner; - - @Column(nullable = false) - private Boolean isPublic = false; - - @ManyToMany - @JoinTable( - name = "image_viewer", - joinColumns = @JoinColumn(name = "image_id"), - inverseJoinColumns = @JoinColumn(name = "viewer_id") - ) - private Set viewers = new HashSet<>(); - - @Override - public Integer getId() { - return this.id; - } - - public void setId(Integer id) { - this.id = id; - } - - @Override - public OffsetDateTime getCreated() { - return this.created; - } - - public void setCreated(OffsetDateTime created) { - this.created = created; - } - - @Override - public @Nullable OffsetDateTime getModified() { - return this.modified; - } - - public void setModified(OffsetDateTime modified) { - this.modified = modified; - } - - @Override - public String getUserFilename() { - return this.userFilename; - } - - public void setUserFilename(String userFilename) { - this.userFilename = userFilename; - } - - @Override - public String getMimeType() { - return this.mimeType; - } - - public void setMimeType(String mimeType) { - this.mimeType = mimeType; - } - - @Override - public @Nullable String getAlt() { - return this.alt; - } - - public void setAlt(String alt) { - this.alt = alt; - } - - @Override - public @Nullable String getCaption() { - return this.caption; - } - - public void setCaption(String caption) { - this.caption = caption; - } - - public String getObjectName() { - return this.objectName; - } - - public void setObjectName(String objectName) { - this.objectName = objectName; - } - - @Override - public @Nullable Integer getHeight() { - return this.height; - } - - public void setHeight(Integer height) { - this.height = height; - } - - @Override - public @Nullable Integer getWidth() { - return this.width; - } - - public void setWidth(Integer width) { - this.width = width; - } - - @Override - public User getOwner() { - return this.owner; - } - - public void setOwner(User owner) { - this.owner = owner; - } - - @Override - public boolean isPublic() { - return this.isPublic; - } - - public void setPublic(Boolean aPublic) { - isPublic = aPublic; - } - - @Override - public Set getViewers() { - return Set.copyOf(this.viewers); - } - - public Set getViewerEntities() { - return this.viewers; - } - - public void setViewers(Set viewers) { - this.viewers = viewers; - } - - @Override - public String toString() { - return "S3ImageEntity(" + this.id + ", " + this.userFilename + ", " + this.objectName + ")"; - } - -} diff --git a/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java b/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java index e79216c..b23d91b 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java +++ b/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java @@ -33,13 +33,13 @@ public class S3ImageService implements ImageService { private static final String IMAGE_WEBP = "image/webp"; private final S3Manager s3Manager; - private final S3ImageRepository imageRepository; + private final ImageRepository imageRepository; private final String imageBucketName; private final String baseUrl; public S3ImageService( S3Manager s3Manager, - S3ImageRepository imageRepository, + ImageRepository imageRepository, @Value("${app.mealsmadeeasy.api.images.bucketName}") String imageBucketName, @Value("${app.mealsmadeeasy.api.baseUrl}") String baseUrl ) { @@ -78,7 +78,7 @@ public class S3ImageService implements ImageService { }; } - private boolean transferFromSpec(S3ImageEntity entity, ImageCreateInfoSpec spec) { + private boolean transferFromSpec(Image entity, ImageCreateInfoSpec spec) { boolean didTransfer = false; if (spec.getAlt() != null) { entity.setAlt(spec.getAlt()); @@ -89,12 +89,12 @@ public class S3ImageService implements ImageService { didTransfer = true; } if (spec.getPublic() != null) { - entity.setPublic(spec.getPublic()); + entity.setIsPublic(spec.getPublic()); didTransfer = true; } final @Nullable Set viewersToAdd = spec.getViewersToAdd(); if (viewersToAdd != null) { - final Set viewers = new HashSet<>(entity.getViewerEntities()); + final Set viewers = new HashSet<>(entity.getViewers()); for (final User viewerToAdd : spec.getViewersToAdd()) { viewers.add((User) viewerToAdd); } @@ -152,7 +152,7 @@ public class S3ImageService implements ImageService { toStore.close(); inputStream.close(); - final S3ImageEntity draft = new S3ImageEntity(); + final Image draft = new Image(); draft.setOwner((User) owner); draft.setUserFilename(userFilename); draft.setMimeType(mimeType); @@ -195,7 +195,7 @@ public class S3ImageService implements ImageService { @Override @PreAuthorize("@imageSecurity.isViewableBy(#image, #viewer)") public InputStream getImageContent(Image image, User viewer) throws IOException { - return this.s3Manager.load(this.imageBucketName, ((S3ImageEntity) image).getObjectName()); + return this.s3Manager.load(this.imageBucketName, ((Image) image).getObjectName()); } @Override @@ -206,7 +206,7 @@ public class S3ImageService implements ImageService { @Override @PreAuthorize("@imageSecurity.isOwner(#image, #modifier)") public Image update(final Image image, User modifier, ImageUpdateInfoSpec updateSpec) { - S3ImageEntity entity = (S3ImageEntity) image; + Image entity = (Image) image; boolean didUpdate = this.transferFromSpec(entity, updateSpec); final @Nullable Boolean clearAllViewers = updateSpec.getClearAllViewers(); if (clearAllViewers != null && clearAllViewers) { @@ -215,7 +215,7 @@ public class S3ImageService implements ImageService { } else { final @Nullable Set viewersToRemove = updateSpec.getViewersToRemove(); if (viewersToRemove != null) { - final Set currentViewers = new HashSet<>(entity.getViewerEntities()); + final Set currentViewers = new HashSet<>(entity.getViewers()); for (final User toRemove : updateSpec.getViewersToRemove()) { currentViewers.remove((User) toRemove); } @@ -232,7 +232,7 @@ public class S3ImageService implements ImageService { @Override @PreAuthorize("@imageSecurity.isOwner(#image, #modifier)") public void deleteImage(Image image, User modifier) throws IOException { - final S3ImageEntity imageEntity = (S3ImageEntity) image; + final Image imageEntity = (Image) image; this.imageRepository.delete(imageEntity); this.s3Manager.delete("images", imageEntity.getObjectName()); } 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 4d6ba6b..5d00ce4 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/view/ImageView.java +++ b/src/main/java/app/mealsmadeeasy/api/image/view/ImageView.java @@ -20,7 +20,7 @@ public class ImageView { view.setAlt(image.getAlt()); view.setCaption(image.getCaption()); view.setOwner(UserInfoView.from(image.getOwner())); - view.setIsPublic(image.isPublic()); + view.setIsPublic(image.getIsPublic()); view.setHeight(image.getHeight()); view.setWidth(image.getWidth()); if (includeViewers) { diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java b/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java index 4228af2..ca507f2 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java @@ -1,6 +1,6 @@ package app.mealsmadeeasy.api.recipe; -import app.mealsmadeeasy.api.image.S3ImageEntity; +import app.mealsmadeeasy.api.image.Image; import app.mealsmadeeasy.api.recipe.comment.RecipeComment; import app.mealsmadeeasy.api.recipe.star.RecipeStar; import app.mealsmadeeasy.api.user.User; @@ -75,7 +75,7 @@ public final class Recipe { @ManyToOne @JoinColumn(name = "main_image_id") - private S3ImageEntity mainImage; + private Image mainImage; @OneToOne(mappedBy = "recipe", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private RecipeEmbeddingEntity embedding; diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java index 8181a66..6b3a161 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java @@ -3,7 +3,6 @@ package app.mealsmadeeasy.api.recipe; import app.mealsmadeeasy.api.image.Image; import app.mealsmadeeasy.api.image.ImageException; import app.mealsmadeeasy.api.image.ImageService; -import app.mealsmadeeasy.api.image.S3ImageEntity; import app.mealsmadeeasy.api.image.view.ImageView; import app.mealsmadeeasy.api.markdown.MarkdownService; import app.mealsmadeeasy.api.recipe.spec.RecipeAiSearchSpec; @@ -63,7 +62,7 @@ public class RecipeServiceImpl implements RecipeService { draft.setSlug(spec.getSlug()); draft.setTitle(spec.getTitle()); draft.setRawText(spec.getRawText()); - draft.setMainImage((S3ImageEntity) spec.getMainImage()); + draft.setMainImage((Image) spec.getMainImage()); draft.setIsPublic(spec.isPublic()); return this.recipeRepository.save(draft); } @@ -231,11 +230,11 @@ public class RecipeServiceImpl implements RecipeService { recipe.setCachedRenderedText(null); recipe.setIsPublic(spec.getIsPublic()); - final S3ImageEntity mainImage; + final Image mainImage; if (spec.getMainImage() == null) { mainImage = null; } else { - mainImage = (S3ImageEntity) this.imageService.getByUsernameAndFilename( + mainImage = (Image) this.imageService.getByUsernameAndFilename( spec.getMainImage().getUsername(), spec.getMainImage().getFilename(), modifier