diff --git a/codiki-application/pom.xml b/codiki-application/pom.xml
index 1e17c96..2f6f156 100644
--- a/codiki-application/pom.xml
+++ b/codiki-application/pom.xml
@@ -33,5 +33,15 @@
com.auth0
java-jwt
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
diff --git a/codiki-application/src/main/java/org/codiki/application/configuration/ServiceConfiguration.java b/codiki-application/src/main/java/org/codiki/application/configuration/ServiceConfiguration.java
index a3e308e..9a5921a 100644
--- a/codiki-application/src/main/java/org/codiki/application/configuration/ServiceConfiguration.java
+++ b/codiki-application/src/main/java/org/codiki/application/configuration/ServiceConfiguration.java
@@ -1,5 +1,7 @@
package org.codiki.application.configuration;
+import java.time.Clock;
+
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@@ -11,4 +13,9 @@ public class ServiceConfiguration {
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
+
+ @Bean
+ public Clock clock() {
+ return Clock.systemDefaultZone();
+ }
}
diff --git a/codiki-application/src/main/java/org/codiki/application/publication/KeyGenerator.java b/codiki-application/src/main/java/org/codiki/application/publication/KeyGenerator.java
new file mode 100644
index 0000000..d92afbc
--- /dev/null
+++ b/codiki-application/src/main/java/org/codiki/application/publication/KeyGenerator.java
@@ -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();
+ }
+}
diff --git a/codiki-application/src/main/java/org/codiki/application/publication/PublicationCreationRequestValidator.java b/codiki-application/src/main/java/org/codiki/application/publication/PublicationCreationRequestValidator.java
new file mode 100644
index 0000000..4b80371
--- /dev/null
+++ b/codiki-application/src/main/java/org/codiki/application/publication/PublicationCreationRequestValidator.java
@@ -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.");
+ }
+ }
+}
diff --git a/codiki-application/src/main/java/org/codiki/application/publication/PublicationUseCases.java b/codiki-application/src/main/java/org/codiki/application/publication/PublicationUseCases.java
new file mode 100644
index 0000000..6f3eb37
--- /dev/null
+++ b/codiki-application/src/main/java/org/codiki/application/publication/PublicationUseCases.java
@@ -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;
+ }
+}
diff --git a/codiki-application/src/test/java/org/codiki/application/publication/KeyGeneratorTest.java b/codiki-application/src/test/java/org/codiki/application/publication/KeyGeneratorTest.java
new file mode 100644
index 0000000..2e15770
--- /dev/null
+++ b/codiki-application/src/test/java/org/codiki/application/publication/KeyGeneratorTest.java
@@ -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();
+ });
+ }
+
+
+
+}
diff --git a/codiki-domain/src/main/java/org/codiki/domain/publication/model/Category.java b/codiki-domain/src/main/java/org/codiki/domain/category/model/Category.java
similarity index 75%
rename from codiki-domain/src/main/java/org/codiki/domain/publication/model/Category.java
rename to codiki-domain/src/main/java/org/codiki/domain/category/model/Category.java
index e837c4a..47ebf1d 100644
--- a/codiki-domain/src/main/java/org/codiki/domain/publication/model/Category.java
+++ b/codiki-domain/src/main/java/org/codiki/domain/category/model/Category.java
@@ -1,4 +1,4 @@
-package org.codiki.domain.publication.model;
+package org.codiki.domain.category.model;
import java.util.List;
import java.util.UUID;
diff --git a/codiki-domain/src/main/java/org/codiki/domain/category/port/CategoryPort.java b/codiki-domain/src/main/java/org/codiki/domain/category/port/CategoryPort.java
new file mode 100644
index 0000000..e22649e
--- /dev/null
+++ b/codiki-domain/src/main/java/org/codiki/domain/category/port/CategoryPort.java
@@ -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 findById(final UUID uuid);
+}
diff --git a/codiki-domain/src/main/java/org/codiki/domain/exception/AuthenticationRequiredException.java b/codiki-domain/src/main/java/org/codiki/domain/exception/AuthenticationRequiredException.java
new file mode 100644
index 0000000..a656e9b
--- /dev/null
+++ b/codiki-domain/src/main/java/org/codiki/domain/exception/AuthenticationRequiredException.java
@@ -0,0 +1,7 @@
+package org.codiki.domain.exception;
+
+public class AuthenticationRequiredException extends FunctionnalException {
+ public AuthenticationRequiredException() {
+ super("Authentication is required to perform this action.");
+ }
+}
diff --git a/codiki-domain/src/main/java/org/codiki/domain/publication/exception/PublicationCreationException.java b/codiki-domain/src/main/java/org/codiki/domain/publication/exception/PublicationCreationException.java
new file mode 100644
index 0000000..b1e2c27
--- /dev/null
+++ b/codiki-domain/src/main/java/org/codiki/domain/publication/exception/PublicationCreationException.java
@@ -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));
+ }
+}
diff --git a/codiki-domain/src/main/java/org/codiki/domain/publication/model/Author.java b/codiki-domain/src/main/java/org/codiki/domain/publication/model/Author.java
index 7b1fbef..b4d7c2a 100644
--- a/codiki-domain/src/main/java/org/codiki/domain/publication/model/Author.java
+++ b/codiki-domain/src/main/java/org/codiki/domain/publication/model/Author.java
@@ -2,9 +2,12 @@ package org.codiki.domain.publication.model;
import java.util.UUID;
+import org.codiki.domain.user.model.User;
+
public record Author(
UUID id,
String name,
String image
) {
+
}
diff --git a/codiki-domain/src/main/java/org/codiki/domain/publication/model/Publication.java b/codiki-domain/src/main/java/org/codiki/domain/publication/model/Publication.java
index 73f1e14..57bf2e8 100644
--- a/codiki-domain/src/main/java/org/codiki/domain/publication/model/Publication.java
+++ b/codiki-domain/src/main/java/org/codiki/domain/publication/model/Publication.java
@@ -3,6 +3,8 @@ package org.codiki.domain.publication.model;
import java.time.ZonedDateTime;
import java.util.UUID;
+import org.codiki.domain.category.model.Category;
+
public record Publication(
UUID id,
String key,
diff --git a/codiki-domain/src/main/java/org/codiki/domain/publication/model/PublicationCreationRequest.java b/codiki-domain/src/main/java/org/codiki/domain/publication/model/PublicationCreationRequest.java
new file mode 100644
index 0000000..3963543
--- /dev/null
+++ b/codiki-domain/src/main/java/org/codiki/domain/publication/model/PublicationCreationRequest.java
@@ -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
+) {}
diff --git a/codiki-domain/src/main/java/org/codiki/domain/publication/model/builder/AuthorBuilder.java b/codiki-domain/src/main/java/org/codiki/domain/publication/model/builder/AuthorBuilder.java
new file mode 100644
index 0000000..e373168
--- /dev/null
+++ b/codiki-domain/src/main/java/org/codiki/domain/publication/model/builder/AuthorBuilder.java
@@ -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);
+ }
+}
diff --git a/codiki-domain/src/main/java/org/codiki/domain/publication/model/builder/PublicationBuilder.java b/codiki-domain/src/main/java/org/codiki/domain/publication/model/builder/PublicationBuilder.java
new file mode 100644
index 0000000..3782bdc
--- /dev/null
+++ b/codiki-domain/src/main/java/org/codiki/domain/publication/model/builder/PublicationBuilder.java
@@ -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
+ );
+ }
+}
diff --git a/codiki-domain/src/main/java/org/codiki/domain/publication/port/PublicationPort.java b/codiki-domain/src/main/java/org/codiki/domain/publication/port/PublicationPort.java
new file mode 100644
index 0000000..a060c32
--- /dev/null
+++ b/codiki-domain/src/main/java/org/codiki/domain/publication/port/PublicationPort.java
@@ -0,0 +1,7 @@
+package org.codiki.domain.publication.port;
+
+import org.codiki.domain.publication.model.Publication;
+
+public interface PublicationPort {
+ void save(Publication publication);
+}