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