diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4e78110ba..b0a13ef47 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -34,12 +34,7 @@ "forwardPorts": [4200, 8080], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "bash -c 'echo \"Installing dependencies...\" && cd frontend/blog-angular-ui && npm config set legacy-peer-deps true && npm install && npm install -g @angular/cli && echo \"Verifying installations...\" && ng version && java -version'", - - // Add mount for ~/.m2 to cache Maven dependencies - "mounts": [ - "source=${localEnv:HOME}${localEnv:USERPROFILE}/.m2,target=/home/vscode/.m2,type=bind,consistency=cached" - ], + "postCreateCommand": "bash -c 'echo \"Installing dependencies...\" && cd frontend/blog-angular-ui && npm install && npm install -g @angular/cli && echo \"Verifying installations...\" && ng version && java -version'", // Configure tool-specific properties. // "customizations": {}, diff --git a/poc-algorithms/pom.xml b/poc-algorithms/pom.xml index d5ca93085..69585ebd8 100644 --- a/poc-algorithms/pom.xml +++ b/poc-algorithms/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.8 + 4.0.0 diff --git a/poc-reactive-learning/pom.xml b/poc-reactive-learning/pom.xml index 21ca1a983..47ef53635 100644 --- a/poc-reactive-learning/pom.xml +++ b/poc-reactive-learning/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.8 + 4.0.0 diff --git a/poc-rest-api/pom.xml b/poc-rest-api/pom.xml index 081df9545..7d8609885 100644 --- a/poc-rest-api/pom.xml +++ b/poc-rest-api/pom.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 com.example.poc @@ -8,7 +8,7 @@ pom Spring Boot Rest Parent 0.4.0-SNAPSHOT - http://maven.apache.org + https://maven.apache.org spring-boot-rest-webmvc diff --git a/poc-rest-api/spring-boot-rest-webflux/pom.xml b/poc-rest-api/spring-boot-rest-webflux/pom.xml index 824fb8144..dd53effa5 100644 --- a/poc-rest-api/spring-boot-rest-webflux/pom.xml +++ b/poc-rest-api/spring-boot-rest-webflux/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.8 + 4.0.0 @@ -37,12 +37,8 @@ spring-boot-starter-data-r2dbc - org.liquibase - liquibase-core - - - org.springframework - spring-jdbc + org.springframework.boot + spring-boot-starter-liquibase org.postgresql @@ -59,24 +55,19 @@ mapstruct ${org.mapstruct.version} - - org.projectlombok - lombok - true - org.springframework.boot - spring-boot-starter-test + spring-boot-starter-webflux-test test org.springframework.boot - spring-boot-testcontainers + spring-boot-starter-data-r2dbc-test test - io.projectreactor - reactor-test + org.springframework.boot + spring-boot-testcontainers test @@ -86,17 +77,17 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - postgresql + testcontainers-postgresql test org.testcontainers - r2dbc + testcontainers-r2dbc test @@ -116,14 +107,6 @@ org.springframework.boot spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - @@ -135,28 +118,15 @@ org.apache.maven.plugins maven-compiler-plugin - - ${java.version} - ${java.version} + org.mapstruct mapstruct-processor ${org.mapstruct.version} - - - org.projectlombok - lombok - ${lombok.version} - - - - org.projectlombok - lombok-mapstruct-binding - 0.2.0 - + ${java.version} @@ -194,7 +164,7 @@ - 1.25.2 + 1.28.0 diff --git a/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/config/SecurityConfig.java b/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/config/SecurityConfig.java index 977156a63..46724c25c 100644 --- a/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/config/SecurityConfig.java +++ b/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/config/SecurityConfig.java @@ -1,27 +1,26 @@ /* Licensed under Apache-2.0 2021-2023 */ package com.example.poc.reactive.config; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.authorization.AuthorizationContext; import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository; -import reactor.core.publisher.Mono; -@Configuration -@Slf4j +@Configuration(proxyBeanMethods = false) class SecurityConfig { + private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class); + @Bean SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) { var postPath = "/posts/**"; @@ -39,19 +38,22 @@ SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) { .pathMatchers(postPath) .hasRole("USER") .pathMatchers("/users/{user}/**") - .access(this::currentUserMatchesPath) + .access( + (authentication, context) -> + authentication + .map( + auth -> + context.getVariables() + .get("user") + .equals( + auth + .getName())) + .map(AuthorizationDecision::new)) .anyExchange() .authenticated()) .build(); } - private Mono currentUserMatchesPath( - Mono authentication, AuthorizationContext context) { - return authentication - .map(a -> context.getVariables().get("user").equals(a.getName())) - .map(AuthorizationDecision::new); - } - @Bean PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); diff --git a/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/controller/PostClassicController.java b/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/controller/PostClassicController.java index 3d7725f4e..81de79b6d 100644 --- a/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/controller/PostClassicController.java +++ b/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/controller/PostClassicController.java @@ -5,7 +5,6 @@ import com.example.poc.reactive.entity.ReactivePost; import com.example.poc.reactive.service.PostService; import java.net.URI; -import lombok.RequiredArgsConstructor; import org.reactivestreams.Publisher; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -20,11 +19,14 @@ @RestController @RequestMapping("/posts") -@RequiredArgsConstructor public class PostClassicController { private final PostService postService; + public PostClassicController(PostService postService) { + this.postService = postService; + } + @GetMapping public Publisher all() { return this.postService.findAllPosts(); diff --git a/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/entity/ReactivePost.java b/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/entity/ReactivePost.java index c2ed0dc2c..260b28f83 100644 --- a/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/entity/ReactivePost.java +++ b/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/entity/ReactivePost.java @@ -2,11 +2,7 @@ package com.example.poc.reactive.entity; import java.time.LocalDateTime; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.ToString; +import java.util.Objects; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; @@ -15,12 +11,7 @@ import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Table; -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor @Table("reactive_posts") -@ToString public class ReactivePost { @Id @@ -53,4 +44,111 @@ public ReactivePost(String title, String content) { this.title = title; this.content = content; } + + public ReactivePost() {} + + public ReactivePost( + Integer id, + String title, + String content, + LocalDateTime createdAt, + String createdBy, + LocalDateTime updatedAt, + String updatedBy) { + this.id = id; + this.title = title; + this.content = content; + this.createdAt = createdAt; + this.createdBy = createdBy; + this.updatedAt = updatedAt; + this.updatedBy = updatedBy; + } + + public Integer getId() { + return id; + } + + public ReactivePost setId(Integer id) { + this.id = id; + return this; + } + + public String getTitle() { + return title; + } + + public ReactivePost setTitle(String title) { + this.title = title; + return this; + } + + public String getContent() { + return content; + } + + public ReactivePost setContent(String content) { + this.content = content; + return this; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public ReactivePost setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + return this; + } + + public String getCreatedBy() { + return createdBy; + } + + public ReactivePost setCreatedBy(String createdBy) { + this.createdBy = createdBy; + return this; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public ReactivePost setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + return this; + } + + public String getUpdatedBy() { + return updatedBy; + } + + public ReactivePost setUpdatedBy(String updatedBy) { + this.updatedBy = updatedBy; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof ReactivePost that)) return false; + + return Objects.equals(getId(), that.getId()) + && Objects.equals(getTitle(), that.getTitle()) + && Objects.equals(getContent(), that.getContent()) + && Objects.equals(getCreatedAt(), that.getCreatedAt()) + && Objects.equals(getCreatedBy(), that.getCreatedBy()) + && Objects.equals(getUpdatedAt(), that.getUpdatedAt()) + && Objects.equals(getUpdatedBy(), that.getUpdatedBy()); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(getId()); + result = 31 * result + Objects.hashCode(getTitle()); + result = 31 * result + Objects.hashCode(getContent()); + result = 31 * result + Objects.hashCode(getCreatedAt()); + result = 31 * result + Objects.hashCode(getCreatedBy()); + result = 31 * result + Objects.hashCode(getUpdatedAt()); + result = 31 * result + Objects.hashCode(getUpdatedBy()); + return result; + } } diff --git a/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/exception/PostNotFoundException.java b/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/exception/PostNotFoundException.java index 7a2f5f0e1..d141618aa 100644 --- a/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/exception/PostNotFoundException.java +++ b/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/exception/PostNotFoundException.java @@ -2,16 +2,20 @@ package com.example.poc.reactive.exception; import java.io.Serial; -import lombok.Getter; public class PostNotFoundException extends Exception { @Serial private static final long serialVersionUID = 1L; - @Getter private final String message; + private final String message; public PostNotFoundException(String exceptionMessage) { super(exceptionMessage); this.message = exceptionMessage; } + + @Override + public String getMessage() { + return message; + } } diff --git a/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/service/PostServiceImpl.java b/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/service/PostServiceImpl.java index 46702cf50..ac90dacc9 100644 --- a/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/service/PostServiceImpl.java +++ b/poc-rest-api/spring-boot-rest-webflux/src/main/java/com/example/poc/reactive/service/PostServiceImpl.java @@ -7,8 +7,8 @@ import com.example.poc.reactive.exception.PostNotFoundException; import com.example.poc.reactive.mapping.PostMapper; import com.example.poc.reactive.repository.PostRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -16,14 +16,22 @@ import reactor.core.publisher.Mono; @Service -@RequiredArgsConstructor -@Slf4j public class PostServiceImpl implements PostService { + private static final Logger log = LoggerFactory.getLogger(PostServiceImpl.class); private final PostRepository postRepository; private final ApplicationEventPublisher publisher; private final PostMapper postMapper; + public PostServiceImpl( + PostRepository postRepository, + ApplicationEventPublisher publisher, + PostMapper postMapper) { + this.postRepository = postRepository; + this.publisher = publisher; + this.postMapper = postMapper; + } + @Override public Flux findAllPosts() { return this.postRepository.findAll(); diff --git a/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/common/AbstractIntegrationTest.java b/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/common/AbstractIntegrationTest.java index da5899faa..88876ef13 100644 --- a/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/common/AbstractIntegrationTest.java +++ b/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/common/AbstractIntegrationTest.java @@ -3,8 +3,8 @@ import com.example.poc.reactive.TestApplication; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webtestclient.autoconfigure.AutoConfigureWebTestClient; import org.springframework.test.web.reactive.server.WebTestClient; @SpringBootTest( diff --git a/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/common/TestContainersConfig.java b/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/common/TestContainersConfig.java index f022eb67a..1905a2369 100644 --- a/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/common/TestContainersConfig.java +++ b/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/common/TestContainersConfig.java @@ -2,10 +2,10 @@ package com.example.poc.reactive.common; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.postgresql.PostgreSQLContainer; public interface TestContainersConfig { @ServiceConnection - PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer<>("postgres:18-alpine"); + PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:18-alpine"); } diff --git a/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/controller/PostClassicControllerIT.java b/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/controller/PostClassicControllerIT.java index 1d03a606c..479326ac6 100644 --- a/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/controller/PostClassicControllerIT.java +++ b/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/controller/PostClassicControllerIT.java @@ -26,18 +26,9 @@ void setUp() { .deleteAll() .thenMany( Flux.just( - ReactivePost.builder() - .title("title 1") - .content("content 1") - .build(), - ReactivePost.builder() - .title("title 2") - .content("content 2") - .build(), - ReactivePost.builder() - .title("title 3") - .content("content 3") - .build())) + new ReactivePost("title 1", "content 1"), + new ReactivePost("title 2", "content 2"), + new ReactivePost("title 3", "content 3"))) .flatMap(reactivePostRepository::save) .thenMany(reactivePostRepository.findAll()); } diff --git a/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/controller/PostsControllerEndpointsTest.java b/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/controller/PostsControllerEndpointsTest.java index f53286316..f9654f9c4 100644 --- a/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/controller/PostsControllerEndpointsTest.java +++ b/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/controller/PostsControllerEndpointsTest.java @@ -11,22 +11,20 @@ import java.util.UUID; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; -import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.webflux.test.autoconfigure.WebFluxTest; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.reactive.server.WebTestClient; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @WebFluxTest(controllers = {PostClassicController.class}) -@AutoConfigureWebTestClient @WithMockUser(username = "username") class PostsControllerEndpointsTest { - @MockBean private PostService postService; + @MockitoBean private PostService postService; @Autowired private WebTestClient webTestClient; @@ -36,8 +34,8 @@ void getAll() { given(this.postService.findAllPosts()) .willReturn( Flux.just( - ReactivePost.builder().id(1).content("A").build(), - ReactivePost.builder().id(2).content("B").build())); + new ReactivePost().setId(1).setContent("A"), + new ReactivePost().setId(2).setContent("B"))); this.webTestClient .get() @@ -62,7 +60,7 @@ void getAll() { @Test void save() { String content = UUID.randomUUID().toString(); - ReactivePost data = ReactivePost.builder().id(123).content(content).build(); + ReactivePost data = new ReactivePost().setId(123).setContent(content); PostDto postDto = new PostDto("title", content); given(this.postService.savePost(any(PostDto.class))).willReturn(Mono.just(data)); this.webTestClient @@ -79,8 +77,8 @@ void save() { @Test @WithMockUser(roles = {"ADMIN"}) void delete() { - ReactivePost data = - ReactivePost.builder().id(123).content(UUID.randomUUID().toString()).build(); + String content = UUID.randomUUID().toString(); + ReactivePost data = new ReactivePost().setId(123).setContent(content); given(this.postService.deletePostById(data.getId())) .willReturn(Mono.just(ResponseEntity.accepted().build())); this.webTestClient @@ -113,7 +111,7 @@ void update() { @Test void getById() { - ReactivePost data = ReactivePost.builder().id(1).content("A").build(); + ReactivePost data = new ReactivePost().setId(1).setContent("A"); given(this.postService.findPostById(data.getId())).willReturn(Mono.just(data)); diff --git a/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/repository/ReactivePostRepositoryTest.java b/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/repository/ReactivePostRepositoryTest.java index 4fa8505ae..52efd400d 100644 --- a/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/repository/ReactivePostRepositoryTest.java +++ b/poc-rest-api/spring-boot-rest-webflux/src/test/java/com/example/poc/reactive/repository/ReactivePostRepositoryTest.java @@ -9,16 +9,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest; +import org.springframework.boot.data.r2dbc.test.autoconfigure.DataR2dbcTest; import org.springframework.boot.testcontainers.context.ImportTestcontainers; import org.springframework.r2dbc.core.DatabaseClient; import reactor.core.publisher.Hooks; import reactor.test.StepVerifier; -@DataR2dbcTest( - properties = { - "spring.test.database.replace=none", - }) +@DataR2dbcTest @ImportTestcontainers(TestContainersConfig.class) class ReactivePostRepositoryTest {