diff --git a/backend/src/main/java/com/github/mdeluise/plantit/botanicalinfo/BotanicalInfo.java b/backend/src/main/java/com/github/mdeluise/plantit/botanicalinfo/BotanicalInfo.java index 215988e7b..2c96f14c3 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/botanicalinfo/BotanicalInfo.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/botanicalinfo/BotanicalInfo.java @@ -1,9 +1,8 @@ package com.github.mdeluise.plantit.botanicalinfo; -import com.github.mdeluise.plantit.image.AbstractBotanicalInfoImage; +import com.github.mdeluise.plantit.image.BotanicalInfoImage; +import com.github.mdeluise.plantit.image.ImageTarget; import com.github.mdeluise.plantit.tracked.plant.Plant; -import jakarta.annotation.Nullable; -import jakarta.persistence.CascadeType; import jakarta.persistence.DiscriminatorColumn; import jakarta.persistence.DiscriminatorType; import jakarta.persistence.Entity; @@ -12,7 +11,6 @@ import jakarta.persistence.Id; import jakarta.persistence.Inheritance; import jakarta.persistence.InheritanceType; -import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.validation.constraints.NotBlank; @@ -28,7 +26,7 @@ @DiscriminatorColumn( name = "botanical_info_type", discriminatorType = DiscriminatorType.INTEGER, columnDefinition = "TINYINT(1)" ) -public class BotanicalInfo implements Serializable { +public class BotanicalInfo implements Serializable, ImageTarget { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @@ -40,10 +38,9 @@ public class BotanicalInfo implements Serializable { @NotNull @OneToMany(mappedBy = "botanicalInfo") private Set plants = new HashSet<>(); - @Nullable - @OneToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "image_id", referencedColumnName = "id") - private AbstractBotanicalInfoImage image; + @NotNull + @OneToOne(mappedBy = "botanicalInfoImageTarget") + private BotanicalInfoImage image; public Long getId() { @@ -106,12 +103,12 @@ public void setPlants(Set plants) { } - public AbstractBotanicalInfoImage getImage() { + public BotanicalInfoImage getImage() { return image; } - public void setImage(AbstractBotanicalInfoImage image) { + public void setImage(BotanicalInfoImage image) { this.image = image; } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/botanicalinfo/BotanicalInfoDTOConverter.java b/backend/src/main/java/com/github/mdeluise/plantit/botanicalinfo/BotanicalInfoDTOConverter.java index 7e5423909..3ec3a4852 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/botanicalinfo/BotanicalInfoDTOConverter.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/botanicalinfo/BotanicalInfoDTOConverter.java @@ -1,8 +1,6 @@ package com.github.mdeluise.plantit.botanicalinfo; import com.github.mdeluise.plantit.common.AbstractDTOConverter; -import com.github.mdeluise.plantit.image.LocalBotanicalInfoImage; -import com.github.mdeluise.plantit.image.WebBotanicalInfoImage; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -27,11 +25,6 @@ public BotanicalInfo convertFromDTO(BotanicalInfoDTO dto) { public BotanicalInfoDTO convertToDTO(BotanicalInfo data) { final BotanicalInfoDTO result = modelMapper.map(data, BotanicalInfoDTO.class); result.setSystemWide(!(data instanceof UserCreatedBotanicalInfo)); - if (data.getImage() instanceof WebBotanicalInfoImage w) { - result.setImageUrl(w.getUrl()); - } else if (data.getImage() instanceof LocalBotanicalInfoImage l) { - result.setImageId(l.getId()); - } return result; } } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/exception/ControllerExceptionHandler.java b/backend/src/main/java/com/github/mdeluise/plantit/exception/ControllerExceptionHandler.java index 183ce237f..b25042bcd 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/exception/ControllerExceptionHandler.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/exception/ControllerExceptionHandler.java @@ -22,7 +22,7 @@ public ResponseEntity entityNotFoundException( new Date(), ex.getMessage(), request.getDescription(false), - ex.getCause() != null ? ex.getCause().getMessage() : "" + ex.getCause() != null ? ex.getCause().getMessage() : null ); return new ResponseEntity<>(message, HttpStatus.NOT_FOUND); } @@ -35,7 +35,7 @@ public ResponseEntity globalExceptionHandler(Exception ex, WebRequ new Date(), ex.getMessage(), request.getDescription(false), - ex.getCause() != null ? ex.getCause().getMessage() : "" + ex.getCause() != null ? ex.getCause().getMessage() : null ); return new ResponseEntity<>(message, HttpStatus.INTERNAL_SERVER_ERROR); } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/AbstractBotanicalInfoImage.java b/backend/src/main/java/com/github/mdeluise/plantit/image/AbstractBotanicalInfoImage.java deleted file mode 100644 index ceb14643c..000000000 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/AbstractBotanicalInfoImage.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.github.mdeluise.plantit.image; - -import com.github.mdeluise.plantit.botanicalinfo.BotanicalInfo; -import jakarta.persistence.Entity; -import jakarta.persistence.OneToOne; - -@Entity -public abstract class AbstractBotanicalInfoImage extends AbstractImage { - @OneToOne(mappedBy = "image") - private BotanicalInfo botanicalInfo; - - - public BotanicalInfo getBotanicalName() { - return botanicalInfo; - } - - - public void setBotanicalName(BotanicalInfo botanicalInfo) { - this.botanicalInfo = botanicalInfo; - } -} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/AbstractImage.java b/backend/src/main/java/com/github/mdeluise/plantit/image/AbstractEntityImage.java similarity index 73% rename from backend/src/main/java/com/github/mdeluise/plantit/image/AbstractImage.java rename to backend/src/main/java/com/github/mdeluise/plantit/image/AbstractEntityImage.java index b9eaad853..547bef9cb 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/AbstractImage.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/AbstractEntityImage.java @@ -8,18 +8,19 @@ import jakarta.persistence.Id; import jakarta.persistence.Inheritance; import jakarta.persistence.InheritanceType; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import org.hibernate.validator.constraints.Length; import java.io.Serializable; import java.util.Date; -@Entity(name = "abstract_images") +@Entity(name = "entity_images") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn( name = "image_type", discriminatorType = DiscriminatorType.INTEGER, columnDefinition = "TINYINT(1)" ) -public abstract class AbstractImage implements Serializable { +public abstract class AbstractEntityImage implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @@ -27,6 +28,10 @@ public abstract class AbstractImage implements Serializable { private String description; @NotNull private Date savedAt = new Date(); + @NotBlank + @Length(max = 255) + private String url; + private String path; public Long getId() { @@ -57,4 +62,24 @@ public Date getSavedAt() { public void setSavedAt(Date savedAt) { this.savedAt = savedAt; } + + + public String getPath() { + return path; + } + + + public void setPath(String path) { + this.path = path; + } + + + public String getUrl() { + return url; + } + + + public void setUrl(String url) { + this.url = url; + } } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/BotanicalInfoImage.java b/backend/src/main/java/com/github/mdeluise/plantit/image/BotanicalInfoImage.java new file mode 100644 index 000000000..d5e90bf91 --- /dev/null +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/BotanicalInfoImage.java @@ -0,0 +1,27 @@ +package com.github.mdeluise.plantit.image; + +import com.github.mdeluise.plantit.botanicalinfo.BotanicalInfo; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.validation.constraints.NotNull; + +@Entity +@DiscriminatorValue("1") +public class BotanicalInfoImage extends AbstractEntityImage { + @NotNull + @OneToOne + @JoinColumn(name = "entity_id", nullable = false) + private BotanicalInfo botanicalInfoImageTarget; + + + public BotanicalInfo getBotanicalInfoImageTarget() { + return botanicalInfoImageTarget; + } + + + public void setBotanicalInfoImageTarget(BotanicalInfo botanicalInfoImageTarget) { + this.botanicalInfoImageTarget = botanicalInfoImageTarget; + } +} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageController.java b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageController.java index 07675ea79..f8545924b 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageController.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageController.java @@ -1,6 +1,14 @@ package com.github.mdeluise.plantit.image; +import com.github.mdeluise.plantit.botanicalinfo.BotanicalInfo; +import com.github.mdeluise.plantit.botanicalinfo.BotanicalInfoService; +import com.github.mdeluise.plantit.image.storage.StorageService; +import com.github.mdeluise.plantit.tracked.TrackedEntityService; +import com.github.mdeluise.plantit.tracked.plant.Plant; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -11,55 +19,73 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; +import java.util.Base64; import java.util.Collection; @RestController @RequestMapping("/image") public class ImageController { - private final ImageService imageService; + private final StorageService storageService; + private final BotanicalInfoService botanicalInfoService; + private final TrackedEntityService trackedEntityService; private final ImageDTOConverter imageDtoConverter; @Autowired - public ImageController(ImageService imageService, ImageDTOConverter imageDtoConverter) { - this.imageService = imageService; + public ImageController(StorageService storageService, BotanicalInfoService botanicalInfoService, + TrackedEntityService trackedEntityService, ImageDTOConverter imageDtoConverter) { + this.storageService = storageService; + this.botanicalInfoService = botanicalInfoService; + this.trackedEntityService = trackedEntityService; this.imageDtoConverter = imageDtoConverter; } - @PostMapping("/botanical-info") - public ResponseEntity saveBotanicalInfoImage(@RequestParam("image") MultipartFile file) throws IOException { - final AbstractImage saved = imageService.saveBotanicalInfoImage(file); - return ResponseEntity.ok(saved.getId()); + @PostMapping("/botanical-info/{id}") + public ResponseEntity saveBotanicalInfoImage(@RequestParam("image") MultipartFile file, + @PathVariable("id") Long id) { + final BotanicalInfo linkedEntity = botanicalInfoService.get(id); + storageService.save(file, linkedEntity); + return ResponseEntity.ok("Successfully uploaded"); } @GetMapping("/{id}") public ResponseEntity get(@PathVariable("id") Long id) { - final AbstractImage result = imageService.get(id); + final AbstractEntityImage result = storageService.get(id); return ResponseEntity.ok(imageDtoConverter.convertToDTO(result)); } - @DeleteMapping("/botanical-info/{id}") + @GetMapping("/content/{id}") + public ResponseEntity getContent(@PathVariable("id") Long id) { + final byte[] result = Base64.getEncoder().encode(storageService.getContent(id)); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.IMAGE_JPEG); + return new ResponseEntity<>(result, headers, HttpStatus.OK); + } + + + @DeleteMapping("/{id}") public ResponseEntity delete(@PathVariable("id") Long id) { - imageService.delete(id); + storageService.remove(id); return ResponseEntity.ok("Success"); } @PostMapping("/entity/{id}") public ResponseEntity saveEntityImage(@RequestParam("image") MultipartFile file, - @PathVariable("id") Long entityId) throws IOException { - final AbstractImage saved = imageService.saveEntityImage(file, entityId); + @PathVariable("id") Long entityId) { + final Plant linkedEntity = (Plant) trackedEntityService.get(entityId); + final AbstractEntityImage saved = storageService.save(file, linkedEntity); return ResponseEntity.ok(saved.getId()); } - @GetMapping("/all/{id}") + @GetMapping("/entity/all/{id}") public ResponseEntity> getAllImageIdsFromEntity(@PathVariable("id") Long id) { - final Collection result = imageService.getAllIds(id); + final Plant linkedEntity = (Plant) trackedEntityService.get(id); + final Collection result = storageService.getAllIds(linkedEntity); return ResponseEntity.ok(result); } } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageDTO.java b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageDTO.java index f7ce3d6ea..5a6078a93 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageDTO.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageDTO.java @@ -6,7 +6,7 @@ public class ImageDTO { private Long id; private Date savedAt; private String url; - private byte[] content; + private String description; public Long getId() { @@ -39,12 +39,12 @@ public void setUrl(String url) { } - public byte[] getContent() { - return content; + public String getDescription() { + return description; } - public void setContent(byte[] content) { - this.content = content; + public void setDescription(String description) { + this.description = description; } } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageDTOConverter.java b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageDTOConverter.java index bb80a27a4..8baa52804 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageDTOConverter.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageDTOConverter.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Component; @Component -public class ImageDTOConverter extends AbstractDTOConverter { +public class ImageDTOConverter extends AbstractDTOConverter { @Autowired public ImageDTOConverter(ModelMapper modelMapper) { super(modelMapper); @@ -14,13 +14,13 @@ public ImageDTOConverter(ModelMapper modelMapper) { @Override - public AbstractImage convertFromDTO(ImageDTO dto) { - return modelMapper.map(dto, AbstractImage.class); + public AbstractEntityImage convertFromDTO(ImageDTO dto) { + return modelMapper.map(dto, AbstractEntityImage.class); } @Override - public ImageDTO convertToDTO(AbstractImage data) { + public ImageDTO convertToDTO(AbstractEntityImage data) { return modelMapper.map(data, ImageDTO.class); } } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageRepository.java b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageRepository.java index 910447705..2a78547be 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageRepository.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageRepository.java @@ -2,5 +2,5 @@ import org.springframework.data.jpa.repository.JpaRepository; -public interface ImageRepository extends JpaRepository { +public interface ImageRepository extends JpaRepository { } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageService.java b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageService.java index 55b0b256f..e619c3f5e 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageService.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageService.java @@ -1,7 +1,9 @@ package com.github.mdeluise.plantit.image; import com.github.mdeluise.plantit.exception.ResourceNotFoundException; +import com.github.mdeluise.plantit.tracked.PlantImage; import com.github.mdeluise.plantit.tracked.TrackedEntityService; +import com.github.mdeluise.plantit.tracked.plant.Plant; import jakarta.transaction.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,30 +15,24 @@ import java.io.File; import java.io.IOException; -import java.util.Collection; -import java.util.List; @Service public class ImageService { private final ImageRepository imageRepository; - private final TrackedEntityImageRepository trackedEntityImageRepository; private final TrackedEntityService trackedEntityService; private final Logger logger = LoggerFactory.getLogger(ImageService.class); @Autowired - public ImageService(ImageRepository imageRepository, TrackedEntityImageRepository trackedEntityImageRepository, + public ImageService(ImageRepository imageRepository, TrackedEntityService trackedEntityService) { this.imageRepository = imageRepository; - this.trackedEntityImageRepository = trackedEntityImageRepository; this.trackedEntityService = trackedEntityService; } - public AbstractImage saveBotanicalInfoImage(MultipartFile file) throws IOException { - LocalBotanicalInfoImage localBotanicalInfoImage = new LocalBotanicalInfoImage(); - byte[] content = getBytes(file); - localBotanicalInfoImage.setContent(content); + public AbstractEntityImage saveBotanicalInfoImage(MultipartFile file) throws IOException { + AbstractEntityImage localBotanicalInfoImage = new BotanicalInfoImage(); return imageRepository.save(localBotanicalInfoImage); } @@ -45,7 +41,7 @@ public AbstractImage saveBotanicalInfoImage(MultipartFile file) throws IOExcepti value = "image", key = "{#id}" ) - public AbstractImage get(@PathVariable("id") Long id) { + public AbstractEntityImage get(@PathVariable("id") Long id) { return imageRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(id)); } @@ -59,12 +55,12 @@ public void delete(Long id) { @Transactional - public AbstractImage saveEntityImage(MultipartFile file, Long entityId) throws IOException { - TrackedEntityImage trackedEntityImage = new TrackedEntityImage(); - trackedEntityImage.setAbstractTrackedEntity(trackedEntityService.get(entityId)); + public AbstractEntityImage saveEntityImage(MultipartFile file, Long entityId) throws IOException { + PlantImage trackedAbstractEntityImage = new PlantImage(); + trackedAbstractEntityImage.setPlantImageTarget((Plant) trackedEntityService.get(entityId)); byte[] content = getBytes(file); - trackedEntityImage.setContent(content); - return imageRepository.save(trackedEntityImage); + //trackedAbstractEntityImage.setContent(content); + return imageRepository.save(trackedAbstractEntityImage); } @@ -81,11 +77,4 @@ private byte[] getBytes(MultipartFile file) throws IOException { } return content; } - - - public Collection getAllIds(Long trackedEntityId) { - final List resultIds = - trackedEntityImageRepository.findAllByAbstractTrackedEntity(trackedEntityService.get(trackedEntityId)); - return resultIds.stream().map(TrackedEntityImageRepository.TrackedEntityImageIdView::getId).toList(); - } } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/ImageTarget.java b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageTarget.java new file mode 100644 index 000000000..ee89c2ec9 --- /dev/null +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/ImageTarget.java @@ -0,0 +1,5 @@ +package com.github.mdeluise.plantit.image; + +// Marker interface +public interface ImageTarget { +} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/LocalBotanicalInfoImage.java b/backend/src/main/java/com/github/mdeluise/plantit/image/LocalBotanicalInfoImage.java deleted file mode 100644 index ed1f01c77..000000000 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/LocalBotanicalInfoImage.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.mdeluise.plantit.image; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.Lob; -import jakarta.validation.constraints.Size; - -@Entity -@DiscriminatorValue("2") -public class LocalBotanicalInfoImage extends AbstractBotanicalInfoImage { - @Size(max = 20000000) // 20MB - @Lob - private byte[] content; - - - public byte[] getContent() { - return content; - } - - - public void setContent(byte[] content) { - this.content = content; - } -} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/PlantImageRepository.java b/backend/src/main/java/com/github/mdeluise/plantit/image/PlantImageRepository.java new file mode 100644 index 000000000..2942522a4 --- /dev/null +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/PlantImageRepository.java @@ -0,0 +1,16 @@ +package com.github.mdeluise.plantit.image; + +import com.github.mdeluise.plantit.tracked.PlantImage; +import com.github.mdeluise.plantit.tracked.plant.Plant; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface PlantImageRepository extends JpaRepository { + // Bugged due to the inheritance + //List findAllByPlantImageTarget(Plant target); + + @Query("SELECT i.id from PlantImage i where i.plantImageTarget = ?1") + List findAllIdsPlantByImageTarget(Plant target); +} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/TrackedEntityImage.java b/backend/src/main/java/com/github/mdeluise/plantit/image/TrackedEntityImage.java deleted file mode 100644 index 65de05a5d..000000000 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/TrackedEntityImage.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.github.mdeluise.plantit.image; - -import com.github.mdeluise.plantit.tracked.AbstractTrackedEntity; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.Lob; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToOne; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; - -@Entity -@DiscriminatorValue("3") -public class TrackedEntityImage extends AbstractImage { - @NotNull - @ManyToOne - @JoinColumn(name = "entity_id", nullable = false) - private AbstractTrackedEntity abstractTrackedEntity; - @OneToOne(mappedBy = "thumbnailImage") - private AbstractTrackedEntity thumbnailOf; - @Size(max = 20000000) // 20MB - @Lob - private byte[] content; - - - public AbstractTrackedEntity getAbstractTrackedEntity() { - return abstractTrackedEntity; - } - - - public void setAbstractTrackedEntity(AbstractTrackedEntity abstractTrackedEntity) { - this.abstractTrackedEntity = abstractTrackedEntity; - } - - - public AbstractTrackedEntity getThumbnailOf() { - return thumbnailOf; - } - - - public void setThumbnailOf(AbstractTrackedEntity thumbnailOf) { - this.thumbnailOf = thumbnailOf; - } - - - public byte[] getContent() { - return content; - } - - - public void setContent(byte[] content) { - this.content = content; - } -} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/TrackedEntityImageRepository.java b/backend/src/main/java/com/github/mdeluise/plantit/image/TrackedEntityImageRepository.java deleted file mode 100644 index 62a40887e..000000000 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/TrackedEntityImageRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.mdeluise.plantit.image; - -import com.github.mdeluise.plantit.tracked.AbstractTrackedEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface TrackedEntityImageRepository extends JpaRepository { - - interface TrackedEntityImageIdView { - Long getId(); - } - - List findAllByAbstractTrackedEntity(AbstractTrackedEntity abstractTrackedEntity); -} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/WebBotanicalInfoImage.java b/backend/src/main/java/com/github/mdeluise/plantit/image/WebBotanicalInfoImage.java deleted file mode 100644 index 63cc02ed2..000000000 --- a/backend/src/main/java/com/github/mdeluise/plantit/image/WebBotanicalInfoImage.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.mdeluise.plantit.image; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import org.hibernate.validator.constraints.Length; - -@Entity -@DiscriminatorValue("1") -public class WebBotanicalInfoImage extends AbstractBotanicalInfoImage { - @Length(max = 255) - private String url; - - - public String getUrl() { - return url; - } - - - public void setUrl(String url) { - this.url = url; - } -} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/storage/FileSystemStorageService.java b/backend/src/main/java/com/github/mdeluise/plantit/image/storage/FileSystemStorageService.java new file mode 100644 index 000000000..17ca658d0 --- /dev/null +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/storage/FileSystemStorageService.java @@ -0,0 +1,136 @@ +package com.github.mdeluise.plantit.image.storage; + +import com.github.mdeluise.plantit.botanicalinfo.BotanicalInfo; +import com.github.mdeluise.plantit.exception.ResourceNotFoundException; +import com.github.mdeluise.plantit.image.AbstractEntityImage; +import com.github.mdeluise.plantit.image.BotanicalInfoImage; +import com.github.mdeluise.plantit.image.ImageRepository; +import com.github.mdeluise.plantit.image.ImageTarget; +import com.github.mdeluise.plantit.image.PlantImageRepository; +import com.github.mdeluise.plantit.tracked.PlantImage; +import com.github.mdeluise.plantit.tracked.plant.Plant; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; +import org.springframework.util.FileSystemUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.UUID; + +@Service +public class FileSystemStorageService implements StorageService { + private final String rootLocation; + private final ImageRepository imageRepository; + private final PlantImageRepository plantImageRepository; + + + @Autowired + public FileSystemStorageService(StorageProperties properties, ImageRepository imageRepository, + PlantImageRepository plantImageRepository) { + this.rootLocation = properties.getLocation(); + this.imageRepository = imageRepository; + this.plantImageRepository = plantImageRepository; + } + + + @Override + public AbstractEntityImage save(MultipartFile file, ImageTarget linkedEntity) { + try { + if (file.isEmpty()) { + throw new StorageException("Failed to save empty file."); + } + final UUID uuid = UUID.randomUUID(); + final String fileExtension = file.getContentType().split("/")[1]; + final String uploadDir = getClass().getClassLoader().getResource(rootLocation).getPath(); + final String fileName = String.format("%s/%s.%s", uploadDir, uuid, fileExtension); + final Path pathToFile = Path.of(fileName); + try (InputStream inputStream = file.getInputStream()) { + Files.copy(inputStream, pathToFile); + AbstractEntityImage abstractEntityImage; + if (linkedEntity instanceof BotanicalInfo b) { + abstractEntityImage = new BotanicalInfoImage(); + ((BotanicalInfoImage) abstractEntityImage).setBotanicalInfoImageTarget(b); + } else if (linkedEntity instanceof Plant p) { + abstractEntityImage = new PlantImage(); + ((PlantImage) abstractEntityImage).setPlantImageTarget(p); + } else { + throw new UnsupportedOperationException("Could not find suitable class for linkedEntity"); + } + abstractEntityImage.setUrl(getBaseUrl() + uuid); + abstractEntityImage.setPath(pathToFile.toString()); + return imageRepository.save(abstractEntityImage); + } + } catch (IOException e) { + throw new StorageException("Failed to save file.", e); + } + } + + + @Override + public AbstractEntityImage get(Long id) { + return imageRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(id)); + } + + + @Override + public byte[] getContent(Long id) { + try { + final File entityImageFile = new ClassPathResource(get(id).getPath()).getFile(); + if (!entityImageFile.exists() || !entityImageFile.canRead()) { + throw new StorageFileNotFoundException("Could not read image with id: " + id); + } + return Files.readAllBytes(entityImageFile.toPath()); + } catch (IOException e) { + throw new StorageFileNotFoundException("Could not read image with id: " + id, e); + } + } + + + @Override + public void removeAll() { + FileSystemUtils.deleteRecursively(Path.of(rootLocation).toFile()); + imageRepository.deleteAll(); + } + + + @Override + public void remove(Long id) { + final String entityImagePath = get(id).getPath(); + final File toRemove = new File(entityImagePath); + if (!toRemove.exists() || !toRemove.canRead()) { + throw new StorageFileNotFoundException("Could not read image with id: " + id); + } + if (!toRemove.delete()) { + throw new StorageException("Could not remove image with id " + id); + } + imageRepository.deleteById(id); + } + + + @Override + public void init() { + try { + Files.createDirectories(Path.of(rootLocation)); + } catch (IOException e) { + throw new StorageException("Could not initialize storage", e); + } + } + + + @Override + public Collection getAllIds(ImageTarget linkedEntity) { + return plantImageRepository.findAllIdsPlantByImageTarget((Plant) linkedEntity); + } + + + private String getBaseUrl() { + //return ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString(); + return "/"; + } +} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageException.java b/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageException.java new file mode 100644 index 000000000..a47d4c50f --- /dev/null +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageException.java @@ -0,0 +1,12 @@ +package com.github.mdeluise.plantit.image.storage; + +public class StorageException extends RuntimeException { + public StorageException(String message) { + super(message); + } + + + public StorageException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageFileNotFoundException.java b/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageFileNotFoundException.java new file mode 100644 index 000000000..2d61d6a51 --- /dev/null +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageFileNotFoundException.java @@ -0,0 +1,12 @@ +package com.github.mdeluise.plantit.image.storage; + +public class StorageFileNotFoundException extends StorageException { + public StorageFileNotFoundException(String message) { + super(message); + } + + + public StorageFileNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageProperties.java b/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageProperties.java new file mode 100644 index 000000000..6b10a2ce1 --- /dev/null +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageProperties.java @@ -0,0 +1,18 @@ +package com.github.mdeluise.plantit.image.storage; + +import org.springframework.stereotype.Component; + +@Component +public class StorageProperties { + private String location = "upload-dir"; + + + public String getLocation() { + return location; + } + + + public void setLocation(String location) { + this.location = location; + } +} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageService.java b/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageService.java new file mode 100644 index 000000000..928ec7df6 --- /dev/null +++ b/backend/src/main/java/com/github/mdeluise/plantit/image/storage/StorageService.java @@ -0,0 +1,23 @@ +package com.github.mdeluise.plantit.image.storage; + +import com.github.mdeluise.plantit.image.AbstractEntityImage; +import com.github.mdeluise.plantit.image.ImageTarget; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Collection; + +public interface StorageService { + void init(); + + AbstractEntityImage save(MultipartFile file, ImageTarget linkedEntity); + + AbstractEntityImage get(Long id); + + byte[] getContent(Long id); + + void remove(Long id); + + void removeAll(); + + Collection getAllIds(ImageTarget linkedEntity); +} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/plantinfo/trafle/TrefleRequestMaker.java b/backend/src/main/java/com/github/mdeluise/plantit/plantinfo/trafle/TrefleRequestMaker.java index 5590d76e0..eb9ef0462 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/plantinfo/trafle/TrefleRequestMaker.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/plantinfo/trafle/TrefleRequestMaker.java @@ -2,7 +2,7 @@ import com.github.mdeluise.plantit.botanicalinfo.BotanicalInfo; import com.github.mdeluise.plantit.exception.InfoExtractionException; -import com.github.mdeluise.plantit.image.WebBotanicalInfoImage; +import com.github.mdeluise.plantit.image.BotanicalInfoImage; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -120,18 +120,9 @@ public Page fetchAll(Pageable pageable) { private void fillImage(BotanicalInfo botanicalInfo, String imageUrl) throws InfoExtractionException { - // AbstractImage image; - // try { - // image = imageService.download(imageUrl); - // } catch (IOException e) { - // throw new InfoExtractionException(e); - // } catch (URISyntaxException e) { - // throw new RuntimeException(e); - // } - // botanicalInfo.setImage((AbstractBotanicalInfoImage) image); - final WebBotanicalInfoImage webBotanicalInfoImage = new WebBotanicalInfoImage(); - webBotanicalInfoImage.setUrl(imageUrl); - botanicalInfo.setImage(webBotanicalInfoImage); + final BotanicalInfoImage abstractEntityImage = new BotanicalInfoImage(); + abstractEntityImage.setUrl(imageUrl); + botanicalInfo.setImage(abstractEntityImage); } } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/tracked/AbstractTrackedEntity.java b/backend/src/main/java/com/github/mdeluise/plantit/tracked/AbstractTrackedEntity.java index 222cd5029..4f246fbdc 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/tracked/AbstractTrackedEntity.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/tracked/AbstractTrackedEntity.java @@ -2,7 +2,6 @@ import com.github.mdeluise.plantit.authentication.User; import com.github.mdeluise.plantit.diary.Diary; -import com.github.mdeluise.plantit.image.TrackedEntityImage; import com.github.mdeluise.plantit.tracked.plant.Plant; import com.github.mdeluise.plantit.tracked.state.State; import jakarta.persistence.CascadeType; @@ -18,7 +17,6 @@ import jakarta.persistence.InheritanceType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -26,8 +24,6 @@ import java.io.Serializable; import java.util.Date; -import java.util.HashSet; -import java.util.Set; @Entity(name = "tracked_entities") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @@ -55,11 +51,6 @@ public abstract class AbstractTrackedEntity implements Serializable { @ManyToOne @JoinColumn(name = "owner_id", nullable = false) private User owner; - @OneToMany(mappedBy = "abstractTrackedEntity") - private Set images = new HashSet<>(); - @OneToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "thumbnail_image_id", referencedColumnName = "id") - private TrackedEntityImage thumbnailImage; public AbstractTrackedEntity() { @@ -166,29 +157,4 @@ public User getOwner() { public void setOwner(User owner) { this.owner = owner; } - - - public Set getImages() { - return images; - } - - - public void setImages(Set images) { - this.images = images; - } - - - public void addImage(TrackedEntityImage image) { - images.add(image); - } - - - public TrackedEntityImage getThumbnailImage() { - return thumbnailImage; - } - - - public void setThumbnailImage(TrackedEntityImage thumbnailImage) { - this.thumbnailImage = thumbnailImage; - } } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/tracked/PlantImage.java b/backend/src/main/java/com/github/mdeluise/plantit/tracked/PlantImage.java new file mode 100644 index 000000000..6a9ba1ec4 --- /dev/null +++ b/backend/src/main/java/com/github/mdeluise/plantit/tracked/PlantImage.java @@ -0,0 +1,28 @@ +package com.github.mdeluise.plantit.tracked; + +import com.github.mdeluise.plantit.image.AbstractEntityImage; +import com.github.mdeluise.plantit.tracked.plant.Plant; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.validation.constraints.NotNull; + +@Entity +@DiscriminatorValue("2") +public class PlantImage extends AbstractEntityImage { + @NotNull + @ManyToOne + @JoinColumn(name = "entity_id", nullable = false) + private Plant plantImageTarget; + + + public Plant getPlantImageTarget() { + return plantImageTarget; + } + + + public void setPlantImageTarget(Plant plantImageTarget) { + this.plantImageTarget = plantImageTarget; + } +} diff --git a/backend/src/main/java/com/github/mdeluise/plantit/tracked/plant/Plant.java b/backend/src/main/java/com/github/mdeluise/plantit/tracked/plant/Plant.java index 26489d630..6f8def463 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/tracked/plant/Plant.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/tracked/plant/Plant.java @@ -1,18 +1,24 @@ package com.github.mdeluise.plantit.tracked.plant; import com.github.mdeluise.plantit.botanicalinfo.BotanicalInfo; +import com.github.mdeluise.plantit.image.ImageTarget; import com.github.mdeluise.plantit.tracked.AbstractTrackedEntity; +import com.github.mdeluise.plantit.tracked.PlantImage; import com.github.mdeluise.plantit.tracked.arrangement.Arrangement; import jakarta.persistence.CascadeType; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.validation.constraints.NotNull; +import java.util.HashSet; +import java.util.Set; + @Entity @DiscriminatorValue("1") -public class Plant extends AbstractTrackedEntity { +public class Plant extends AbstractTrackedEntity implements ImageTarget { @NotNull @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "botanical_name_id", nullable = false) @@ -20,6 +26,9 @@ public class Plant extends AbstractTrackedEntity { @ManyToOne @JoinColumn(name = "arrangement_id") private Arrangement arrangement; + @NotNull + @OneToMany(mappedBy = "plantImageTarget") + private Set images = new HashSet<>(); public Plant() { @@ -45,4 +54,14 @@ public Arrangement getArrangement() { public void setArrangement(Arrangement arrangement) { this.arrangement = arrangement; } + + + public Set getImages() { + return images; + } + + + public void setImages(Set images) { + this.images = images; + } } diff --git a/backend/src/main/java/com/github/mdeluise/plantit/tracked/plant/PlantDTOConverter.java b/backend/src/main/java/com/github/mdeluise/plantit/tracked/plant/PlantDTOConverter.java index 01c3474ea..8403d3dc0 100644 --- a/backend/src/main/java/com/github/mdeluise/plantit/tracked/plant/PlantDTOConverter.java +++ b/backend/src/main/java/com/github/mdeluise/plantit/tracked/plant/PlantDTOConverter.java @@ -1,94 +1,28 @@ package com.github.mdeluise.plantit.tracked.plant; -import com.github.mdeluise.plantit.botanicalinfo.BotanicalInfo; -import com.github.mdeluise.plantit.botanicalinfo.BotanicalInfoDTO; -import com.github.mdeluise.plantit.botanicalinfo.BotanicalInfoDTOConverter; -import com.github.mdeluise.plantit.botanicalinfo.GlobalBotanicalInfo; -import com.github.mdeluise.plantit.botanicalinfo.UserCreatedBotanicalInfo; import com.github.mdeluise.plantit.common.AbstractDTOConverter; -import com.github.mdeluise.plantit.image.AbstractBotanicalInfoImage; -import com.github.mdeluise.plantit.image.ImageService; -import com.github.mdeluise.plantit.image.WebBotanicalInfoImage; -import org.modelmapper.Converter; import org.modelmapper.ModelMapper; -import org.modelmapper.spi.MappingContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -/* - * This class is a mess. It's this way because in BotanicalInfo there is the field image of type - * AbstractBotanicalInfoImage. AbstractBotanicalInfoImage is an abstract class, and modelMapper throw error - * converting Plant <-> PlantDTO because it trys to instantiate AbstractBotanicalInfoImage. - * Thus, this very bad workaround. - */ @Component public class PlantDTOConverter extends AbstractDTOConverter { - private final BotanicalInfoDTOConverter botanicalInfoDtoConverter; - private final ImageService imageService; @Autowired - public PlantDTOConverter(ModelMapper modelMapper, BotanicalInfoDTOConverter botanicalInfoDtoConverter, - ImageService imageService) { + public PlantDTOConverter(ModelMapper modelMapper) { super(modelMapper); - this.botanicalInfoDtoConverter = botanicalInfoDtoConverter; - this.imageService = imageService; - - modelMapper.getConfiguration().setSkipNullEnabled(true); } @Override public Plant convertFromDTO(PlantDTO dto) { - modelMapper.typeMap(BotanicalInfoDTO.class, BotanicalInfo.class) - .setConverter(new BotanicalInfoConverter()); - - if (dto.getBotanicalInfo().getId() == null) { - modelMapper.typeMap(BotanicalInfoDTO.class, UserCreatedBotanicalInfo.class) - .addMappings(mapper -> mapper.skip(BotanicalInfo::setId)); - } else { - modelMapper.typeMap(BotanicalInfoDTO.class, GlobalBotanicalInfo.class); - } - return modelMapper.map(dto, Plant.class); } @Override public PlantDTO convertToDTO(Plant data) { - final PlantDTO result = modelMapper.map(data, PlantDTO.class); - result.setBotanicalInfo(botanicalInfoDtoConverter.convertToDTO(data.getBotanicalInfo())); - return result; - } - - - private class BotanicalInfoConverter implements Converter { - @Override - public BotanicalInfo convert(MappingContext context) { - final BotanicalInfoDTO botanicalInfoDTO = context.getSource(); - Class clazz = botanicalInfoDTO.isSystemWide() ? - GlobalBotanicalInfo.class : UserCreatedBotanicalInfo.class; - - final BotanicalInfo botanicalInfo; - try { - botanicalInfo = clazz.getConstructor().newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - botanicalInfo.setScientificName(botanicalInfoDTO.getScientificName()); - botanicalInfo.setFamily(botanicalInfoDTO.getFamily()); - botanicalInfo.setGenus(botanicalInfoDTO.getGenus()); - botanicalInfo.setId(botanicalInfoDTO.getId()); - botanicalInfo.setSpecies(botanicalInfoDTO.getSpecies()); - if (botanicalInfoDTO.getImageId() != null) { - botanicalInfo.setImage((AbstractBotanicalInfoImage) imageService.get(botanicalInfoDTO.getImageId())); - } else if (botanicalInfoDTO.getImageUrl() != null) { - WebBotanicalInfoImage webBotanicalInfoImage = new WebBotanicalInfoImage(); - webBotanicalInfoImage.setUrl(botanicalInfoDTO.getImageUrl()); - botanicalInfo.setImage(webBotanicalInfoImage); - } - return botanicalInfo; - } + return modelMapper.map(data, PlantDTO.class); } } diff --git a/backend/src/main/resources/dblogs/changelog/changes/changelog-0.xml b/backend/src/main/resources/dblogs/changelog/changes/changelog-0.xml index a653de140..42bffda1a 100644 --- a/backend/src/main/resources/dblogs/changelog/changes/changelog-0.xml +++ b/backend/src/main/resources/dblogs/changelog/changes/changelog-0.xml @@ -130,13 +130,13 @@ - + - + - + @@ -144,6 +144,7 @@ + @@ -151,8 +152,9 @@ + - + @@ -178,7 +180,7 @@ - + diff --git a/backend/src/main/resources/dblogs/changelog/init-dummy-data.xml b/backend/src/main/resources/dblogs/changelog/init-dummy-data.xml index 6a83f77d6..6ea65ad94 100644 --- a/backend/src/main/resources/dblogs/changelog/init-dummy-data.xml +++ b/backend/src/main/resources/dblogs/changelog/init-dummy-data.xml @@ -11,29 +11,6 @@ - - - - - - - - - - - - - - - - - - - - - - @@ -41,7 +18,7 @@ - + @@ -50,7 +27,7 @@ - + @@ -59,7 +36,7 @@ - + @@ -110,6 +87,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id = '99' + + + + id = '98' + + + + id = '97' + + + + diff --git a/backend/src/main/resources/images/dummy-image.jpg b/backend/src/main/resources/upload-dir/dummy-image.jpg similarity index 100% rename from backend/src/main/resources/images/dummy-image.jpg rename to backend/src/main/resources/upload-dir/dummy-image.jpg diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b6fc0f112..f729a97ef 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -17,7 +17,7 @@ export function App() { const backendURL = window._env_.REACT_APP_API_URL != null ? window._env_.REACT_APP_API_URL : "http://localhost:8085/api"; const axiosReq = axios.create({ baseURL: backendURL, - timeout: 5000 + //timeout: 5000 }); axiosReq.interceptors.request.use( diff --git a/frontend/src/common.ts b/frontend/src/common.ts index ce31a1f05..2262a919a 100644 --- a/frontend/src/common.ts +++ b/frontend/src/common.ts @@ -1,7 +1,32 @@ +import { AxiosInstance } from "axios"; + export const isBigScreen = (): boolean => { return window.screen.width > 768; -} +}; export const titleCase = (string: string): string => { return string.charAt(0) + string.substring(1).toLowerCase(); +}; + +const readImage = (requestor: AxiosInstance, imageUrl: string): Promise => { + return new Promise((resolve, reject) => { + requestor.get(`image/content${imageUrl}`) + .then((res) => { + resolve(`data:application/octet-stream;base64,${res.data}`); + }); + }); +}; + +const setAbsoluteImageUrl = (requestor: AxiosInstance, publicUrl: string, imageUrl?: string): Promise => { + if (imageUrl == undefined) { + return new Promise((resolve, _reject) => resolve(`${publicUrl}botanical-info-no-img.png`)); + } + if (imageUrl.startsWith("/")) { + return readImage(requestor, imageUrl); + } + return new Promise((resolve, _reject) => resolve(imageUrl)); +}; + +export const getBotanicalInfoImg = (requestor: AxiosInstance, imageUrl?: string): Promise => { + return setAbsoluteImageUrl(requestor, process.env.PUBLIC_URL, imageUrl); }; \ No newline at end of file diff --git a/frontend/src/components/AddPlant.tsx b/frontend/src/components/AddPlant.tsx index c8e47a0f9..98363715e 100644 --- a/frontend/src/components/AddPlant.tsx +++ b/frontend/src/components/AddPlant.tsx @@ -24,19 +24,33 @@ export default function AddPlant(props: { const [useDate, setUseDate] = useState(true); const [selectedImage, setSelectedImage] = useState(); const [downloadedImg, setDownloadedImg] = useState(); - let imgSrc = props.entity == undefined ? "" : props.entity.imageUrl != undefined ? - props.entity.imageUrl : - props.entity.imageId != undefined ? - `data:image/png;base64,${downloadedImg}` : - process.env.PUBLIC_URL + "botanical-info-no-img.png"; - - const readImage = (): void => { - props.requestor.get(`image/${props.entity!.imageId}`) + const [imageDownloaded, setImageDownloaded] = useState(false); + + const readImage = (imageUrl: string): void => { + props.requestor.get(`image/content${imageUrl}`) .then((res) => { - setDownloadedImg(Buffer.from(res.data.content, "utf-8").toString()); + setDownloadedImg(res.data); + setImageDownloaded(true); }); }; + const setAbsoluteImageUrl = (imageUrl: string | undefined, publicUrl: string): string => { + if (imageUrl == undefined) { + return publicUrl + "botanical-info-no-img.png"; + } + if (imageUrl.startsWith("/")) { + readImage(imageUrl); + } + return imageUrl; + }; + + const setImageSrc = (): string => { + return imageDownloaded ? `data:application/octet-stream;base64,${downloadedImg}` : + setAbsoluteImageUrl(props.entity?.imageUrl, process.env.PUBLIC_URL); + }; + + let imgSrc = setImageSrc(); + const getName = (): void => { if (props.entity === undefined) { if (props.name === undefined) { @@ -149,11 +163,8 @@ export default function AddPlant(props: { }; useEffect(() => { - if (props.entity != undefined && - props.entity.imageUrl == undefined && - props.entity.imageId != undefined) { - readImage(); - } + setImageDownloaded(false); + setImageSrc(); getName(); }, [props.entity, props.name]); diff --git a/frontend/src/components/PlantDetails.tsx b/frontend/src/components/PlantDetails.tsx index 0c5f56535..24426f667 100644 --- a/frontend/src/components/PlantDetails.tsx +++ b/frontend/src/components/PlantDetails.tsx @@ -14,6 +14,7 @@ import "swiper/css"; import "swiper/css/pagination"; import 'swiper/css/virtual'; import "swiper/css/free-mode"; +import { getBotanicalInfoImg } from "../common"; function AllPlantLog(props: { requestor: AxiosInstance, @@ -179,26 +180,18 @@ function PlantHeader(props: { const [imageLoaded, setImageLoaded] = useState(false); const [checkedImages, setCheckedImages] = useState(false); const [imageIds, setImageIds] = useState([]); - const [downloadedBotanicalInfoImg, setDownloadedBotanicalInfoImg] = useState(); const [activeIndex, setActiveIndex] = useState(0); - let botanicalInfoimgSrc = props.entity?.botanicalInfo.imageUrl != undefined ? - props.entity?.botanicalInfo.imageUrl : - props.entity?.botanicalInfo.imageId != undefined ? - `data:image/png;base64,${downloadedBotanicalInfoImg}` : process.env.PUBLIC_URL + "botanical-info-no-img.png"; - - const readBotanicalInfoImage = (): void => { - props.requestor.get(`/image/${props.entity?.botanicalInfo.imageId}`) - .then((res) => { - setDownloadedBotanicalInfoImg(Buffer.from(res.data.content, "utf-8").toString()); - setCheckedImages(true); - }); - }; + const [imageSrc, setImageSrc] = useState(); useEffect(() => { - props.requestor.get(`/image/all/${props.entity?.id}`) + props.requestor.get(`/image/entity/all/${props.entity?.id}`) .then((res) => { - if (res.data.length === 0 && !props.entity?.botanicalInfo.systemWide) { - readBotanicalInfoImage(); + if (res.data.length === 0) { + getBotanicalInfoImg(props.requestor, props.entity?.botanicalInfo.imageUrl) + .then(res => { + setImageSrc(res); + setCheckedImages(true); + }); return; } setImageIds(res.data.reverse()); @@ -218,7 +211,7 @@ function PlantHeader(props: { { imageIds.length === 0 && checkedImages && void; }) { const [imageLoaded, setImageLoaded] = useState(false); - const [downloadedImg, setDownloadedImg] = useState(); - let imgSrc = props.entity.imageUrl != undefined ? - props.entity.imageUrl : - props.entity.imageId != undefined ? - `data:image/png;base64,${downloadedImg}` : process.env.PUBLIC_URL + "botanical-info-no-img.png"; + const [imgSrc, setImgSrc] = useState(); - const readImage = (): void => { - props.requestor.get(`image/${props.entity.imageId}`) + const setImageSrc = (): void => { + getBotanicalInfoImg(props.requestor, props.entity.imageUrl) .then((res) => { - setDownloadedImg(Buffer.from(res.data.content, "utf-8").toString()); + setImageLoaded(true); + setImgSrc(res); }); }; useEffect(() => { - if (props.entity.imageUrl == undefined && - props.entity.imageId != undefined) { - readImage(); - } - }); + setImageLoaded(false); + setImageSrc(); + }, [props.entity]); return ( void; }) { const [imageLoaded, setImageLoaded] = useState(false); - const [downloadedImg, setDownloadedImg] = useState(); - let imgSrc = props.entity.botanicalInfo.imageUrl != undefined ? - props.entity.botanicalInfo.imageUrl : - props.entity.botanicalInfo.imageId != undefined ? - `data:image/png;base64,${downloadedImg}` : process.env.PUBLIC_URL + "botanical-info-no-img.png"; + const [imgSrc, setImgSrc] = useState(); - const readImage = (): void => { - props.requestor.get(`image/${props.entity.botanicalInfo.imageId}`) + const setImageSrc = (): void => { + getBotanicalInfoImg(props.requestor, props.entity.botanicalInfo.imageUrl) .then((res) => { - setDownloadedImg(Buffer.from(res.data.content, "utf-8").toString()); + setImageLoaded(true); + setImgSrc(res); }); }; useEffect(() => { - if (props.entity.botanicalInfo.imageUrl == undefined && - props.entity.botanicalInfo.imageId != undefined) { - readImage(); - } - }); + setImageSrc(); + }, [props.entity]); return (