diff --git a/src/main/java/de/sample/schulung/spring/blog/boundary/config/CorsConfiguration.java b/src/main/java/de/sample/schulung/spring/blog/boundary/config/CorsConfiguration.java
new file mode 100644
index 0000000..550ea2f
--- /dev/null
+++ b/src/main/java/de/sample/schulung/spring/blog/boundary/config/CorsConfiguration.java
@@ -0,0 +1,50 @@
+package de.sample.schulung.spring.blog.boundary.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import static java.util.Arrays.stream;
+
+@SuppressWarnings("NullableProblems")
+@Configuration
+public class CorsConfiguration {
+
+ @Bean
+ public WebMvcConfigurer corsConfigurer(final CorsConfigurationData allowed) {
+ return new WebMvcConfigurer() {
+
+ @Override
+ public void addCorsMappings(final CorsRegistry registry) {
+ registry.addMapping("/**")
+ .exposedHeaders(
+ HttpHeaders.LOCATION,
+ HttpHeaders.LINK
+ )
+ // allow all HTTP request methods
+ .allowedMethods(
+ stream(RequestMethod.values())
+ .map(Enum::name)
+ .toArray(String[]::new)
+ )
+ // allow the commonly used headers
+ .allowedHeaders(
+ HttpHeaders.ORIGIN,
+ HttpHeaders.CONTENT_TYPE,
+ HttpHeaders.CONTENT_LANGUAGE,
+ HttpHeaders.ACCEPT,
+ HttpHeaders.ACCEPT_LANGUAGE,
+ HttpHeaders.IF_MATCH,
+ HttpHeaders.IF_NONE_MATCH
+ )
+ // this is stage specific
+ .allowedOrigins(allowed.getOrigins())
+ .allowCredentials(allowed.isCredentials());
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/de/sample/schulung/spring/blog/boundary/config/CorsConfigurationData.java b/src/main/java/de/sample/schulung/spring/blog/boundary/config/CorsConfigurationData.java
new file mode 100644
index 0000000..4a8cdc6
--- /dev/null
+++ b/src/main/java/de/sample/schulung/spring/blog/boundary/config/CorsConfigurationData.java
@@ -0,0 +1,25 @@
+package de.sample.schulung.spring.blog.boundary.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * The properties from application.yml. You can specify them by the following snippet:
+ *
+ *
+ * server:
+ * endpoints:
+ * api:
+ * v1: /api/v1
+ *
+ */
+@Configuration
+@ConfigurationProperties(prefix = "cors.allow")
+@Data
+public class CorsConfigurationData {
+
+ private String[] origins = { "*" };
+ private boolean credentials = false;
+
+}
diff --git a/src/main/java/de/sample/schulung/spring/blog/boundary/config/IndexPageConfiguration.java b/src/main/java/de/sample/schulung/spring/blog/boundary/config/IndexPageConfiguration.java
new file mode 100644
index 0000000..f6c65a8
--- /dev/null
+++ b/src/main/java/de/sample/schulung/spring/blog/boundary/config/IndexPageConfiguration.java
@@ -0,0 +1,24 @@
+package de.sample.schulung.spring.blog.boundary.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@SuppressWarnings("NullableProblems")
+@Configuration
+public class IndexPageConfiguration {
+
+ @Bean
+ WebMvcConfigurer indexPageConfig() {
+ return new WebMvcConfigurer() {
+ @Override
+ public void addViewControllers(ViewControllerRegistry registry) {
+ registry
+ .addViewController("/")
+ .setViewName("redirect:/index.html");
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostService.java b/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostService.java
index ef3f880..b3b055f 100644
--- a/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostService.java
+++ b/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostService.java
@@ -7,9 +7,6 @@
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
@@ -19,28 +16,23 @@
@RequiredArgsConstructor
public class BlogPostService {
- private final Map blogPosts = new HashMap<>();
private final ApplicationEventPublisher eventPublisher;
+ private final BlogPostSink sink;
public long count() {
- return blogPosts.size();
+ return sink.count();
}
public Stream findAll() {
- return blogPosts.values().stream();
+ return sink.findAll();
}
public Optional findById(UUID id) {
- return Optional.ofNullable(
- this.blogPosts.get(id)
- );
+ return sink.findById(id);
}
public void create(@Valid @NotNull BlogPost blogPost) {
- final var id = UUID.randomUUID();
- blogPost.setId(id);
- blogPost.setTimestamp(LocalDateTime.now());
- this.blogPosts.put(id, blogPost);
+ this.sink.create(blogPost);
this.eventPublisher.publishEvent(new BlogPostCreatedEvent(blogPost));
}
diff --git a/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostSink.java b/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostSink.java
new file mode 100644
index 0000000..ea67490
--- /dev/null
+++ b/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostSink.java
@@ -0,0 +1,14 @@
+package de.sample.schulung.spring.blog.domain;
+
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+public interface BlogPostSink {
+
+ long count();
+ void create(BlogPost post);
+ Stream findAll();
+ Optional findById(UUID id);
+
+}
diff --git a/src/main/java/de/sample/schulung/spring/blog/domain/InMemoryBlogPostSink.java b/src/main/java/de/sample/schulung/spring/blog/domain/InMemoryBlogPostSink.java
new file mode 100644
index 0000000..5c16961
--- /dev/null
+++ b/src/main/java/de/sample/schulung/spring/blog/domain/InMemoryBlogPostSink.java
@@ -0,0 +1,39 @@
+package de.sample.schulung.spring.blog.domain;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+public class InMemoryBlogPostSink implements BlogPostSink {
+
+ private final Map blogPosts = new HashMap<>();
+
+ @Override
+ public long count() {
+ return this.blogPosts.size();
+ }
+
+ @Override
+ public void create(BlogPost post) {
+ final var id = UUID.randomUUID();
+ post.setId(id);
+ post.setTimestamp(LocalDateTime.now());
+ this.blogPosts.put(id, post);
+
+ }
+
+ @Override
+ public Stream findAll() {
+ return blogPosts.values().stream();
+ }
+
+ @Override
+ public Optional findById(UUID id) {
+ return Optional.ofNullable(
+ this.blogPosts.get(id)
+ );
+ }
+}
diff --git a/src/main/java/de/sample/schulung/spring/blog/domain/InMemoryBlogPostSinkConfiguration.java b/src/main/java/de/sample/schulung/spring/blog/domain/InMemoryBlogPostSinkConfiguration.java
new file mode 100644
index 0000000..715733c
--- /dev/null
+++ b/src/main/java/de/sample/schulung/spring/blog/domain/InMemoryBlogPostSinkConfiguration.java
@@ -0,0 +1,16 @@
+package de.sample.schulung.spring.blog.domain;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class InMemoryBlogPostSinkConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(BlogPostSink.class)
+ BlogPostSink inMemorySink() {
+ return new InMemoryBlogPostSink();
+ }
+
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index ee3322d..2b99f98 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -7,5 +7,6 @@ application:
content: This is a great blog :)
- title: BlogPost 2
content: Das ist ein weiterer Blog Post
-
-
+cors:
+ allow:
+ origins: ${CORS_ALLOW_ORIGINS:*}
diff --git a/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostControllerTests.java b/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostControllerTests.java
index 1100257..03c433a 100644
--- a/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostControllerTests.java
+++ b/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostControllerTests.java
@@ -1,6 +1,7 @@
package de.sample.schulung.spring.blog.boundary;
import de.sample.schulung.spring.blog.domain.BlogPostService;
+import de.sample.schulung.spring.blog.domain.InMemoryBlogPostSink;
import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
import org.mockito.Mockito;
@@ -14,7 +15,7 @@ public class BlogPostControllerTests {
@Test
void shouldCreateBlogPostSuccessfully() {
final var eventPublisher = Mockito.mock(ApplicationEventPublisher.class);
- final var service = new BlogPostService(eventPublisher);
+ final var service = new BlogPostService(eventPublisher, new InMemoryBlogPostSink());
final var mapper = Mappers.getMapper(BlogPostDtoMapper.class);
final var controller = new BlogPostController(service, mapper);
final var blogPost = new BlogPostDto();