Add implementation of publication creation use case.
This commit is contained in:
@@ -33,5 +33,15 @@
|
|||||||
<groupId>com.auth0</groupId>
|
<groupId>com.auth0</groupId>
|
||||||
<artifactId>java-jwt</artifactId>
|
<artifactId>java-jwt</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.codiki.application.configuration;
|
package org.codiki.application.configuration;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
|
||||||
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.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
@@ -11,4 +13,9 @@ public class ServiceConfiguration {
|
|||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Clock clock() {
|
||||||
|
return Clock.systemDefaultZone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.codiki.application.publication;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class KeyGenerator {
|
||||||
|
private static final String ALLOWED_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
private static final int KEY_LENGTH = 10;
|
||||||
|
|
||||||
|
public String generateKey() {
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
StringBuilder code = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < KEY_LENGTH; i++) {
|
||||||
|
int randomIndex = random.nextInt(ALLOWED_CHARACTERS.length());
|
||||||
|
code.append(ALLOWED_CHARACTERS.charAt(randomIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
return code.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.codiki.application.publication;
|
||||||
|
|
||||||
|
import org.codiki.domain.publication.exception.PublicationCreationException;
|
||||||
|
import org.codiki.domain.publication.model.PublicationCreationRequest;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class PublicationCreationRequestValidator {
|
||||||
|
void isValid(PublicationCreationRequest request) {
|
||||||
|
if (request.title() == null) {
|
||||||
|
throw new PublicationCreationException("title cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.text() == null) {
|
||||||
|
throw new PublicationCreationException("text cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.description() == null) {
|
||||||
|
throw new PublicationCreationException("description cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.image() == null) {
|
||||||
|
throw new PublicationCreationException("image cannot be null.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package org.codiki.application.publication;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.codiki.domain.publication.model.builder.AuthorBuilder.anAuthor;
|
||||||
|
import static org.codiki.domain.publication.model.builder.PublicationBuilder.aPublication;
|
||||||
|
import org.codiki.application.user.UserUseCases;
|
||||||
|
import org.codiki.domain.category.model.Category;
|
||||||
|
import org.codiki.domain.category.port.CategoryPort;
|
||||||
|
import org.codiki.domain.exception.AuthenticationRequiredException;
|
||||||
|
import org.codiki.domain.publication.exception.PublicationCreationException;
|
||||||
|
import org.codiki.domain.publication.model.Publication;
|
||||||
|
import org.codiki.domain.publication.model.PublicationCreationRequest;
|
||||||
|
import org.codiki.domain.publication.port.PublicationPort;
|
||||||
|
import org.codiki.domain.user.model.User;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PublicationUseCases {
|
||||||
|
private final CategoryPort categoryPort;
|
||||||
|
private final KeyGenerator keyGenerator;
|
||||||
|
private final PublicationPort publicationPort;
|
||||||
|
private final PublicationCreationRequestValidator publicationCreationRequestValidator;
|
||||||
|
private final UserUseCases userUseCases;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
public PublicationUseCases(
|
||||||
|
CategoryPort categoryPort,
|
||||||
|
KeyGenerator keyGenerator,
|
||||||
|
PublicationPort publicationPort,
|
||||||
|
PublicationCreationRequestValidator publicationCreationRequestValidator,
|
||||||
|
UserUseCases userUseCases,
|
||||||
|
Clock clock
|
||||||
|
) {
|
||||||
|
this.publicationCreationRequestValidator = publicationCreationRequestValidator;
|
||||||
|
this.userUseCases = userUseCases;
|
||||||
|
this.keyGenerator = keyGenerator;
|
||||||
|
this.clock = clock;
|
||||||
|
this.categoryPort = categoryPort;
|
||||||
|
this.publicationPort = publicationPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Publication createPublication(PublicationCreationRequest request) {
|
||||||
|
publicationCreationRequestValidator.isValid(request);
|
||||||
|
|
||||||
|
User authenticatedUser = userUseCases.getAuthenticatedUser()
|
||||||
|
.orElseThrow(AuthenticationRequiredException::new);
|
||||||
|
|
||||||
|
Category category = categoryPort.findById(request.categoryId())
|
||||||
|
.orElseThrow(() -> new PublicationCreationException(
|
||||||
|
String.format("No any category exists for id %s", request.categoryId())
|
||||||
|
));
|
||||||
|
|
||||||
|
Publication newPublication = aPublication()
|
||||||
|
.withId(UUID.randomUUID())
|
||||||
|
.withKey(keyGenerator.generateKey())
|
||||||
|
.withTitle(request.title())
|
||||||
|
.withText(request.text())
|
||||||
|
.withDescription(request.description())
|
||||||
|
.withImage(request.image())
|
||||||
|
.withCreationDate(ZonedDateTime.now(clock))
|
||||||
|
.withAuthor(anAuthor().basedOn(authenticatedUser).build())
|
||||||
|
.withCategory(category)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
publicationPort.save(newPublication);
|
||||||
|
|
||||||
|
return newPublication;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.codiki.application.publication;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class KeyGeneratorTest {
|
||||||
|
private KeyGenerator generator;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
generator = new KeyGenerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void generateKey_should_generate_random_keys_with_alphanumeric_characters() {
|
||||||
|
Pattern validationRegex = Pattern.compile("^[0-9A-Z]{10}$");
|
||||||
|
|
||||||
|
IntStream.range(0, 1000)
|
||||||
|
.forEach(index -> {
|
||||||
|
String result = generator.generateKey();
|
||||||
|
assertThat(validationRegex.matcher(result).matches()).isTrue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.codiki.domain.publication.model;
|
package org.codiki.domain.category.model;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.codiki.domain.category.port;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.codiki.domain.category.model.Category;
|
||||||
|
|
||||||
|
public interface CategoryPort {
|
||||||
|
Optional<Category> findById(final UUID uuid);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.codiki.domain.exception;
|
||||||
|
|
||||||
|
public class AuthenticationRequiredException extends FunctionnalException {
|
||||||
|
public AuthenticationRequiredException() {
|
||||||
|
super("Authentication is required to perform this action.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.codiki.domain.publication.exception;
|
||||||
|
|
||||||
|
import org.codiki.domain.exception.FunctionnalException;
|
||||||
|
|
||||||
|
public class PublicationCreationException extends FunctionnalException {
|
||||||
|
public PublicationCreationException(String reason) {
|
||||||
|
super(String.format("Impossible to create a publication because : %s", reason));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,12 @@ package org.codiki.domain.publication.model;
|
|||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.codiki.domain.user.model.User;
|
||||||
|
|
||||||
public record Author(
|
public record Author(
|
||||||
UUID id,
|
UUID id,
|
||||||
String name,
|
String name,
|
||||||
String image
|
String image
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.codiki.domain.publication.model;
|
|||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.codiki.domain.category.model.Category;
|
||||||
|
|
||||||
public record Publication(
|
public record Publication(
|
||||||
UUID id,
|
UUID id,
|
||||||
String key,
|
String key,
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.codiki.domain.publication.model;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record PublicationCreationRequest(
|
||||||
|
String title,
|
||||||
|
String text,
|
||||||
|
String description,
|
||||||
|
String image,
|
||||||
|
UUID categoryId
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.codiki.domain.publication.model.builder;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.codiki.domain.publication.model.Author;
|
||||||
|
import org.codiki.domain.user.model.User;
|
||||||
|
|
||||||
|
public class AuthorBuilder {
|
||||||
|
private UUID id;
|
||||||
|
private String name;
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
private AuthorBuilder() {}
|
||||||
|
|
||||||
|
public static AuthorBuilder anAuthor() {
|
||||||
|
return new AuthorBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorBuilder basedOn(User user) {
|
||||||
|
return new AuthorBuilder()
|
||||||
|
.withId(user.id())
|
||||||
|
// .withName(user.name())
|
||||||
|
// .withImage(user.image())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorBuilder withId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public AuthorBuilder withName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public AuthorBuilder withImage(String image) {
|
||||||
|
this.image = image;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author build() {
|
||||||
|
return new Author(id, name, image);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package org.codiki.domain.publication.model.builder;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.codiki.domain.publication.model.Author;
|
||||||
|
import org.codiki.domain.category.model.Category;
|
||||||
|
import org.codiki.domain.publication.model.Publication;
|
||||||
|
|
||||||
|
public class PublicationBuilder {
|
||||||
|
private UUID id;
|
||||||
|
private String key;
|
||||||
|
private String title;
|
||||||
|
private String text;
|
||||||
|
private String description;
|
||||||
|
private String image;
|
||||||
|
private ZonedDateTime creationDate;
|
||||||
|
private Author author;
|
||||||
|
private Category category;
|
||||||
|
|
||||||
|
private PublicationBuilder() {}
|
||||||
|
|
||||||
|
public static PublicationBuilder aPublication() {
|
||||||
|
return new PublicationBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicationBuilder withId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicationBuilder withKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicationBuilder withTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicationBuilder withText(String text) {
|
||||||
|
this.text = text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicationBuilder withDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicationBuilder withImage(String image) {
|
||||||
|
this.image = image;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicationBuilder withCreationDate(ZonedDateTime creationDate) {
|
||||||
|
this.creationDate = creationDate;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicationBuilder withAuthor(Author author) {
|
||||||
|
this.author = author;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicationBuilder withCategory(Category category) {
|
||||||
|
this.category = category;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Publication build() {
|
||||||
|
return new Publication(
|
||||||
|
id,
|
||||||
|
key,
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
creationDate,
|
||||||
|
author,
|
||||||
|
category
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.codiki.domain.publication.port;
|
||||||
|
|
||||||
|
import org.codiki.domain.publication.model.Publication;
|
||||||
|
|
||||||
|
public interface PublicationPort {
|
||||||
|
void save(Publication publication);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user