5 Commits
26 changed files with 241 additions and 102 deletions
@@ -1,6 +1,7 @@
package org.codiki.application.picture; package org.codiki.application.picture;
import java.io.File; import java.io.File;
import java.util.Optional;
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;
@@ -30,4 +31,8 @@ public class PictureUseCases {
public void deletePicture(UUID pictureId) { public void deletePicture(UUID pictureId) {
picturePort.deleteById(pictureId); picturePort.deleteById(pictureId);
} }
public Optional<Picture> findById(UUID pictureId) {
return picturePort.findById(pictureId);
}
} }
@@ -19,8 +19,8 @@ public class PublicationCreationRequestValidator {
throw new PublicationEditionException("description cannot be null"); throw new PublicationEditionException("description cannot be null");
} }
if (request.image() == null) { if (request.pictureId() == null) {
throw new PublicationEditionException("image cannot be null"); throw new PublicationEditionException("pictureId cannot be null");
} }
} }
} }
@@ -13,7 +13,7 @@ public class PublicationUpdateRequestValidator {
isNull(request.title()) && isNull(request.title()) &&
isNull(request.text()) && isNull(request.text()) &&
isNull(request.description()) && isNull(request.description()) &&
isNull(request.image()) && isNull(request.pictureId()) &&
isNull(request.categoryId()) isNull(request.categoryId())
) { ) {
throw new PublicationEditionException("no any field is filled"); throw new PublicationEditionException("no any field is filled");
@@ -3,15 +3,19 @@ package org.codiki.application.publication;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
import java.time.Clock; import java.time.Clock;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static org.codiki.domain.publication.model.builder.AuthorBuilder.anAuthor; import static org.codiki.domain.publication.model.builder.AuthorBuilder.anAuthor;
import static org.codiki.domain.publication.model.builder.PublicationBuilder.aPublication; import static org.codiki.domain.publication.model.builder.PublicationBuilder.aPublication;
import org.codiki.application.category.CategoryUseCases; import org.codiki.application.category.CategoryUseCases;
import org.codiki.application.picture.PictureUseCases;
import org.codiki.application.user.UserUseCases; import org.codiki.application.user.UserUseCases;
import org.codiki.domain.category.exception.CategoryNotFoundException; import org.codiki.domain.category.exception.CategoryNotFoundException;
import org.codiki.domain.category.model.Category; import org.codiki.domain.category.model.Category;
import org.codiki.domain.exception.AuthenticationRequiredException; import org.codiki.domain.exception.AuthenticationRequiredException;
import org.codiki.domain.picture.exception.PictureNotFoundException;
import org.codiki.domain.picture.model.Picture;
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;
@@ -25,19 +29,22 @@ import org.springframework.stereotype.Service;
@Service @Service
public class PublicationUseCases { public class PublicationUseCases {
private final CategoryUseCases categoryUseCases; private final CategoryUseCases categoryUseCases;
private final Clock clock;
private final KeyGenerator keyGenerator; private final KeyGenerator keyGenerator;
private final PictureUseCases pictureUseCases;
private final PublicationPort publicationPort; private final PublicationPort publicationPort;
private final PublicationCreationRequestValidator publicationCreationRequestValidator; private final PublicationCreationRequestValidator publicationCreationRequestValidator;
private final PublicationUpdateRequestValidator publicationUpdateRequestValidator; private final PublicationUpdateRequestValidator publicationUpdateRequestValidator;
private final UserUseCases userUseCases; private final UserUseCases userUseCases;
private final Clock clock;
public PublicationUseCases( public PublicationUseCases(
CategoryUseCases categoryUseCases, CategoryUseCases categoryUseCases,
Clock clock, Clock clock,
KeyGenerator keyGenerator, KeyGenerator keyGenerator,
PictureUseCases pictureUseCases,
PublicationCreationRequestValidator publicationCreationRequestValidator, PublicationCreationRequestValidator publicationCreationRequestValidator,
PublicationPort publicationPort, PublicationUpdateRequestValidator publicationUpdateRequestValidator, PublicationPort publicationPort,
PublicationUpdateRequestValidator publicationUpdateRequestValidator,
UserUseCases userUseCases UserUseCases userUseCases
) { ) {
this.categoryUseCases = categoryUseCases; this.categoryUseCases = categoryUseCases;
@@ -47,6 +54,7 @@ public class PublicationUseCases {
this.publicationPort = publicationPort; this.publicationPort = publicationPort;
this.publicationUpdateRequestValidator = publicationUpdateRequestValidator; this.publicationUpdateRequestValidator = publicationUpdateRequestValidator;
this.userUseCases = userUseCases; this.userUseCases = userUseCases;
this.pictureUseCases = pictureUseCases;
} }
public Publication createPublication(PublicationEditionRequest request) { public Publication createPublication(PublicationEditionRequest request) {
@@ -60,13 +68,18 @@ public class PublicationUseCases {
new CategoryNotFoundException(request.categoryId()) new CategoryNotFoundException(request.categoryId())
)); ));
Picture picture = pictureUseCases.findById(request.pictureId())
.orElseThrow(() -> new PublicationEditionException(
new PictureNotFoundException(request.pictureId())
));
Publication newPublication = aPublication() Publication newPublication = aPublication()
.withId(UUID.randomUUID()) .withId(UUID.randomUUID())
.withKey(keyGenerator.generateKey()) .withKey(keyGenerator.generateKey())
.withTitle(request.title()) .withTitle(request.title())
.withText(request.text()) .withText(request.text())
.withDescription(request.description()) .withDescription(request.description())
.withImage(request.image()) .withPicture(picture)
.withCreationDate(ZonedDateTime.now(clock)) .withCreationDate(ZonedDateTime.now(clock))
.withAuthor(anAuthor().basedOn(authenticatedUser).build()) .withAuthor(anAuthor().basedOn(authenticatedUser).build())
.withCategory(category) .withCategory(category)
@@ -104,8 +117,12 @@ public class PublicationUseCases {
publicationBuilder.withDescription(request.description()); publicationBuilder.withDescription(request.description());
} }
if (!isNull(request.image())) { if (!isNull(request.pictureId())) {
publicationBuilder.withImage(request.image()); Picture picture = pictureUseCases.findById(request.pictureId())
.orElseThrow(() -> new PublicationEditionException(
new PictureNotFoundException(request.pictureId())
));
publicationBuilder.withPicture(picture);
} }
if (!isNull(request.categoryId())) { if (!isNull(request.categoryId())) {
@@ -137,4 +154,8 @@ public class PublicationUseCases {
publicationPort.delete(publicationToDelete); publicationPort.delete(publicationToDelete);
} }
public Optional<Publication> findById(UUID publicationId) {
return publicationPort.findById(publicationId);
}
} }
@@ -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);
}
}
@@ -8,6 +8,6 @@ public class PublicationEditionException extends FunctionnalException {
} }
public PublicationEditionException(FunctionnalException cause) { public PublicationEditionException(FunctionnalException cause) {
super("Impossible to edit a publication due to a root cause.", cause); super(String.format("Impossible to edit a publication due to a root cause: %s.", cause.getMessage()));
} }
} }
@@ -4,6 +4,7 @@ import java.time.ZonedDateTime;
import java.util.UUID; import java.util.UUID;
import org.codiki.domain.category.model.Category; import org.codiki.domain.category.model.Category;
import org.codiki.domain.picture.model.Picture;
public record Publication( public record Publication(
UUID id, UUID id,
@@ -11,8 +12,8 @@ public record Publication(
String title, String title,
String text, String text,
String description, String description,
String image,
ZonedDateTime creationDate, ZonedDateTime creationDate,
Picture picture,
Author author, Author author,
Category category Category category
) { ) {
@@ -6,6 +6,6 @@ public record PublicationEditionRequest(
String title, String title,
String text, String text,
String description, String description,
String image, UUID pictureId,
UUID categoryId UUID categoryId
) {} ) {}
@@ -20,7 +20,7 @@ public class AuthorBuilder {
return new AuthorBuilder() return new AuthorBuilder()
.withId(user.id()) .withId(user.id())
// .withName(user.name()) // .withName(user.name())
// .withImage(user.image()) // .withImage(user.pictureId())
; ;
} }
@@ -3,6 +3,7 @@ package org.codiki.domain.publication.model.builder;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.UUID; import java.util.UUID;
import org.codiki.domain.picture.model.Picture;
import org.codiki.domain.publication.model.Author; import org.codiki.domain.publication.model.Author;
import org.codiki.domain.category.model.Category; import org.codiki.domain.category.model.Category;
import org.codiki.domain.publication.model.Publication; import org.codiki.domain.publication.model.Publication;
@@ -13,7 +14,7 @@ public class PublicationBuilder {
private String title; private String title;
private String text; private String text;
private String description; private String description;
private String image; private Picture picture;
private ZonedDateTime creationDate; private ZonedDateTime creationDate;
private Author author; private Author author;
private Category category; private Category category;
@@ -49,8 +50,8 @@ public class PublicationBuilder {
return this; return this;
} }
public PublicationBuilder withImage(String image) { public PublicationBuilder withPicture(Picture picture) {
this.image = image; this.picture = picture;
return this; return this;
} }
@@ -76,8 +77,7 @@ public class PublicationBuilder {
title, title,
text, text,
description, description,
image, creationDate, picture,
creationDate,
author, author,
category category
); );
@@ -90,7 +90,7 @@ public class PublicationBuilder {
.withTitle(publication.title()) .withTitle(publication.title())
.withText(publication.text()) .withText(publication.text())
.withDescription(publication.description()) .withDescription(publication.description())
.withImage(publication.image()) .withPicture(publication.picture())
.withCreationDate(publication.creationDate()) .withCreationDate(publication.creationDate())
.withAuthor(publication.author()) .withAuthor(publication.author())
.withCategory(publication.category()); .withCategory(publication.category());
+4
View File
@@ -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>-->
@@ -11,73 +11,57 @@ 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;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice @RestControllerAdvice
public class GlobalControllerExceptionHandler { public class GlobalControllerExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({
@ResponseStatus(BAD_REQUEST) CategoryDeletionException.class,
@ExceptionHandler(LoginFailureException.class) CategoryEditionException.class,
public void handleLoginFailureException() { CategoryNotFoundException.class,
// Do nothing. LoginFailureException.class,
PublicationEditionException.class,
PictureUploadException.class
})
public ProblemDetail handleBadRequestExceptions(Exception exception) {
return buildProblemDetail(BAD_REQUEST, exception);
} }
@ResponseStatus(NOT_FOUND) @ExceptionHandler({
@ExceptionHandler(UserDoesNotExistException.class) UserDoesNotExistException.class,
public void handleUserDoesNotExistException() { RefreshTokenDoesNotExistException.class,
// Do nothing. PublicationNotFoundException.class,
PictureNotFoundException.class
})
public ProblemDetail handleNotFoundExceptions(Exception exception) {
return buildProblemDetail(NOT_FOUND, exception);
} }
@ResponseStatus(NOT_FOUND) @ExceptionHandler({
@ExceptionHandler(RefreshTokenDoesNotExistException.class) RefreshTokenExpiredException.class
public void handleRefreshTokenDoesNotExistException() { })
// Do nothing. public ProblemDetail handleUnauthorizedExceptions(Exception exception) {
return buildProblemDetail(UNAUTHORIZED, exception);
} }
@ResponseStatus(UNAUTHORIZED) @ExceptionHandler({
@ExceptionHandler(RefreshTokenExpiredException.class) PublicationUpdateForbiddenException.class
public void handleRefreshTokenExpiredException() { })
// Do nothing. public ProblemDetail handleForbiddenExceptions(Exception exception) {
return buildProblemDetail(FORBIDDEN, exception);
} }
@ResponseStatus(BAD_REQUEST) private static ProblemDetail buildProblemDetail(HttpStatus forbidden, Exception exception) {
@ExceptionHandler(CategoryNotFoundException.class) return ProblemDetail.forStatusAndDetail(forbidden, exception.getMessage());
public void handleCategoryNotFoundException() {
// Do nothing.
} }
@ResponseStatus(BAD_REQUEST)
@ExceptionHandler(PublicationEditionException.class)
public void handlePublicationEditionException() {
// Do nothing.
}
@ResponseStatus(NOT_FOUND)
@ExceptionHandler(PublicationNotFoundException.class)
public void handlePublicationNotFoundException() {
// Do nothing.
}
@ResponseStatus(FORBIDDEN)
@ExceptionHandler(PublicationUpdateForbiddenException.class)
public void handlePublicationUpdateForbiddenException() {
// Do nothing.
}
@ResponseStatus(BAD_REQUEST)
@ExceptionHandler(CategoryEditionException.class)
public void handleCategoryEditionException() {
// Do nothing.
}
@ResponseStatus(BAD_REQUEST)
@ExceptionHandler(CategoryDeletionException.class)
public void handleCategoryDeletionException() {
// Do nothing.
}
} }
@@ -6,6 +6,7 @@ import static org.springframework.http.HttpMethod.OPTIONS;
import static org.springframework.http.HttpMethod.POST; import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpMethod.PUT; import static org.springframework.http.HttpMethod.PUT;
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
import org.codiki.domain.user.model.UserRole;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
@@ -44,6 +45,8 @@ public class SecurityConfiguration {
GET, GET,
"/api/health/check", "/api/health/check",
"/api/categories", "/api/categories",
"/api/pictures/{pictureId}",
"/api/publications/{publicationId}",
"/error" "/error"
).permitAll() ).permitAll()
.requestMatchers( .requestMatchers(
@@ -54,15 +57,15 @@ public class SecurityConfiguration {
.requestMatchers( .requestMatchers(
POST, POST,
"/api/categories" "/api/categories"
).hasRole("ADMIN") ).hasRole(UserRole.ADMIN.name())
.requestMatchers( .requestMatchers(
PUT, PUT,
"/api/categories/{categoryId}" "/api/categories/{categoryId}"
).hasRole("ADMIN") ).hasRole(UserRole.ADMIN.name())
.requestMatchers( .requestMatchers(
DELETE, DELETE,
"/api/categories/{categoryId}" "/api/categories/{categoryId}"
).hasRole("ADMIN") ).hasRole(UserRole.ADMIN.name())
.requestMatchers(OPTIONS).permitAll() .requestMatchers(OPTIONS).permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
); );
@@ -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,14 @@ 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.exception.PictureNotFoundException;
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 +37,11 @@ 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)
.orElseThrow(() -> new PictureNotFoundException(pictureId));
return new FileSystemResource(picture.contentFile());
}
} }
@@ -5,11 +5,13 @@ import java.util.UUID;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.NO_CONTENT; import static org.springframework.http.HttpStatus.NO_CONTENT;
import org.codiki.application.publication.PublicationUseCases; import org.codiki.application.publication.PublicationUseCases;
import org.codiki.domain.publication.exception.PublicationNotFoundException;
import org.codiki.domain.publication.model.Publication; import org.codiki.domain.publication.model.Publication;
import org.codiki.domain.publication.model.PublicationEditionRequest; import org.codiki.domain.publication.model.PublicationEditionRequest;
import org.codiki.exposition.publication.model.PublicationDto;
import org.codiki.exposition.publication.model.PublicationEditionRequestDto; import org.codiki.exposition.publication.model.PublicationEditionRequestDto;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; 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.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
@@ -27,6 +29,13 @@ public class PublicationController {
this.publicationUseCases = publicationUseCases; this.publicationUseCases = publicationUseCases;
} }
@GetMapping("/{publicationId}")
public PublicationDto getById(@PathVariable("publicationId") UUID publicationId) {
return publicationUseCases.findById(publicationId)
.map(PublicationDto::new)
.orElseThrow(() -> new PublicationNotFoundException(publicationId));
}
@PostMapping @PostMapping
@ResponseStatus(CREATED) @ResponseStatus(CREATED)
public PublicationDto createPublication(@RequestBody PublicationEditionRequestDto requestDto) { public PublicationDto createPublication(@RequestBody PublicationEditionRequestDto requestDto) {
@@ -1,11 +1,10 @@
package org.codiki.exposition.publication; package org.codiki.exposition.publication.model;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.UUID; import java.util.UUID;
import org.codiki.domain.publication.model.Publication; import org.codiki.domain.publication.model.Publication;
import org.codiki.exposition.category.model.CategoryDto; import org.codiki.exposition.category.model.CategoryDto;
import org.codiki.exposition.publication.model.AuthorDto;
public record PublicationDto( public record PublicationDto(
UUID id, UUID id,
@@ -13,8 +12,8 @@ public record PublicationDto(
String title, String title,
String text, String text,
String description, String description,
String image,
ZonedDateTime creationDate, ZonedDateTime creationDate,
UUID picture,
AuthorDto author, AuthorDto author,
CategoryDto category CategoryDto category
) { ) {
@@ -25,8 +24,8 @@ public record PublicationDto(
publication.title(), publication.title(),
publication.text(), publication.text(),
publication.description(), publication.description(),
publication.image(),
publication.creationDate(), publication.creationDate(),
publication.picture().id(),
new AuthorDto(publication.author()), new AuthorDto(publication.author()),
new CategoryDto(publication.category()) new CategoryDto(publication.category())
); );
@@ -8,10 +8,10 @@ public record PublicationEditionRequestDto(
String title, String title,
String text, String text,
String description, String description,
String image, UUID pictureId,
UUID categoryId UUID categoryId
) { ) {
public PublicationEditionRequest toDomain() { public PublicationEditionRequest toDomain() {
return new PublicationEditionRequest(title, text, description, image, categoryId); return new PublicationEditionRequest(title, text, description, pictureId, categoryId);
} }
} }
@@ -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
@@ -24,13 +24,13 @@ public class AuthorEntity {
private UUID id; private UUID id;
@Column(nullable = false) @Column(nullable = false)
private String name; private String name;
// private String image; // private String pictureId;
public AuthorEntity(Author author) { public AuthorEntity(Author author) {
this( this(
author.id(), author.id(),
author.name() author.name()
// author.image() // author.pictureId()
); );
} }
@@ -5,6 +5,7 @@ import java.util.UUID;
import org.codiki.domain.publication.model.Publication; import org.codiki.domain.publication.model.Publication;
import org.codiki.infrastructure.category.model.CategoryEntity; import org.codiki.infrastructure.category.model.CategoryEntity;
import org.codiki.infrastructure.picture.model.PictureEntity;
import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.FetchType.LAZY;
import jakarta.persistence.Column; import jakarta.persistence.Column;
@@ -36,10 +37,11 @@ public class PublicationEntity {
@Column(nullable = false) @Column(nullable = false)
private String description; private String description;
@Column(nullable = false) @Column(nullable = false)
private String image;
@Column(nullable = false)
private ZonedDateTime creationDate; private ZonedDateTime creationDate;
@ManyToOne(fetch = LAZY) @ManyToOne(fetch = LAZY)
@JoinColumn(name = "picture_id")
private PictureEntity picture;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "author_id") @JoinColumn(name = "author_id")
private AuthorEntity author; private AuthorEntity author;
@ManyToOne(fetch = LAZY) @ManyToOne(fetch = LAZY)
@@ -53,8 +55,8 @@ public class PublicationEntity {
publication.title(), publication.title(),
publication.text(), publication.text(),
publication.description(), publication.description(),
publication.image(),
publication.creationDate(), publication.creationDate(),
new PictureEntity(publication.picture()),
new AuthorEntity(publication.author()), new AuthorEntity(publication.author()),
new CategoryEntity(publication.category()) new CategoryEntity(publication.category())
); );
@@ -67,8 +69,7 @@ public class PublicationEntity {
title, title,
text, text,
description, description,
image, creationDate, picture.toDomain(),
creationDate,
author.toDomain(), author.toDomain(),
category.toDomain() category.toDomain()
); );
@@ -9,6 +9,13 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
public interface PublicationRepository extends JpaRepository<PublicationEntity, UUID> { public interface PublicationRepository extends JpaRepository<PublicationEntity, UUID> {
@Query("SELECT p FROM PublicationEntity p JOIN FETCH p.author a JOIN FETCH p.category C WHERE p.id = :publicationId") @Query("""
SELECT p
FROM PublicationEntity p
JOIN FETCH p.picture pi
JOIN FETCH p.author a
JOIN FETCH p.category c
WHERE p.id = :publicationId
""")
Optional<PublicationEntity> findById(@Param("publicationId") UUID publicationId); Optional<PublicationEntity> findById(@Param("publicationId") UUID publicationId);
} }
@@ -31,24 +31,27 @@ CREATE TABLE IF NOT EXISTS category (
); );
CREATE INDEX category_parent_category_id_idx ON category (parent_category_id); CREATE INDEX category_parent_category_id_idx ON category (parent_category_id);
CREATE TABLE IF NOT EXISTS picture (
id UUID NOT NULL,
CONSTRAINT picture_pk PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS publication ( CREATE TABLE IF NOT EXISTS publication (
id UUID NOT NULL, id UUID NOT NULL,
key VARCHAR(10) NOT NULL, key VARCHAR(10) NOT NULL,
title VARCHAR NOT NULL, title VARCHAR NOT NULL,
text VARCHAR NOT NULL, text VARCHAR NOT NULL,
description VARCHAR NOT NULL, description VARCHAR NOT NULL,
image VARCHAR NOT NULL,
creation_date TIMESTAMP NOT NULL, creation_date TIMESTAMP NOT NULL,
picture_id UUID NOT NULL,
author_id UUID NOT NULL, author_id UUID NOT NULL,
category_id UUID NOT NULL, category_id UUID NOT NULL,
CONSTRAINT publication_pk PRIMARY KEY (id), CONSTRAINT publication_pk PRIMARY KEY (id),
CONSTRAINT publication_picture_id_fk FOREIGN KEY (picture_id) REFERENCES picture (id),
CONSTRAINT publication_author_id_fk FOREIGN KEY (author_id) REFERENCES "user" (id), CONSTRAINT publication_author_id_fk FOREIGN KEY (author_id) REFERENCES "user" (id),
CONSTRAINT publication_category_id_fk FOREIGN KEY (category_id) REFERENCES category (id) CONSTRAINT publication_category_id_fk FOREIGN KEY (category_id) REFERENCES category (id)
); );
CREATE INDEX publication_picture_id_idx ON publication (picture_id);
CREATE INDEX publication_author_id_idx ON publication (author_id); CREATE INDEX publication_author_id_idx ON publication (author_id);
CREATE INDEX publication_category_id_idx ON publication (category_id); CREATE INDEX publication_category_id_idx ON publication (category_id);
CREATE TABLE IF NOT EXISTS picture (
id UUID NOT NULL,
CONSTRAINT picture_pk PRIMARY KEY (id)
);
+6
View File
@@ -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: e23831a6-9cc0-4f3d-9efa-7a1cae191cb1
bearerToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YWQ0NjJiOC04ZjllLTRhMjYtYmI4Ni1jNzRmZWY1ZDExYjYiLCJleHAiOjE3MTAzMzc2Njd9.ExV8xDeqqKk5WjIVb16NBqF1gPoRqx7uL4jQIhWjjY0QVhB5EAGdHMIbLr4s9Ck2f6z8U4sRlpPAQquDOr_9NA bearerToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YWQ0NjJiOC04ZjllLTRhMjYtYmI4Ni1jNzRmZWY1ZDExYjYiLCJleHAiOjE3MTA0MDc5Mjd9.QdanZXjfLztIVJU-pRS3gZxnC4GIycyhmlCkhYSPchiXpeNzruw_GY3fH1_qshC_AjOCSqGmCq3X1S_zVFdodQ
categoryId: 172fa901-3f4b-4540-92f3-1c15820e8ec9 categoryId: 172fa901-3f4b-4540-92f3-1c15820e8ec9
pictureId: 65b660b7-66bb-4e4a-a62c-fd0ca101f972
} }