Add picture format control.
This commit is contained in:
@@ -4,6 +4,7 @@ import java.io.File;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.codiki.domain.picture.model.builder.PictureBuilder.aPicture;
|
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.model.Picture;
|
||||||
import org.codiki.domain.picture.port.PicturePort;
|
import org.codiki.domain.picture.port.PicturePort;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -30,4 +31,9 @@ public class PictureUseCases {
|
|||||||
public void deletePicture(UUID pictureId) {
|
public void deletePicture(UUID pictureId) {
|
||||||
picturePort.deleteById(pictureId);
|
picturePort.deleteById(pictureId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Picture findById(UUID pictureId) {
|
||||||
|
return picturePort.findById(pictureId)
|
||||||
|
.orElseThrow(() -> new PictureNotFoundException(pictureId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,10 @@
|
|||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tika</groupId>
|
||||||
|
<artifactId>tika-core</artifactId>
|
||||||
|
</dependency>
|
||||||
<!-- <dependency>-->
|
<!-- <dependency>-->
|
||||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||||
<!-- <artifactId>spring-boot-starter-data-jpa</artifactId>-->
|
<!-- <artifactId>spring-boot-starter-data-jpa</artifactId>-->
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.codiki.exposition.configuration;
|
|||||||
|
|
||||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
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.NOT_FOUND;
|
||||||
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
|
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
|
||||||
import org.codiki.domain.category.exception.CategoryDeletionException;
|
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.RefreshTokenDoesNotExistException;
|
||||||
import org.codiki.domain.exception.RefreshTokenExpiredException;
|
import org.codiki.domain.exception.RefreshTokenExpiredException;
|
||||||
import org.codiki.domain.exception.UserDoesNotExistException;
|
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.PublicationEditionException;
|
||||||
import org.codiki.domain.publication.exception.PublicationNotFoundException;
|
import org.codiki.domain.publication.exception.PublicationNotFoundException;
|
||||||
import org.codiki.domain.publication.exception.PublicationUpdateForbiddenException;
|
import org.codiki.domain.publication.exception.PublicationUpdateForbiddenException;
|
||||||
@@ -80,4 +83,16 @@ public class GlobalControllerExceptionHandler {
|
|||||||
public void handleCategoryDeletionException() {
|
public void handleCategoryDeletionException() {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResponseStatus(BAD_REQUEST)
|
||||||
|
@ExceptionHandler(PictureUploadException.class)
|
||||||
|
public void handlePictureUploadException() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResponseStatus(NOT_FOUND)
|
||||||
|
@ExceptionHandler(PictureNotFoundException.class)
|
||||||
|
public void handlePictureNotFoundException() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public class SecurityConfiguration {
|
|||||||
GET,
|
GET,
|
||||||
"/api/health/check",
|
"/api/health/check",
|
||||||
"/api/categories",
|
"/api/categories",
|
||||||
|
"/api/pictures/{pictureId}",
|
||||||
"/error"
|
"/error"
|
||||||
).permitAll()
|
).permitAll()
|
||||||
.requestMatchers(
|
.requestMatchers(
|
||||||
|
|||||||
@@ -1,23 +1,44 @@
|
|||||||
package org.codiki.exposition.picture;
|
package org.codiki.exposition.picture;
|
||||||
|
|
||||||
|
import static java.util.Objects.isNull;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
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.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MultipartFileConverter {
|
public class MultipartFileConverter {
|
||||||
private final String tempPicturesForlderPath;
|
private static final List<MimeType> ALLOWED_MIME_TYPES;
|
||||||
|
|
||||||
public MultipartFileConverter(@Value("${application.pictures.temp-path}") String tempPicturesForlderPath) {
|
static {
|
||||||
this.tempPicturesForlderPath = tempPicturesForlderPath;
|
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) {
|
public File transformToFile(MultipartFile fileContent) {
|
||||||
File pictureFile = new File(String.format("%s/%s", tempPicturesForlderPath, UUID.randomUUID()));
|
File pictureFile = new File(buildPicturePath(fileContent));
|
||||||
try {
|
try {
|
||||||
fileContent.transferTo(pictureFile);
|
fileContent.transferTo(pictureFile);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -25,4 +46,34 @@ public class MultipartFileConverter {
|
|||||||
}
|
}
|
||||||
return pictureFile;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ package org.codiki.exposition.picture;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.UUID;
|
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 static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
||||||
import org.codiki.application.picture.PictureUseCases;
|
import org.codiki.application.picture.PictureUseCases;
|
||||||
import org.codiki.domain.picture.model.Picture;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -32,4 +36,10 @@ public class PictureController {
|
|||||||
Picture newPicture = pictureUseCases.createPicture(pictureFile);
|
Picture newPicture = pictureUseCases.createPicture(pictureFile);
|
||||||
return newPicture.id();
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import java.io.File;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
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.exception.PictureStorageErrorException;
|
||||||
import org.codiki.domain.picture.model.Picture;
|
import org.codiki.domain.picture.model.Picture;
|
||||||
import org.codiki.domain.picture.port.PicturePort;
|
import org.codiki.domain.picture.port.PicturePort;
|
||||||
@@ -35,7 +37,17 @@ public class PictureJpaAdapter implements PicturePort {
|
|||||||
@Override
|
@Override
|
||||||
public Optional<Picture> findById(UUID pictureId) {
|
public Optional<Picture> findById(UUID pictureId) {
|
||||||
return repository.findById(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
|
@Override
|
||||||
|
|||||||
6
pom.xml
6
pom.xml
@@ -18,6 +18,7 @@
|
|||||||
<jakarta.servlet-api.version>6.0.0</jakarta.servlet-api.version>
|
<jakarta.servlet-api.version>6.0.0</jakarta.servlet-api.version>
|
||||||
<java-jwt.version>4.4.0</java-jwt.version>
|
<java-jwt.version>4.4.0</java-jwt.version>
|
||||||
<postgresql.version>42.7.0</postgresql.version>
|
<postgresql.version>42.7.0</postgresql.version>
|
||||||
|
<tika-core.version>2.9.0</tika-core.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
@@ -72,6 +73,11 @@
|
|||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
<version>${postgresql.version}</version>
|
<version>${postgresql.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tika</groupId>
|
||||||
|
<artifactId>tika-core</artifactId>
|
||||||
|
<version>${tika-core.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
vars {
|
vars {
|
||||||
url: http://localhost:8080
|
url: http://localhost:8080
|
||||||
publicationId: fce1de27-11c6-4deb-a248-b63288c00037
|
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
|
categoryId: 172fa901-3f4b-4540-92f3-1c15820e8ec9
|
||||||
|
pictureId: 65b660b7-66bb-4e4a-a62c-fd0ca101f972
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user