Compare commits

5 Commits

Author SHA1 Message Date
Florian THIERRY
fb13cfd74d Implementation of getting publication by its id. 2024-03-14 10:05:36 +01:00
Florian THIERRY
8d8a220fa0 Add picture fetch while retrieving a publication. 2024-03-14 09:59:46 +01:00
Florian THIERRY
adc3cdf9a3 Add dependency to pictures in publications. 2024-03-14 09:55:51 +01:00
Florian THIERRY
5c5304ff98 Reworking the exception handling. 2024-03-14 09:31:14 +01:00
Florian THIERRY
a872a9fe33 Add picture format control. 2024-03-14 09:24:19 +01:00
26 changed files with 241 additions and 102 deletions

View File

@@ -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);
}
} }

View File

@@ -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");
} }
} }
} }

View File

@@ -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");

View File

@@ -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);
}
} }

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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()));
} }
} }

View File

@@ -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
) { ) {

View File

@@ -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
) {} ) {}

View File

@@ -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())
; ;
} }

View File

@@ -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());

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>-->

View File

@@ -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.
}
} }

View File

@@ -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()
); );

View File

@@ -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);
}
} }

View File

@@ -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());
}
} }

View File

@@ -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) {

View File

@@ -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())
); );

View File

@@ -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);
} }
} }

View File

@@ -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

View File

@@ -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()
); );
} }

View File

@@ -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()
); );

View File

@@ -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);
} }

View File

@@ -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)
);

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>

View File

@@ -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
} }