diff --git a/codiki-application/src/main/java/org/codiki/application/picture/PictureUseCases.java b/codiki-application/src/main/java/org/codiki/application/picture/PictureUseCases.java
index 71c2d0f..7ff59be 100644
--- a/codiki-application/src/main/java/org/codiki/application/picture/PictureUseCases.java
+++ b/codiki-application/src/main/java/org/codiki/application/picture/PictureUseCases.java
@@ -4,6 +4,7 @@ import java.io.File;
import java.util.UUID;
import static org.codiki.domain.picture.model.builder.PictureBuilder.aPicture;
+import org.codiki.domain.picture.exception.PictureNotFoundException;
import org.codiki.domain.picture.model.Picture;
import org.codiki.domain.picture.port.PicturePort;
import org.springframework.stereotype.Service;
@@ -30,4 +31,9 @@ public class PictureUseCases {
public void deletePicture(UUID pictureId) {
picturePort.deleteById(pictureId);
}
+
+ public Picture findById(UUID pictureId) {
+ return picturePort.findById(pictureId)
+ .orElseThrow(() -> new PictureNotFoundException(pictureId));
+ }
}
diff --git a/codiki-domain/src/main/java/org/codiki/domain/picture/exception/PictureNotFoundException.java b/codiki-domain/src/main/java/org/codiki/domain/picture/exception/PictureNotFoundException.java
new file mode 100644
index 0000000..cca099f
--- /dev/null
+++ b/codiki-domain/src/main/java/org/codiki/domain/picture/exception/PictureNotFoundException.java
@@ -0,0 +1,11 @@
+package org.codiki.domain.picture.exception;
+
+import java.util.UUID;
+
+import org.codiki.domain.exception.FunctionnalException;
+
+public class PictureNotFoundException extends FunctionnalException {
+ public PictureNotFoundException(UUID pictureId) {
+ super(String.format("Picture with id %s is not found.", pictureId));
+ }
+}
diff --git a/codiki-domain/src/main/java/org/codiki/domain/picture/exception/PictureUploadException.java b/codiki-domain/src/main/java/org/codiki/domain/picture/exception/PictureUploadException.java
new file mode 100644
index 0000000..bdeab72
--- /dev/null
+++ b/codiki-domain/src/main/java/org/codiki/domain/picture/exception/PictureUploadException.java
@@ -0,0 +1,9 @@
+package org.codiki.domain.picture.exception;
+
+import org.codiki.domain.exception.FunctionnalException;
+
+public class PictureUploadException extends FunctionnalException {
+ public PictureUploadException(String message) {
+ super(message);
+ }
+}
diff --git a/codiki-exposition/pom.xml b/codiki-exposition/pom.xml
index 95b3ca0..392fa00 100644
--- a/codiki-exposition/pom.xml
+++ b/codiki-exposition/pom.xml
@@ -29,6 +29,10 @@
org.projectlombok
lombok
+
+ org.apache.tika
+ tika-core
+
diff --git a/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java b/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java
index 2023382..fd04a1c 100644
--- a/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java
+++ b/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java
@@ -2,6 +2,7 @@ package org.codiki.exposition.configuration;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.FORBIDDEN;
+import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import org.codiki.domain.category.exception.CategoryDeletionException;
@@ -11,6 +12,8 @@ import org.codiki.domain.exception.LoginFailureException;
import org.codiki.domain.exception.RefreshTokenDoesNotExistException;
import org.codiki.domain.exception.RefreshTokenExpiredException;
import org.codiki.domain.exception.UserDoesNotExistException;
+import org.codiki.domain.picture.exception.PictureNotFoundException;
+import org.codiki.domain.picture.exception.PictureUploadException;
import org.codiki.domain.publication.exception.PublicationEditionException;
import org.codiki.domain.publication.exception.PublicationNotFoundException;
import org.codiki.domain.publication.exception.PublicationUpdateForbiddenException;
@@ -80,4 +83,16 @@ public class GlobalControllerExceptionHandler {
public void handleCategoryDeletionException() {
// Do nothing.
}
+
+ @ResponseStatus(BAD_REQUEST)
+ @ExceptionHandler(PictureUploadException.class)
+ public void handlePictureUploadException() {
+ // Do nothing.
+ }
+
+ @ResponseStatus(NOT_FOUND)
+ @ExceptionHandler(PictureNotFoundException.class)
+ public void handlePictureNotFoundException() {
+ // Do nothing.
+ }
}
diff --git a/codiki-exposition/src/main/java/org/codiki/exposition/configuration/security/SecurityConfiguration.java b/codiki-exposition/src/main/java/org/codiki/exposition/configuration/security/SecurityConfiguration.java
index f2bcbff..72f9322 100644
--- a/codiki-exposition/src/main/java/org/codiki/exposition/configuration/security/SecurityConfiguration.java
+++ b/codiki-exposition/src/main/java/org/codiki/exposition/configuration/security/SecurityConfiguration.java
@@ -44,6 +44,7 @@ public class SecurityConfiguration {
GET,
"/api/health/check",
"/api/categories",
+ "/api/pictures/{pictureId}",
"/error"
).permitAll()
.requestMatchers(
diff --git a/codiki-exposition/src/main/java/org/codiki/exposition/picture/MultipartFileConverter.java b/codiki-exposition/src/main/java/org/codiki/exposition/picture/MultipartFileConverter.java
index 900bf46..a454654 100644
--- a/codiki-exposition/src/main/java/org/codiki/exposition/picture/MultipartFileConverter.java
+++ b/codiki-exposition/src/main/java/org/codiki/exposition/picture/MultipartFileConverter.java
@@ -1,23 +1,44 @@
package org.codiki.exposition.picture;
+import static java.util.Objects.isNull;
import java.io.File;
import java.io.IOException;
+import java.util.List;
import java.util.UUID;
+import org.apache.tika.mime.MimeType;
+import org.apache.tika.mime.MimeTypeException;
+import org.apache.tika.mime.MimeTypes;
+import org.codiki.domain.picture.exception.PictureUploadException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Component
public class MultipartFileConverter {
- private final String tempPicturesForlderPath;
+ private static final List ALLOWED_MIME_TYPES;
- public MultipartFileConverter(@Value("${application.pictures.temp-path}") String tempPicturesForlderPath) {
- this.tempPicturesForlderPath = tempPicturesForlderPath;
+ static {
+ MimeTypes mimeTypes = MimeTypes.getDefaultMimeTypes();
+
+ try {
+ ALLOWED_MIME_TYPES = List.of(
+ mimeTypes.forName("image/png"),
+ mimeTypes.forName("image/jpeg")
+ );
+ } catch (MimeTypeException exception) {
+ throw new RuntimeException("An error occurred while loading allowed mime types.", exception);
+ }
+ }
+
+ private final String tempPicturesFolderPath;
+
+ public MultipartFileConverter(@Value("${application.pictures.temp-path}") String tempPicturesFolderPath) {
+ this.tempPicturesFolderPath = tempPicturesFolderPath;
}
public File transformToFile(MultipartFile fileContent) {
- File pictureFile = new File(String.format("%s/%s", tempPicturesForlderPath, UUID.randomUUID()));
+ File pictureFile = new File(buildPicturePath(fileContent));
try {
fileContent.transferTo(pictureFile);
} catch (IOException e) {
@@ -25,4 +46,34 @@ public class MultipartFileConverter {
}
return pictureFile;
}
+
+ private String buildPicturePath(MultipartFile fileContent) {
+ MimeType fileContentType = extractMimeType(fileContent);
+ return String.format(
+ "%s/%s%s",
+ tempPicturesFolderPath,
+ UUID.randomUUID(),
+ fileContentType.getExtension()
+ );
+ }
+
+ private MimeType extractMimeType(MultipartFile fileContent) {
+ MimeType result = null;
+ try {
+ result = MimeTypes.getDefaultMimeTypes()
+ .forName(fileContent.getContentType());
+ } catch (MimeTypeException exception) {
+ // Do nothing
+ }
+
+ if (isNull(result) || !isAllowedMimeType(result)) {
+ throw new PictureUploadException("Unable to upload the picture because its format is incorrect.");
+ }
+
+ return result;
+ }
+
+ private boolean isAllowedMimeType(MimeType mimeType) {
+ return ALLOWED_MIME_TYPES.contains(mimeType);
+ }
}
diff --git a/codiki-exposition/src/main/java/org/codiki/exposition/picture/PictureController.java b/codiki-exposition/src/main/java/org/codiki/exposition/picture/PictureController.java
index 3cb07e0..9e71271 100644
--- a/codiki-exposition/src/main/java/org/codiki/exposition/picture/PictureController.java
+++ b/codiki-exposition/src/main/java/org/codiki/exposition/picture/PictureController.java
@@ -3,9 +3,13 @@ package org.codiki.exposition.picture;
import java.io.File;
import java.util.UUID;
+import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import org.codiki.application.picture.PictureUseCases;
import org.codiki.domain.picture.model.Picture;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -32,4 +36,10 @@ public class PictureController {
Picture newPicture = pictureUseCases.createPicture(pictureFile);
return newPicture.id();
}
+
+ @GetMapping(value = "/{pictureId}", produces = APPLICATION_OCTET_STREAM_VALUE)
+ public FileSystemResource loadPicture(@PathVariable("pictureId") UUID pictureId) {
+ Picture picture = pictureUseCases.findById(pictureId);
+ return new FileSystemResource(picture.contentFile());
+ }
}
diff --git a/codiki-infrastructure/src/main/java/org/codiki/infrastructure/picture/PictureJpaAdapter.java b/codiki-infrastructure/src/main/java/org/codiki/infrastructure/picture/PictureJpaAdapter.java
index 0407dc8..213e391 100644
--- a/codiki-infrastructure/src/main/java/org/codiki/infrastructure/picture/PictureJpaAdapter.java
+++ b/codiki-infrastructure/src/main/java/org/codiki/infrastructure/picture/PictureJpaAdapter.java
@@ -4,6 +4,8 @@ import java.io.File;
import java.util.Optional;
import java.util.UUID;
+import static org.codiki.domain.picture.model.builder.PictureBuilder.aPicture;
+import org.codiki.domain.picture.exception.PictureNotFoundException;
import org.codiki.domain.picture.exception.PictureStorageErrorException;
import org.codiki.domain.picture.model.Picture;
import org.codiki.domain.picture.port.PicturePort;
@@ -35,7 +37,17 @@ public class PictureJpaAdapter implements PicturePort {
@Override
public Optional findById(UUID pictureId) {
return repository.findById(pictureId)
- .map(PictureEntity::toDomain);
+ .map(PictureEntity::toDomain)
+ .map(picture -> {
+ File pictureFile = new File(String.format("%s/%s", pictureFolderPath, pictureId));
+ if (!pictureFile.exists()) {
+ throw new PictureNotFoundException(pictureId);
+ }
+ return aPicture()
+ .basedOn(picture)
+ .withContentFile(pictureFile)
+ .build();
+ });
}
@Override
diff --git a/pom.xml b/pom.xml
index 3447375..2c4aeda 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,7 @@
6.0.0
4.4.0
42.7.0
+ 2.9.0
@@ -72,6 +73,11 @@
postgresql
${postgresql.version}
+
+ org.apache.tika
+ tika-core
+ ${tika-core.version}
+
diff --git a/rest-client-collection/Codiki/environments/localhost.bru b/rest-client-collection/Codiki/environments/localhost.bru
index 9e658b6..da6c63f 100644
--- a/rest-client-collection/Codiki/environments/localhost.bru
+++ b/rest-client-collection/Codiki/environments/localhost.bru
@@ -1,6 +1,7 @@
vars {
url: http://localhost:8080
publicationId: fce1de27-11c6-4deb-a248-b63288c00037
- bearerToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YWQ0NjJiOC04ZjllLTRhMjYtYmI4Ni1jNzRmZWY1ZDExYjYiLCJleHAiOjE3MTAzMzc2Njd9.ExV8xDeqqKk5WjIVb16NBqF1gPoRqx7uL4jQIhWjjY0QVhB5EAGdHMIbLr4s9Ck2f6z8U4sRlpPAQquDOr_9NA
+ bearerToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YWQ0NjJiOC04ZjllLTRhMjYtYmI4Ni1jNzRmZWY1ZDExYjYiLCJleHAiOjE3MTA0MDYxMzF9.FhAT0my_DfKKTcgpWA3cesv8WYNw36dV6O1ZYrNtW0NR3E9AQ_XP0hAw_GH1K4maMxIzToqzNrZVJ-ug-cIaCQ
categoryId: 172fa901-3f4b-4540-92f3-1c15820e8ec9
+ pictureId: 65b660b7-66bb-4e4a-a62c-fd0ca101f972
}