diff --git a/api-gateway/src/main/java/com/hoangtien2k3/apigateway/ApiGatewayApplication.java b/api-gateway/src/main/java/com/hoangtien2k3/apigateway/ApiGatewayApplication.java
index f16633ef..cac267bc 100644
--- a/api-gateway/src/main/java/com/hoangtien2k3/apigateway/ApiGatewayApplication.java
+++ b/api-gateway/src/main/java/com/hoangtien2k3/apigateway/ApiGatewayApplication.java
@@ -2,10 +2,8 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
-@EnableEurekaClient
public class ApiGatewayApplication {
public static void main(String[] args) {
diff --git a/build_output.txt b/build_output.txt
new file mode 100644
index 00000000..4a801f4d
Binary files /dev/null and b/build_output.txt differ
diff --git a/common-lib/pom.xml b/common-lib/pom.xml
index c5363121..57322a1f 100644
--- a/common-lib/pom.xml
+++ b/common-lib/pom.xml
@@ -46,11 +46,11 @@
org.springframework.kafka
spring-kafka
-
-
-
-
-
+
+ com.opencsv
+ opencsv
+ 5.9
+
diff --git a/discovery-service/src/main/java/com/hoangtien2k3/discoveryserver/DiscoveryServerApplication.java b/discovery-service/src/main/java/com/hoangtien2k3/discoveryserver/DiscoveryServerApplication.java
index 621606ce..2592cf6d 100644
--- a/discovery-service/src/main/java/com/hoangtien2k3/discoveryserver/DiscoveryServerApplication.java
+++ b/discovery-service/src/main/java/com/hoangtien2k3/discoveryserver/DiscoveryServerApplication.java
@@ -2,12 +2,10 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import java.util.TimeZone;
@SpringBootApplication
-@EnableEurekaServer
public class DiscoveryServerApplication {
public static void main(String[] args) {
diff --git a/discovery-service/src/main/resources/application.properties b/discovery-service/src/main/resources/application.properties
index 134aab63..23df2c97 100644
--- a/discovery-service/src/main/resources/application.properties
+++ b/discovery-service/src/main/resources/application.properties
@@ -1,11 +1,10 @@
-# default port for eureka server
-server.port=8761
+# default port for consul server
+server.port=8500
spring.application.name=DISCOVERY-SERVICE
-eureka.instance.hostname=localhost
-eureka.client.register-with-eureka=false
-eureka.client.fetch-registry=false
+spring.cloud.consul.host=localhost
+spring.cloud.consul.port=8500
-# Eureka server url
-eureka.client.serviceUrl.defaultZone=${EUREKA_URI:http://localhost:8761/eureka}
+spring.cloud.consul.discovery.register=true
+spring.cloud.consul.discovery.health-check-interval=10s
diff --git a/favourite-service/src/main/java/com/hoangtien2k3qx1/favouriteservice/FavouriteServiceApplication.java b/favourite-service/src/main/java/com/hoangtien2k3qx1/favouriteservice/FavouriteServiceApplication.java
index 24ab1e61..d2ea11c6 100644
--- a/favourite-service/src/main/java/com/hoangtien2k3qx1/favouriteservice/FavouriteServiceApplication.java
+++ b/favourite-service/src/main/java/com/hoangtien2k3qx1/favouriteservice/FavouriteServiceApplication.java
@@ -2,12 +2,10 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import java.util.TimeZone;
@SpringBootApplication
-@EnableEurekaClient
public class FavouriteServiceApplication {
public static void main(String[] args) {
diff --git a/favourite-service/src/main/java/com/hoangtien2k3qx1/favouriteservice/config/Swagger2Config.java b/favourite-service/src/main/java/com/hoangtien2k3qx1/favouriteservice/config/Swagger2Config.java
index 68a8dad3..bdea5265 100644
--- a/favourite-service/src/main/java/com/hoangtien2k3qx1/favouriteservice/config/Swagger2Config.java
+++ b/favourite-service/src/main/java/com/hoangtien2k3qx1/favouriteservice/config/Swagger2Config.java
@@ -1,36 +1,69 @@
package com.hoangtien2k3qx1.favouriteservice.config;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.servers.Server;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.service.ApiInfo;
-import springfox.documentation.service.Contact;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
+
+import java.util.List;
@Configuration
-@EnableWebMvc
public class Swagger2Config {
+
+ @Value("${server.port:8080}")
+ private String serverPort;
+
+ @Value("${spring.application.name:favourite-service}")
+ private String applicationName;
+
@Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2).select()
- .apis(RequestHandlerSelectors.any())
- .paths(PathSelectors.any())
- .build()
- .apiInfo(apiEndPointsInfo());
+ public OpenAPI customOpenAPI() {
+ return new OpenAPI()
+ .info(apiInfo())
+ .servers(getServers());
}
- private ApiInfo apiEndPointsInfo() {
- return new ApiInfoBuilder()
- .title("FAVORITE-SERVICE API")
- .description("API Documentation's FAVORITE-SERVICE")
- .contact(new Contact("hoangtien2k3", "https://hoangtien2k3qx1.github.io/", "hoangtien2k3qx1@gmail.com"))
- .license("Apache 2.0")
- .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
+ private Info apiInfo() {
+ return new Info()
+ .title("FAVORITE SERVICE API")
+ .description("""
+ ## Favorite Service API Documentation
+
+ This service handles all favorite-related operations including:
+ - Managing user favorites
+ - Tracking favorite items
+ - Favorite lists and collections
+
+ ### Features
+ - Add/remove items to favorites
+ - Organize favorites into lists
+ - Share favorite collections
+ - Integration with product and user services
+ """)
.version("1.0.0")
- .build();
+ .contact(new Contact()
+ .name("hoangtien2k3")
+ .url("https://hoangtien2k3qx1.github.io/")
+ .email("hoangtien2k3qx1@gmail.com"))
+ .license(new License()
+ .name("Apache 2.0")
+ .url("http://www.apache.org/licenses/LICENSE-2.0.html"));
+ }
+
+ private List getServers() {
+ Server localServer = new Server()
+ .url("http://localhost:" + serverPort)
+ .description("Local development server");
+
+ Server productionServer = new Server()
+ .url("https://api.yourdomain.com/favourite-service")
+ .description("Production server");
+
+ return List.of(localServer, productionServer);
}
-}
+}
\ No newline at end of file
diff --git a/inventory-service/src/main/java/com/hoangtien2k3/inventoryservice/InventoryServiceApplication.java b/inventory-service/src/main/java/com/hoangtien2k3/inventoryservice/InventoryServiceApplication.java
index d02f755f..4cbc8e09 100644
--- a/inventory-service/src/main/java/com/hoangtien2k3/inventoryservice/InventoryServiceApplication.java
+++ b/inventory-service/src/main/java/com/hoangtien2k3/inventoryservice/InventoryServiceApplication.java
@@ -2,10 +2,8 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
-@EnableEurekaClient
public class InventoryServiceApplication {
public static void main(String[] args) {
diff --git a/inventory-service/src/main/java/com/hoangtien2k3/inventoryservice/config/OpenApiConfig.java b/inventory-service/src/main/java/com/hoangtien2k3/inventoryservice/config/OpenApiConfig.java
new file mode 100644
index 00000000..3c4ca291
--- /dev/null
+++ b/inventory-service/src/main/java/com/hoangtien2k3/inventoryservice/config/OpenApiConfig.java
@@ -0,0 +1,70 @@
+package com.hoangtien2k3.inventoryservice.config;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.servers.Server;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@Configuration
+public class OpenApiConfig {
+
+ @Value("${server.port:8080}")
+ private String serverPort;
+
+ @Value("${spring.application.name:inventory-service}")
+ private String applicationName;
+
+ @Bean
+ public OpenAPI customOpenAPI() {
+ return new OpenAPI()
+ .info(apiInfo())
+ .servers(getServers());
+ }
+
+ private Info apiInfo() {
+ return new Info()
+ .title("INVENTORY SERVICE API")
+ .description("""
+ ## Inventory Service API Documentation
+
+ This service handles all inventory-related operations including:
+ - Product inventory management
+ - Stock level tracking
+ - Inventory updates and synchronization
+ - Product availability checks
+
+ ### Features
+ - Real-time inventory tracking
+ - Multi-warehouse support
+ - Stock alerts and notifications
+ - Integration with order and product services
+ """)
+ .version("1.0.0")
+ .contact(new Contact()
+ .name("hoangtien2k3")
+ .url("https://hoangtien2k3qx1.github.io/")
+ .email("hoangtien2k3qx1@gmail.com"))
+ .license(new License()
+ .name("Apache 2.0")
+ .url("http://www.apache.org/licenses/LICENSE-2.0.html"));
+ }
+
+ private List getServers() {
+ Server localServer = new Server()
+ .url("http://localhost:" + serverPort)
+ .description("Local development server");
+
+ Server productionServer = new Server()
+ .url("https://api.yourdomain.com/inventory-service")
+ .description("Production server");
+
+ return List.of(localServer, productionServer);
+ }
+}
\ No newline at end of file
diff --git a/inventory-service/src/main/java/com/hoangtien2k3/inventoryservice/config/Swagger2Config.java b/inventory-service/src/main/java/com/hoangtien2k3/inventoryservice/config/Swagger2Config.java
deleted file mode 100644
index 7b3b5d3c..00000000
--- a/inventory-service/src/main/java/com/hoangtien2k3/inventoryservice/config/Swagger2Config.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.hoangtien2k3.inventoryservice.config;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.service.ApiInfo;
-import springfox.documentation.service.Contact;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
-
-@Configuration
-@EnableWebMvc
-public class Swagger2Config {
- @Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2).select()
- .apis(RequestHandlerSelectors.any())
- .paths(PathSelectors.any())
- .build()
- .apiInfo(apiEndPointsInfo());
- }
-
- private ApiInfo apiEndPointsInfo() {
- return new ApiInfoBuilder()
- .title("INVENTORY-SERVICE API")
- .description("API Documentation's INVENTORY-SERVICE")
- .contact(new Contact("hoangtien2k3", "https://hoangtien2k3qx1.github.io/", "hoangtien2k3qx1@gmail.com"))
- .license("Apache 2.0")
- .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
- .version("1.0.0")
- .build();
- }
-}
diff --git a/media-service/src/main/java/com/hoangtien2k3/media/config/Hoangtien2k3Config.java b/media-service/src/main/java/com/hoangtien2k3/media/config/Hoangtien2k3Config.java
new file mode 100644
index 00000000..b42cb9d1
--- /dev/null
+++ b/media-service/src/main/java/com/hoangtien2k3/media/config/Hoangtien2k3Config.java
@@ -0,0 +1,15 @@
+package com.hoangtien2k3.media.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class Hoangtien2k3Config {
+
+ @Value("${app.public-url:http://localhost:8080}")
+ private String publicUrl;
+
+ public String publicUrl() {
+ return publicUrl;
+ }
+}
\ No newline at end of file
diff --git a/media-service/src/main/java/com/hoangtien2k3/media/service/MediaServiceImpl.java b/media-service/src/main/java/com/hoangtien2k3/media/service/MediaServiceImpl.java
index d5414209..0c9b1b4e 100644
--- a/media-service/src/main/java/com/hoangtien2k3/media/service/MediaServiceImpl.java
+++ b/media-service/src/main/java/com/hoangtien2k3/media/service/MediaServiceImpl.java
@@ -16,8 +16,10 @@
import lombok.SneakyThrows;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
+import com.hoangtien2k3.media.config.Hoangtien2k3Config;
import org.springframework.web.util.UriComponentsBuilder;
+
@RequiredArgsConstructor
@Service
public class MediaServiceImpl implements MediaService {
@@ -25,6 +27,7 @@ public class MediaServiceImpl implements MediaService {
private final MediaVmMapper mediaVmMapper;
private final MediaRepository mediaRepository;
private final FileSystemRepository fileSystemRepository;
+ private final Hoangtien2k3Config hoangtien2k3Config; // Added this
@Override
@SneakyThrows
diff --git a/notification-service/pom.xml b/notification-service/pom.xml
index 21b47180..d1f7bc75 100644
--- a/notification-service/pom.xml
+++ b/notification-service/pom.xml
@@ -30,6 +30,15 @@
springfox-swagger2
3.0.0
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+ com.sun.mail
+ jakarta.mail
+ 2.0.1
+
io.springfox
springfox-swagger-ui
@@ -64,6 +73,11 @@
spring-boot-starter-mail
2.5.6
+
+ org.eclipse.angus
+ angus-mail
+ 2.0.2
+
org.springframework
spring-core
diff --git a/notification-service/src/main/java/com/hoangtien2k3/notificationservice/service/impl/EmailServiceImpl.java b/notification-service/src/main/java/com/hoangtien2k3/notificationservice/service/impl/EmailServiceImpl.java
index 8fc84139..23e8d097 100644
--- a/notification-service/src/main/java/com/hoangtien2k3/notificationservice/service/impl/EmailServiceImpl.java
+++ b/notification-service/src/main/java/com/hoangtien2k3/notificationservice/service/impl/EmailServiceImpl.java
@@ -2,6 +2,8 @@
import com.hoangtien2k3.notificationservice.dto.EmailDetails;
import com.hoangtien2k3.notificationservice.service.EmailService;
+import jakarta.mail.MessagingException;
+import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@@ -14,8 +16,6 @@
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Mono;
-import javax.mail.MessagingException;
-import javax.mail.internet.MimeMessage;
import java.io.File;
import java.util.Arrays;
import java.util.Objects;
@@ -65,7 +65,7 @@ public Mono sendMailWithAttachment(EmailDetails details) {
javaMailSender.send(mimeMessage);
return "Mail Sent Successfully";
})
- .onErrorResume(MessagingException.class, ex -> {
+ .onErrorResume(ex -> {
log.error("Error while sending mail with attachment", ex);
return Mono.just("Error while Sending Mail with Attachment");
});
@@ -96,5 +96,4 @@ public Mono sendMail(MultipartFile[] files, String to, String[] cc, Stri
return Mono.just("Error while Sending Email");
});
}
-
-}
+}
\ No newline at end of file
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/OrderServiceApplication.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/OrderServiceApplication.java
index 7bf0f550..e2e2cc84 100644
--- a/order-service/src/main/java/com/hoangtien2k3/orderservice/OrderServiceApplication.java
+++ b/order-service/src/main/java/com/hoangtien2k3/orderservice/OrderServiceApplication.java
@@ -2,10 +2,8 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
-@EnableEurekaClient
public class OrderServiceApplication {
public static void main(String[] args) {
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/api/CartController.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/api/CartController.java
index f5962dc2..d1aebb99 100644
--- a/order-service/src/main/java/com/hoangtien2k3/orderservice/api/CartController.java
+++ b/order-service/src/main/java/com/hoangtien2k3/orderservice/api/CartController.java
@@ -3,10 +3,12 @@
import com.hoangtien2k3.orderservice.dto.order.CartDto;
import com.hoangtien2k3.orderservice.service.CartService;
import com.hoangtien2k3.orderservice.service.CallAPI;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiResponse;
-import io.swagger.annotations.ApiResponses;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
@@ -18,7 +20,7 @@
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
-import javax.validation.Valid;
+import jakarta.validation.Valid;
import java.util.Collections;
import java.util.List;
@@ -26,16 +28,16 @@
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/carts")
-@Tag(name = "CartController", description = "Operations related to carts")
+@Tag(name = "Cart Controller", description = "Cart management APIs")
public class CartController {
private final CartService cartService;
private final CallAPI testCallApi;
- @ApiOperation(value = "Get all carts", notes = "Retrieve a list of all carts.")
+ @Operation(summary = "Get all carts", description = "Retrieve a list of all carts.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Carts retrieved successfully", response = List.class),
- @ApiResponse(code = 204, message = "No content", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Carts retrieved successfully"),
+ @ApiResponse(responseCode = "204", description = "No content")
})
@GetMapping
@PreAuthorize("hasAuthority('ADMIN') or hasAuthority('USER')")
@@ -46,10 +48,10 @@ public Mono>> findAll() {
.defaultIfEmpty(ResponseEntity.ok(Collections.emptyList()));
}
- @ApiOperation(value = "Get all carts with paging", notes = "Retrieve a paginated list of all carts.")
+ @Operation(summary = "Get all carts with paging", description = "Retrieve a paginated list of all carts.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Carts retrieved successfully", response = Page.class),
- @ApiResponse(code = 204, message = "No content", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Carts retrieved successfully"),
+ @ApiResponse(responseCode = "204", description = "No content")
})
@GetMapping("/all")
@PreAuthorize("hasAuthority('ADMIN') or hasAuthority('USER')")
@@ -62,10 +64,10 @@ public Mono>> findAll(@RequestParam(defaultValue =
.defaultIfEmpty(ResponseEntity.noContent().build());
}
- @ApiOperation(value = "Get cart by ID", notes = "Retrieve cart information based on the provided ID.")
+ @Operation(summary = "Get cart by ID", description = "Retrieve cart information based on the provided ID.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Cart retrieved successfully", response = CartDto.class),
- @ApiResponse(code = 404, message = "Cart not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Cart retrieved successfully"),
+ @ApiResponse(responseCode = "404", description = "Cart not found")
})
@GetMapping("/{cartId}")
@PreAuthorize("hasAuthority('USER') or hasAuthority('ADMIN')")
@@ -76,11 +78,11 @@ public ResponseEntity> findById(@PathVariable("cartId")
return ResponseEntity.ok(this.cartService.findById(Integer.parseInt(cartId)));
}
-
- @ApiOperation(value = "Save cart", notes = "Save a new cart.")
+ @Operation(summary = "Save cart", description = "Save a new cart.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Cart saved successfully", response = CartDto.class),
- @ApiResponse(code = 500, message = "Internal Server Error", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "201", description = "Cart created successfully"),
+ @ApiResponse(responseCode = "400", description = "Bad request"),
+ @ApiResponse(responseCode = "500", description = "Internal Server Error")
})
@PostMapping
@PreAuthorize("hasAuthority('USER')")
@@ -89,14 +91,15 @@ public Mono> save(@RequestBody
@Valid final CartDto cartDto) {
log.info("*** CartDto, resource; save cart *");
return cartService.save(cartDto)
- .map(ResponseEntity::ok)
+ .map(cart -> ResponseEntity.status(HttpStatus.CREATED).body(cart))
.defaultIfEmpty(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
}
- @ApiOperation(value = "Update cart", notes = "Update an existing cart.")
+ @Operation(summary = "Update cart", description = "Update an existing cart.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Cart updated successfully", response = CartDto.class),
- @ApiResponse(code = 404, message = "Cart not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Cart updated successfully"),
+ @ApiResponse(responseCode = "404", description = "Cart not found"),
+ @ApiResponse(responseCode = "400", description = "Bad request")
})
@PutMapping
@PreAuthorize("hasAuthority('ADMIN')")
@@ -109,10 +112,11 @@ public Mono> update(@RequestBody
.defaultIfEmpty(ResponseEntity.notFound().build());
}
- @ApiOperation(value = "Update cart by ID", notes = "Update an existing cart based on the provided ID.")
+ @Operation(summary = "Update cart by ID", description = "Update an existing cart based on the provided ID.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Cart updated successfully", response = CartDto.class),
- @ApiResponse(code = 404, message = "Cart not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Cart updated successfully"),
+ @ApiResponse(responseCode = "404", description = "Cart not found"),
+ @ApiResponse(responseCode = "400", description = "Bad request")
})
@PutMapping("/{cartId}")
@PreAuthorize("hasAuthority('USER')")
@@ -128,10 +132,10 @@ public Mono> update(@PathVariable("cartId")
.defaultIfEmpty(ResponseEntity.notFound().build());
}
- @ApiOperation(value = "Delete cart by ID", notes = "Delete a cart based on the provided ID.")
+ @Operation(summary = "Delete cart by ID", description = "Delete a cart based on the provided ID.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Cart deleted successfully", response = Boolean.class),
- @ApiResponse(code = 404, message = "Cart not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Cart deleted successfully"),
+ @ApiResponse(responseCode = "404", description = "Cart not found")
})
@DeleteMapping("/{cartId}")
@PreAuthorize("hasAuthority('USER')")
@@ -141,5 +145,4 @@ public Mono> deleteById(@PathVariable("cartId") final St
.thenReturn(ResponseEntity.ok(true))
.defaultIfEmpty(ResponseEntity.status(HttpStatus.NOT_FOUND).body(false));
}
-
-}
+}
\ No newline at end of file
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/api/OrderController.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/api/OrderController.java
index 790e5bd1..ade69880 100644
--- a/order-service/src/main/java/com/hoangtien2k3/orderservice/api/OrderController.java
+++ b/order-service/src/main/java/com/hoangtien2k3/orderservice/api/OrderController.java
@@ -2,12 +2,12 @@
import com.hoangtien2k3.orderservice.dto.order.OrderDto;
import com.hoangtien2k3.orderservice.service.OrderService;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiResponse;
-import io.swagger.annotations.ApiResponses;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -15,9 +15,9 @@
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
-import javax.validation.Valid;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
import java.util.Collections;
import java.util.List;
@@ -25,15 +25,15 @@
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/orders")
-@Tag(name = "OrderController", description = "Operations related to orders")
+@Tag(name = "Order Controller", description = "Order management APIs")
public class OrderController {
private final OrderService orderService;
- @ApiOperation(value = "Get all orders", notes = "Retrieve a list of all orders.")
+ @Operation(summary = "Get all orders", description = "Retrieve a list of all orders.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Orders retrieved successfully", response = List.class),
- @ApiResponse(code = 204, message = "No content", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Orders retrieved successfully"),
+ @ApiResponse(responseCode = "204", description = "No content")
})
@GetMapping
@PreAuthorize("hasAuthority('ADMIN') or hasAuthority('USER')")
@@ -44,10 +44,10 @@ public Mono>> findAll() {
.defaultIfEmpty(ResponseEntity.ok(Collections.emptyList()));
}
- @ApiOperation(value = "Get all orders with paging", notes = "Retrieve a paginated list of all orders.")
+ @Operation(summary = "Get all orders with paging", description = "Retrieve a paginated list of all orders.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Orders retrieved successfully", response = Page.class),
- @ApiResponse(code = 204, message = "No content", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Orders retrieved successfully"),
+ @ApiResponse(responseCode = "204", description = "No content")
})
@GetMapping("/all")
@PreAuthorize("hasAuthority('ADMIN') or hasAuthority('USER')")
@@ -60,10 +60,10 @@ public Mono>> findAll(@RequestParam(defaultValue =
.defaultIfEmpty(ResponseEntity.noContent().build());
}
- @ApiOperation(value = "Get order by ID", notes = "Retrieve order information based on the provided ID.")
+ @Operation(summary = "Get order by ID", description = "Retrieve order information based on the provided ID.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Order retrieved successfully", response = OrderDto.class),
- @ApiResponse(code = 404, message = "Order not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Order retrieved successfully"),
+ @ApiResponse(responseCode = "404", description = "Order not found")
})
@GetMapping("/{orderId}")
@PreAuthorize("hasAuthority('ADMIN') or hasAuthority('USER')")
@@ -74,10 +74,11 @@ public ResponseEntity> findById(@PathVariable("orderId")
return ResponseEntity.ok(orderService.findById(Integer.parseInt(orderId)));
}
- @ApiOperation(value = "Save order", notes = "Save a new order.")
+ @Operation(summary = "Save order", description = "Save a new order.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Order saved successfully", response = OrderDto.class),
- @ApiResponse(code = 500, message = "Internal Server Error", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "201", description = "Order created successfully"),
+ @ApiResponse(responseCode = "400", description = "Bad request"),
+ @ApiResponse(responseCode = "500", description = "Internal Server Error")
})
@PostMapping
@PreAuthorize("hasAuthority('USER')")
@@ -86,14 +87,15 @@ public Mono> save(@RequestBody
@Valid final OrderDto orderDto) {
log.info("*** OrderDto, resource; save order *");
return orderService.save(orderDto)
- .map(ResponseEntity::ok)
+ .map(order -> ResponseEntity.status(HttpStatus.CREATED).body(order))
.defaultIfEmpty(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
}
- @ApiOperation(value = "Update order", notes = "Update an existing order.")
+ @Operation(summary = "Update order", description = "Update an existing order.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Order updated successfully", response = OrderDto.class),
- @ApiResponse(code = 404, message = "Order not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Order updated successfully"),
+ @ApiResponse(responseCode = "404", description = "Order not found"),
+ @ApiResponse(responseCode = "400", description = "Bad request")
})
@PutMapping
@PreAuthorize("hasAuthority('ADMIN')")
@@ -106,10 +108,11 @@ public Mono> update(@RequestBody
.defaultIfEmpty(ResponseEntity.notFound().build());
}
- @ApiOperation(value = "Update order by ID", notes = "Update an existing order based on the provided ID.")
+ @Operation(summary = "Update order by ID", description = "Update an existing order based on the provided ID.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Order updated successfully", response = OrderDto.class),
- @ApiResponse(code = 404, message = "Order not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Order updated successfully"),
+ @ApiResponse(responseCode = "404", description = "Order not found"),
+ @ApiResponse(responseCode = "400", description = "Bad request")
})
@PutMapping("/{orderId}")
@PreAuthorize("hasAuthority('USER')")
@@ -125,25 +128,23 @@ public Mono> update(@PathVariable("orderId")
.defaultIfEmpty(ResponseEntity.notFound().build());
}
- @ApiOperation(value = "Delete order by ID", notes = "Delete an order based on the provided ID.")
+ @Operation(summary = "Delete order by ID", description = "Delete an order based on the provided ID.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Order deleted successfully", response = Boolean.class),
- @ApiResponse(code = 404, message = "Order not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Order deleted successfully"),
+ @ApiResponse(responseCode = "404", description = "Order not found")
})
@DeleteMapping("/{orderId}")
@PreAuthorize("hasAuthority('USER') or hasAuthority('ADMIN')")
public Mono> deleteById(@PathVariable("orderId") final String orderId) {
log.info("*** Boolean, resource; delete order by id *");
- this.orderService.deleteById(Integer.parseInt(orderId));
return orderService.deleteById(Integer.parseInt(orderId))
.thenReturn(ResponseEntity.ok(true))
.defaultIfEmpty(ResponseEntity.status(HttpStatus.NOT_FOUND).body(false));
}
-
+ @Operation(summary = "Check if order exists", description = "Check if an order exists by ID.")
@GetMapping("/existOrderId")
- public Boolean existsByOrderId(Integer orderId) {
- return orderService.existsByOrderId(orderId);
+ public Mono existsByOrderId(@RequestParam Integer orderId) {
+ return Mono.just(orderService.existsByOrderId(orderId));
}
-
-}
+}
\ No newline at end of file
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/config/swagger/OpenApiConfig.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/config/swagger/OpenApiConfig.java
new file mode 100644
index 00000000..ccdbddf1
--- /dev/null
+++ b/order-service/src/main/java/com/hoangtien2k3/orderservice/config/swagger/OpenApiConfig.java
@@ -0,0 +1,84 @@
+package com.hoangtien2k3.orderservice.config.swagger;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@Configuration
+public class OpenApiConfig {
+
+ @Value("${server.port:8080}")
+ private String serverPort;
+
+ @Value("${spring.application.name:order-service}")
+ private String applicationName;
+
+ @Bean
+ public OpenAPI customOpenAPI() {
+ return new OpenAPI()
+ .info(apiInfo())
+ .addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
+ .components(new Components()
+ .addSecuritySchemes("Bearer Authentication", createBearerSecurityScheme()))
+ .servers(getServers());
+ }
+
+ private Info apiInfo() {
+ return new Info()
+ .title("ORDER SERVICE API")
+ .description("""
+ ## Order Service API Documentation
+
+ This service handles all order-related operations including:
+ - Order creation and management
+ - Order tracking and status updates
+ - Integration with cart and payment services
+ - Order history and reporting
+
+ ### Authentication
+ All endpoints require JWT Bearer token authentication.
+ Include the token in the Authorization header as: `Bearer {token}`
+ """)
+ .version("1.0.0")
+ .contact(new Contact()
+ .name("hoangtien2k3")
+ .url("https://hoangtien2k3qx1.github.io/")
+ .email("hoangtien2k3qx1@gmail.com"))
+ .license(new License()
+ .name("MIT License")
+ .url("https://mit-license.org/"))
+ .termsOfService("https://hoangtien2k3qx1.github.io/terms");
+ }
+
+ private SecurityScheme createBearerSecurityScheme() {
+ return new SecurityScheme()
+ .name("Bearer Authentication")
+ .type(SecurityScheme.Type.HTTP)
+ .scheme("bearer")
+ .bearerFormat("JWT")
+ .description("Enter your JWT token in the format: Bearer {token}")
+ .in(SecurityScheme.In.HEADER);
+ }
+
+ private List getServers() {
+ Server localServer = new Server()
+ .url("http://localhost:" + serverPort)
+ .description("Local development server");
+
+ Server productionServer = new Server()
+ .url("https://api.yourdomain.com/order-service")
+ .description("Production server");
+
+ return List.of(localServer, productionServer);
+ }
+}
\ No newline at end of file
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/config/swagger/Swagger2Config.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/config/swagger/Swagger2Config.java
deleted file mode 100644
index 23e9732e..00000000
--- a/order-service/src/main/java/com/hoangtien2k3/orderservice/config/swagger/Swagger2Config.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.hoangtien2k3.orderservice.config.swagger;
-
-import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
-import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
-import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
-import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
-import org.springframework.boot.actuate.endpoint.web.*;
-import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
-import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
-import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.env.Environment;
-import org.springframework.util.StringUtils;
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.service.*;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spi.service.contexts.SecurityContext;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
-import java.util.*;
-
-@Configuration
-@EnableSwagger2
-public class Swagger2Config {
- @Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2)
- .select()
- .apis(RequestHandlerSelectors.any())
- .paths(PathSelectors.any())
- .build()
- .apiInfo(apiEndPointsInfo())
- .securitySchemes(List.of(apiKey()))
- .securityContexts(Collections.singletonList(securityContext()));
- }
-
- private ApiInfo apiEndPointsInfo() {
- return new ApiInfoBuilder()
- .title("ORDER-SERVICE API")
- .description("API Documentation's ORDER-SERVICE")
- .contact(new Contact("hoangtien2k3", "https://hoangtien2k3qx1.github.io/", "hoangtien2k3qx1@gmail.com"))
- .license("MIT")
- .licenseUrl("https://mit-license.org/")
- .version("1.0.0")
- .build();
- }
-
- private ApiKey apiKey() {
- return new ApiKey("JWT", "Authorization", "header");
- }
-
- private SecurityContext securityContext() {
- return SecurityContext.builder()
- .securityReferences(defaultAuth())
- .build();
- }
-
- private List defaultAuth() {
- AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
- SecurityReference reference = new SecurityReference("JWT", new AuthorizationScope[]{authorizationScope});
- return List.of(reference);
- }
-
- @Bean
- public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
- ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
- EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
- WebEndpointProperties webEndpointProperties, Environment environment) {
- List> allEndpoints = new ArrayList<>();
- Collection webEndpoints = webEndpointsSupplier.getEndpoints();
- allEndpoints.addAll(webEndpoints);
- allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
- allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
- String basePath = webEndpointProperties.getBasePath();
- EndpointMapping endpointMapping = new EndpointMapping(basePath);
- boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment,
- basePath);
- return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
- corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
- shouldRegisterLinksMapping, null);
- }
-
- private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment,
- String basePath) {
- return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath)
- || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
- }
-
-}
\ No newline at end of file
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/config/web/WebSecurityConfig.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/config/web/WebSecurityConfig.java
index 277bebc7..3812e2c5 100644
--- a/order-service/src/main/java/com/hoangtien2k3/orderservice/config/web/WebSecurityConfig.java
+++ b/order-service/src/main/java/com/hoangtien2k3/orderservice/config/web/WebSecurityConfig.java
@@ -6,18 +6,18 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
-import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
-@EnableGlobalMethodSecurity(prePostEnabled = true)
-public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+@EnableMethodSecurity(prePostEnabled = true)
+public class WebSecurityConfig {
private final JwtEntryPoint jwtEntryPoint;
@@ -32,27 +32,38 @@ public JwtTokenFilter jwtTokenFilter() {
}
@Bean
- @Override
- public AuthenticationManager authenticationManager() throws Exception {
- return super.authenticationManager();
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
}
- @Override
- protected void configure(HttpSecurity httpSecurity) throws Exception {
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
- .csrf(AbstractHttpConfigurer::disable)
- .authorizeRequests()
- .antMatchers("/api/orders/**").authenticated()
- .antMatchers("/api/carts/**").authenticated()
- .antMatchers("/v2/api-docs", "/swagger-ui/**", "/swagger-resources/**", "/webjars/**").permitAll()
- .anyRequest().permitAll()
- .and()
- .sessionManagement()
- .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- .exceptionHandling().authenticationEntryPoint(jwtEntryPoint)
- .and()
+ .csrf(csrf -> csrf.disable())
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/api/orders/**").authenticated()
+ .requestMatchers("/api/carts/**").authenticated()
+ // SpringDoc OpenAPI endpoints
+ .requestMatchers(
+ "/v3/api-docs/**",
+ "/swagger-ui/**",
+ "/swagger-ui.html",
+ "/swagger-resources/**",
+ "/webjars/**",
+ "/api-docs/**"
+ ).permitAll()
+ // Actuator endpoints (optional)
+ .requestMatchers("/actuator/**").permitAll()
+ .anyRequest().permitAll()
+ )
+ .sessionManagement(session -> session
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ )
+ .exceptionHandling(exception -> exception
+ .authenticationEntryPoint(jwtEntryPoint)
+ )
.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
- }
-}
+ return httpSecurity.build();
+ }
+}
\ No newline at end of file
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/AbstractMappedEntity.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/AbstractMappedEntity.java
index 45b49bca..485e6525 100644
--- a/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/AbstractMappedEntity.java
+++ b/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/AbstractMappedEntity.java
@@ -9,8 +9,8 @@
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
-import javax.persistence.Column;
-import javax.persistence.MappedSuperclass;
+import jakarta.persistence.Column;
+import jakarta.persistence.MappedSuperclass;
import java.io.Serial;
import java.io.Serializable;
import java.time.Instant;
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/Cart.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/Cart.java
index 117838ea..77f43021 100644
--- a/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/Cart.java
+++ b/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/Cart.java
@@ -3,7 +3,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
-import javax.persistence.*;
+import jakarta.persistence.*;
import java.io.Serial;
import java.util.Set;
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/Order.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/Order.java
index 68db17d5..69c6365d 100644
--- a/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/Order.java
+++ b/order-service/src/main/java/com/hoangtien2k3/orderservice/entity/Order.java
@@ -13,7 +13,7 @@
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
-import javax.persistence.*;
+import jakarta.persistence.*;
import java.io.Serial;
import java.time.LocalDateTime;
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/security/JwtEntryPoint.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/security/JwtEntryPoint.java
index ce32605c..b529d4c6 100644
--- a/order-service/src/main/java/com/hoangtien2k3/orderservice/security/JwtEntryPoint.java
+++ b/order-service/src/main/java/com/hoangtien2k3/orderservice/security/JwtEntryPoint.java
@@ -6,9 +6,9 @@
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
diff --git a/order-service/src/main/java/com/hoangtien2k3/orderservice/security/JwtTokenFilter.java b/order-service/src/main/java/com/hoangtien2k3/orderservice/security/JwtTokenFilter.java
index bccb7bf1..27ee56ee 100644
--- a/order-service/src/main/java/com/hoangtien2k3/orderservice/security/JwtTokenFilter.java
+++ b/order-service/src/main/java/com/hoangtien2k3/orderservice/security/JwtTokenFilter.java
@@ -14,10 +14,10 @@
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.filter.OncePerRequestFilter;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtTokenFilter extends OncePerRequestFilter {
diff --git a/payment-service/pom.xml b/payment-service/pom.xml
index 34e5d596..234dd2d3 100644
--- a/payment-service/pom.xml
+++ b/payment-service/pom.xml
@@ -131,6 +131,11 @@
org.apache.kafka
kafka-clients
+
+ org.springdoc
+ springdoc-openapi-ui
+ 1.7.0
+
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/PaymentServiceApplication.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/PaymentServiceApplication.java
index 5d5c0806..0c91800b 100644
--- a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/PaymentServiceApplication.java
+++ b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/PaymentServiceApplication.java
@@ -2,10 +2,8 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
-@EnableEurekaClient
public class PaymentServiceApplication {
public static void main(String[] args) {
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/api/PaymentController.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/api/PaymentController.java
index 38731f72..f5228817 100644
--- a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/api/PaymentController.java
+++ b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/api/PaymentController.java
@@ -5,9 +5,11 @@
import com.hoangtien2k3.paymentservice.http.HeaderGenerator;
import com.hoangtien2k3.paymentservice.service.PaymentService;
import com.hoangtien2k3.paymentservice.service.impl.PaymentServiceImpl;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiResponse;
-import io.swagger.annotations.ApiResponses;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -18,9 +20,9 @@
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
-import javax.validation.Valid;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
import java.util.Collections;
import java.util.List;
@@ -39,10 +41,10 @@ public class PaymentController {
@Autowired
private final HeaderGenerator headerGenerator;
- @ApiOperation(value = "Get all payment", notes = "Retrieve a list of all payment.")
+ @Operation(summary = "Get all payments", description = "Retrieve a list of all payments.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Payments retrieved successfully", response = List.class),
- @ApiResponse(code = 204, message = "No content", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Payments retrieved successfully", content = @Content(schema = @Schema(implementation = List.class))),
+ @ApiResponse(responseCode = "204", description = "No content")
})
@GetMapping
@PreAuthorize("hasAuthority('ADMIN')")
@@ -53,10 +55,10 @@ public Mono>> findAll() {
.defaultIfEmpty(ResponseEntity.ok(Collections.emptyList()));
}
- @ApiOperation(value = "Get all payments with paging", notes = "Retrieve a paginated list of all payments.")
+ @Operation(summary = "Get all payments with paging", description = "Retrieve a paginated list of all payments.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Payments retrieved successfully", response = Page.class),
- @ApiResponse(code = 204, message = "No content", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Payments retrieved successfully", content = @Content(schema = @Schema(implementation = Page.class))),
+ @ApiResponse(responseCode = "204", description = "No content")
})
@GetMapping("/all")
@PreAuthorize("hasAuthority('ADMIN')")
@@ -69,10 +71,10 @@ public Mono>> findAll(@RequestParam(defaultValue
.defaultIfEmpty(ResponseEntity.notFound().build());
}
- @ApiOperation(value = "Get payment by ID", notes = "Retrieve cart information based on the provided ID.")
+ @Operation(summary = "Get payment by ID", description = "Retrieve payment information based on the provided ID.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Payment retrieved successfully", response = PaymentDto.class),
- @ApiResponse(code = 404, message = "Payment not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Payment retrieved successfully", content = @Content(schema = @Schema(implementation = PaymentDto.class))),
+ @ApiResponse(responseCode = "404", description = "Payment not found")
})
@GetMapping("/{paymentId}")
@PreAuthorize("hasAuthority('USER') or hasAuthority('ADMIN')")
@@ -83,17 +85,22 @@ public ResponseEntity> findById(@PathVariable("paymentId")
return ResponseEntity.ok(paymentService.findById(Integer.parseInt(paymentId)));
}
-
+ @Operation(summary = "Get order by ID", description = "Retrieve order information by order ID.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "Order retrieved successfully", content = @Content(schema = @Schema(implementation = OrderDto.class))),
+ @ApiResponse(responseCode = "404", description = "Order not found")
+ })
@GetMapping("/getOrder/{orderId}")
+ @PreAuthorize("hasAuthority('USER') or hasAuthority('ADMIN')")
public ResponseEntity> getOrderDto(@PathVariable("orderId") final Integer orderId) {
return ResponseEntity.ok(paymentServiceImpl.getOrderDto(orderId));
}
-
- @ApiOperation(value = "Save payment", notes = "Save a new payment.")
+ @Operation(summary = "Save payment", description = "Save a new payment.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Payment saved successfully", response = PaymentDto.class),
- @ApiResponse(code = 500, message = "Internal Server Error", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "201", description = "Payment created successfully", content = @Content(schema = @Schema(implementation = PaymentDto.class))),
+ @ApiResponse(responseCode = "400", description = "Bad request"),
+ @ApiResponse(responseCode = "500", description = "Internal Server Error")
})
@PostMapping
@PreAuthorize("hasAuthority('USER')")
@@ -102,14 +109,15 @@ public Mono> save(@RequestBody
@Valid final PaymentDto paymentDto) {
log.info("*** PaymentDto, resource; save payments *");
return paymentService.save(paymentDto)
- .map(ResponseEntity::ok)
+ .map(payment -> ResponseEntity.status(HttpStatus.CREATED).body(payment))
.defaultIfEmpty(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
}
- @ApiOperation(value = "Update payment", notes = "Update an existing payment.")
+ @Operation(summary = "Update payment", description = "Update an existing payment.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Payment updated successfully", response = PaymentDto.class),
- @ApiResponse(code = 404, message = "Payment not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Payment updated successfully", content = @Content(schema = @Schema(implementation = PaymentDto.class))),
+ @ApiResponse(responseCode = "404", description = "Payment not found"),
+ @ApiResponse(responseCode = "400", description = "Bad request")
})
@PutMapping
@PreAuthorize("hasAuthority('ADMIN')")
@@ -122,11 +130,11 @@ public Mono> update(@RequestBody
.defaultIfEmpty(ResponseEntity.notFound().build());
}
-
- @ApiOperation(value = "Update payment by ID", notes = "Update an existing cart based on the provided ID.")
+ @Operation(summary = "Update payment by ID", description = "Update an existing payment based on the provided ID.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Payment updated successfully", response = PaymentDto.class),
- @ApiResponse(code = 404, message = "Payment not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Payment updated successfully", content = @Content(schema = @Schema(implementation = PaymentDto.class))),
+ @ApiResponse(responseCode = "404", description = "Payment not found"),
+ @ApiResponse(responseCode = "400", description = "Bad request")
})
@PutMapping("/{paymentId}")
@PreAuthorize("hasAuthority('USER')")
@@ -142,10 +150,10 @@ public Mono> update(@PathVariable("paymentId")
.defaultIfEmpty(ResponseEntity.notFound().build());
}
- @ApiOperation(value = "Delete payment by ID", notes = "Delete a cart based on the provided ID.")
+ @Operation(summary = "Delete payment by ID", description = "Delete a payment based on the provided ID.")
@ApiResponses({
- @ApiResponse(code = 200, message = "Payment deleted successfully", response = Boolean.class),
- @ApiResponse(code = 404, message = "Payment not found", response = ResponseEntity.class)
+ @ApiResponse(responseCode = "200", description = "Payment deleted successfully", content = @Content(schema = @Schema(implementation = Boolean.class))),
+ @ApiResponse(responseCode = "404", description = "Payment not found")
})
@DeleteMapping("/{paymentId}")
@PreAuthorize("hasAuthority('USER')")
@@ -155,5 +163,4 @@ public Mono> deleteById(@PathVariable("paymentId") final
.thenReturn(ResponseEntity.ok(true))
.defaultIfEmpty(ResponseEntity.status(HttpStatus.NOT_FOUND).body(false));
}
-
}
\ No newline at end of file
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/config/swagger/Swagger2Config.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/config/swagger/Swagger2Config.java
deleted file mode 100644
index edcd5372..00000000
--- a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/config/swagger/Swagger2Config.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.hoangtien2k3.paymentservice.config.swagger;
-
-import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
-import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
-import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
-import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
-import org.springframework.boot.actuate.endpoint.web.*;
-import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
-import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
-import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.env.Environment;
-import org.springframework.util.StringUtils;
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.service.*;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spi.service.contexts.SecurityContext;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-@Configuration
-@EnableSwagger2
-public class Swagger2Config {
-
- @Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2)
- .select()
- .apis(RequestHandlerSelectors.any())
- .paths(PathSelectors.any())
- .build()
- .apiInfo(apiEndPointsInfo())
- .securitySchemes(List.of(apiKey()))
- .securityContexts(Collections.singletonList(securityContext()));
- }
-
- private ApiInfo apiEndPointsInfo() {
- return new ApiInfoBuilder()
- .title("ORDER-SERVICE API")
- .description("API Documentation's ORDER-SERVICE")
- .contact(new Contact("hoangtien2k3", "https://hoangtien2k3qx1.github.io/", "hoangtien2k3qx1@gmail.com"))
- .license("MIT")
- .licenseUrl("https://mit-license.org/")
- .version("1.0.0")
- .build();
- }
-
- private ApiKey apiKey() {
- return new ApiKey("JWT", "Authorization", "header");
- }
-
- private SecurityContext securityContext() {
- return SecurityContext.builder()
- .securityReferences(defaultAuth())
- .build();
- }
-
- private List defaultAuth() {
- AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
- SecurityReference reference = new SecurityReference("JWT", new AuthorizationScope[]{authorizationScope});
- return List.of(reference);
- }
-
- @Bean
- public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
- ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
- EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
- WebEndpointProperties webEndpointProperties, Environment environment) {
- List> allEndpoints = new ArrayList<>();
- Collection webEndpoints = webEndpointsSupplier.getEndpoints();
- allEndpoints.addAll(webEndpoints);
- allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
- allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
- String basePath = webEndpointProperties.getBasePath();
- EndpointMapping endpointMapping = new EndpointMapping(basePath);
- boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment,
- basePath);
- return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
- corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
- shouldRegisterLinksMapping);
- }
-
- private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment,
- String basePath) {
- return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath)
- || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
- }
-
-}
\ No newline at end of file
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/config/swagger/SwaggerConfig.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/config/swagger/SwaggerConfig.java
new file mode 100644
index 00000000..1a4de7bb
--- /dev/null
+++ b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/config/swagger/SwaggerConfig.java
@@ -0,0 +1,84 @@
+package com.hoangtien2k3.paymentservice.config.swagger;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@Configuration
+public class SwaggerConfig {
+
+ @Value("${server.port:8080}")
+ private String serverPort;
+
+ @Value("${spring.application.name:payment-service}")
+ private String applicationName;
+
+ @Bean
+ public OpenAPI customOpenAPI() {
+ return new OpenAPI()
+ .info(apiInfo())
+ .addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
+ .components(new Components()
+ .addSecuritySchemes("Bearer Authentication", createBearerSecurityScheme()))
+ .servers(getServers());
+ }
+
+ private Info apiInfo() {
+ return new Info()
+ .title("PAYMENT SERVICE API")
+ .description("""
+ ## Payment Service API Documentation
+
+ This service handles all payment-related operations including:
+ - Payment processing and management
+ - Payment status tracking
+ - Integration with order service
+ - Transaction history
+
+ ### Authentication
+ All endpoints require JWT Bearer token authentication.
+ Include the token in the Authorization header as: `Bearer {token}`
+ """)
+ .version("1.0.0")
+ .contact(new Contact()
+ .name("hoangtien2k3")
+ .url("https://hoangtien2k3qx1.github.io/")
+ .email("hoangtien2k3qx1@gmail.com"))
+ .license(new License()
+ .name("MIT License")
+ .url("https://mit-license.org/"))
+ .termsOfService("https://hoangtien2k3qx1.github.io/terms");
+ }
+
+ private SecurityScheme createBearerSecurityScheme() {
+ return new SecurityScheme()
+ .name("Bearer Authentication")
+ .type(SecurityScheme.Type.HTTP)
+ .scheme("bearer")
+ .bearerFormat("JWT")
+ .description("Enter your JWT token in the format: Bearer {token}")
+ .in(SecurityScheme.In.HEADER);
+ }
+
+ private List getServers() {
+ Server localServer = new Server()
+ .url("http://localhost:" + serverPort)
+ .description("Local development server");
+
+ Server productionServer = new Server()
+ .url("https://api.yourdomain.com/payment-service")
+ .description("Production server");
+
+ return List.of(localServer, productionServer);
+ }
+}
\ No newline at end of file
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/config/web/WebSecurityConfig.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/config/web/WebSecurityConfig.java
index 58176b7e..3b31ecc7 100644
--- a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/config/web/WebSecurityConfig.java
+++ b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/config/web/WebSecurityConfig.java
@@ -6,17 +6,18 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
-@EnableGlobalMethodSecurity(prePostEnabled = true)
-public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+@EnableMethodSecurity(prePostEnabled = true)
+public class WebSecurityConfig {
private final JwtEntryPoint jwtEntryPoint;
@@ -31,26 +32,38 @@ public JwtTokenFilter jwtTokenFilter() {
}
@Bean
- @Override
- public AuthenticationManager authenticationManager() throws Exception {
- return super.authenticationManager();
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
}
- @Override
- protected void configure(HttpSecurity httpSecurity) throws Exception {
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
- .csrf(AbstractHttpConfigurer::disable)
- .authorizeRequests()
- .antMatchers("/api/orders/**").authenticated()
- .antMatchers("/api/carts/**").authenticated()
- .antMatchers("/v2/api-docs", "/swagger-ui/**", "/swagger-resources/**", "/webjars/**").permitAll()
- .anyRequest().permitAll()
- .and()
- .sessionManagement()
- .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- .exceptionHandling().authenticationEntryPoint(jwtEntryPoint)
- .and()
+ .csrf(csrf -> csrf.disable())
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/api/orders/**").authenticated()
+ .requestMatchers("/api/carts/**").authenticated()
+ .requestMatchers("/api/payments/**").authenticated()
+ // SpringDoc OpenAPI endpoints
+ .requestMatchers(
+ "/v3/api-docs/**",
+ "/swagger-ui/**",
+ "/swagger-ui.html",
+ "/swagger-resources/**",
+ "/webjars/**"
+ ).permitAll()
+ // Actuator endpoints (optional)
+ .requestMatchers("/actuator/**").permitAll()
+ .anyRequest().permitAll()
+ )
+ .sessionManagement(session -> session
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ )
+ .exceptionHandling(exception -> exception
+ .authenticationEntryPoint(jwtEntryPoint)
+ )
.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
+
+ return httpSecurity.build();
}
-}
+}
\ No newline at end of file
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/entity/AbstractMappedEntity.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/entity/AbstractMappedEntity.java
index a63e24ba..59a39175 100644
--- a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/entity/AbstractMappedEntity.java
+++ b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/entity/AbstractMappedEntity.java
@@ -4,7 +4,7 @@
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
-import javax.persistence.Column;
+import jakarta.persistence.*;
import java.io.Serial;
import java.io.Serializable;
import java.time.Instant;
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/entity/Payment.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/entity/Payment.java
index 8cf3f037..88eba359 100644
--- a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/entity/Payment.java
+++ b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/entity/Payment.java
@@ -3,7 +3,7 @@
import lombok.*;
-import javax.persistence.*;
+import jakarta.persistence.*;
import java.io.Serial;
import java.io.Serializable;
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/http/HeaderGenerator.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/http/HeaderGenerator.java
index 1fa7f9d1..bd04b546 100644
--- a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/http/HeaderGenerator.java
+++ b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/http/HeaderGenerator.java
@@ -3,7 +3,7 @@
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
-import javax.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
import java.net.URI;
import java.net.URISyntaxException;
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/security/JwtEntryPoint.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/security/JwtEntryPoint.java
index 6ed7ee4b..cfa4227a 100644
--- a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/security/JwtEntryPoint.java
+++ b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/security/JwtEntryPoint.java
@@ -6,9 +6,9 @@
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/security/JwtTokenFilter.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/security/JwtTokenFilter.java
index 6d358ba9..a02121d5 100644
--- a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/security/JwtTokenFilter.java
+++ b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/security/JwtTokenFilter.java
@@ -15,10 +15,10 @@
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.filter.OncePerRequestFilter;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtTokenFilter extends OncePerRequestFilter {
diff --git a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/service/impl/PaymentServiceImpl.java b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/service/impl/PaymentServiceImpl.java
index f41b3792..f5037d8a 100644
--- a/payment-service/src/main/java/com/hoangtien2k3/paymentservice/service/impl/PaymentServiceImpl.java
+++ b/payment-service/src/main/java/com/hoangtien2k3/paymentservice/service/impl/PaymentServiceImpl.java
@@ -24,7 +24,7 @@
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
-import javax.transaction.Transactional;
+import jakarta.transaction.Transactional;
import java.util.List;
@Service
diff --git a/product-service/src/main/java/com/hoangtien2k3/productservice/ProductServiceApplication.java b/product-service/src/main/java/com/hoangtien2k3/productservice/ProductServiceApplication.java
index 4e7943fb..b5b2fcf7 100644
--- a/product-service/src/main/java/com/hoangtien2k3/productservice/ProductServiceApplication.java
+++ b/product-service/src/main/java/com/hoangtien2k3/productservice/ProductServiceApplication.java
@@ -2,10 +2,8 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
-@EnableEurekaClient
public class ProductServiceApplication {
public static void main(String[] args) {
diff --git a/product-service/src/main/java/com/hoangtien2k3/productservice/api/CategoryController.java b/product-service/src/main/java/com/hoangtien2k3/productservice/api/CategoryController.java
index 8db217e8..a9a6d256 100644
--- a/product-service/src/main/java/com/hoangtien2k3/productservice/api/CategoryController.java
+++ b/product-service/src/main/java/com/hoangtien2k3/productservice/api/CategoryController.java
@@ -14,9 +14,9 @@
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
-import javax.validation.Valid;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
import java.util.List;
@Slf4j
diff --git a/product-service/src/main/java/com/hoangtien2k3/productservice/api/ProductController.java b/product-service/src/main/java/com/hoangtien2k3/productservice/api/ProductController.java
index 3e4a2a0c..e50a82cb 100644
--- a/product-service/src/main/java/com/hoangtien2k3/productservice/api/ProductController.java
+++ b/product-service/src/main/java/com/hoangtien2k3/productservice/api/ProductController.java
@@ -10,9 +10,9 @@
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
-import javax.validation.Valid;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
import java.util.List;
diff --git a/product-service/src/main/java/com/hoangtien2k3/productservice/entity/AbstractMappedEntity.java b/product-service/src/main/java/com/hoangtien2k3/productservice/entity/AbstractMappedEntity.java
index abdecba2..b9078720 100644
--- a/product-service/src/main/java/com/hoangtien2k3/productservice/entity/AbstractMappedEntity.java
+++ b/product-service/src/main/java/com/hoangtien2k3/productservice/entity/AbstractMappedEntity.java
@@ -9,7 +9,7 @@
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
-import javax.persistence.*;
+import jakarta.persistence.*;
import java.io.Serial;
import java.io.Serializable;
import java.time.Instant;
diff --git a/product-service/src/main/java/com/hoangtien2k3/productservice/entity/Category.java b/product-service/src/main/java/com/hoangtien2k3/productservice/entity/Category.java
index f7a92692..b072c5ef 100644
--- a/product-service/src/main/java/com/hoangtien2k3/productservice/entity/Category.java
+++ b/product-service/src/main/java/com/hoangtien2k3/productservice/entity/Category.java
@@ -3,7 +3,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
-import javax.persistence.*;
+import jakarta.persistence.*;
import java.io.Serial;
import java.io.Serializable;
import java.util.Set;
diff --git a/product-service/src/main/java/com/hoangtien2k3/productservice/entity/Product.java b/product-service/src/main/java/com/hoangtien2k3/productservice/entity/Product.java
index d46de04c..e623529d 100644
--- a/product-service/src/main/java/com/hoangtien2k3/productservice/entity/Product.java
+++ b/product-service/src/main/java/com/hoangtien2k3/productservice/entity/Product.java
@@ -1,7 +1,7 @@
package com.hoangtien2k3.productservice.entity;
import lombok.*;
-import javax.persistence.*;
+import jakarta.persistence.*;
import java.io.Serial;
import java.io.Serializable;
diff --git a/promotion-service/pom.xml b/promotion-service/pom.xml
index a4caa042..6cf38c09 100644
--- a/promotion-service/pom.xml
+++ b/promotion-service/pom.xml
@@ -50,6 +50,15 @@
h2
test
+
+ io.github.resilience4j
+ resilience4j-spring-boot3
+ 2.2.0
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
diff --git a/promotion-service/src/main/java/com/hoangtien2k3/promotion/config/SecurityConfig.java b/promotion-service/src/main/java/com/hoangtien2k3/promotion/config/SecurityConfig.java
index a51da1d9..fbd7c103 100644
--- a/promotion-service/src/main/java/com/hoangtien2k3/promotion/config/SecurityConfig.java
+++ b/promotion-service/src/main/java/com/hoangtien2k3/promotion/config/SecurityConfig.java
@@ -7,7 +7,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
@@ -15,18 +15,35 @@
import org.springframework.security.web.SecurityFilterChain;
@Configuration
+@EnableWebSecurity
public class SecurityConfig {
+
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
- .authorizeRequests(auth -> auth
- .antMatchers("/actuator/prometheus", "/actuator/health/**",
- "/swagger-ui", "/swagger-ui/**", "/error", "/v3/api-docs/**").permitAll()
- .antMatchers("/storefront/**").permitAll()
- .antMatchers("/backoffice/**").hasRole("ADMIN")
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers(
+ "/actuator/prometheus",
+ "/actuator/health/**",
+ "/swagger-ui",
+ "/swagger-ui/**",
+ "/swagger-ui.html",
+ "/error",
+ "/v3/api-docs/**",
+ "/api-docs/**",
+ "/webjars/**",
+ "/swagger-resources/**"
+ ).permitAll()
+ .requestMatchers("/storefront/**").permitAll()
+ .requestMatchers("/backoffice/**").hasRole("ADMIN")
.anyRequest().authenticated())
- .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
- .csrf().disable();
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(jwt -> jwt
+ .jwtAuthenticationConverter(jwtAuthenticationConverterForKeycloak())
+ )
+ )
+ .csrf(csrf -> csrf.disable());
+
return http.build();
}
@@ -34,7 +51,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() {
Converter> jwtGrantedAuthoritiesConverter = jwt -> {
Map> realmAccess = jwt.getClaim("realm_access");
+ if (realmAccess == null) {
+ return java.util.Collections.emptyList();
+ }
+
Collection roles = realmAccess.get("roles");
+ if (roles == null) {
+ return java.util.Collections.emptyList();
+ }
+
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
@@ -45,4 +70,4 @@ public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() {
return jwtAuthenticationConverter;
}
-}
+}
\ No newline at end of file
diff --git a/promotion-service/src/main/java/com/hoangtien2k3/promotion/listener/CustomAuditingEntityListener.java b/promotion-service/src/main/java/com/hoangtien2k3/promotion/listener/CustomAuditingEntityListener.java
index 06d5f456..193d4a0f 100644
--- a/promotion-service/src/main/java/com/hoangtien2k3/promotion/listener/CustomAuditingEntityListener.java
+++ b/promotion-service/src/main/java/com/hoangtien2k3/promotion/listener/CustomAuditingEntityListener.java
@@ -6,8 +6,8 @@
import org.springframework.data.auditing.AuditingHandler;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
-import javax.persistence.PrePersist;
-import javax.persistence.PreUpdate;
+import jakarta.persistence.PrePersist;
+import jakarta.persistence.PreUpdate;
@Configurable
public class CustomAuditingEntityListener extends AuditingEntityListener {
diff --git a/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/AbstractAuditEntity.java b/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/AbstractAuditEntity.java
index 47cc1429..a9c131a1 100644
--- a/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/AbstractAuditEntity.java
+++ b/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/AbstractAuditEntity.java
@@ -9,8 +9,8 @@
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
-import javax.persistence.EntityListeners;
-import javax.persistence.MappedSuperclass;
+import jakarta.persistence.EntityListeners;
+import jakarta.persistence.MappedSuperclass;
@MappedSuperclass
@Getter
diff --git a/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/Promotion.java b/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/Promotion.java
index f45b93dc..8aaf5e63 100644
--- a/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/Promotion.java
+++ b/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/Promotion.java
@@ -13,7 +13,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
-import javax.persistence.*;
+import jakarta.persistence.*;
@Entity
@Table(name = "promotion")
diff --git a/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/PromotionApply.java b/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/PromotionApply.java
index 1e61abe5..6f2167d0 100644
--- a/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/PromotionApply.java
+++ b/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/PromotionApply.java
@@ -7,7 +7,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
-import javax.persistence.*;
+import jakarta.persistence.*;
@Entity
@Table(name = "promotion_apply")
diff --git a/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/PromotionUsage.java b/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/PromotionUsage.java
index 52b1692b..60d7478e 100644
--- a/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/PromotionUsage.java
+++ b/promotion-service/src/main/java/com/hoangtien2k3/promotion/model/PromotionUsage.java
@@ -7,7 +7,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
-import javax.persistence.*;
+import jakarta.persistence.*;
@Entity
@Table(name = "promotion_usage")
diff --git a/promotion-service/src/main/java/com/hoangtien2k3/promotion/validation/PromotionConstraint.java b/promotion-service/src/main/java/com/hoangtien2k3/promotion/validation/PromotionConstraint.java
index 72088bae..1e2c2fe4 100644
--- a/promotion-service/src/main/java/com/hoangtien2k3/promotion/validation/PromotionConstraint.java
+++ b/promotion-service/src/main/java/com/hoangtien2k3/promotion/validation/PromotionConstraint.java
@@ -1,7 +1,7 @@
package com.hoangtien2k3.promotion.validation;
-import javax.validation.Constraint;
-import javax.validation.Payload;
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/promotion-service/src/main/java/com/hoangtien2k3/promotion/viewmodel/PromotionPostVm.java b/promotion-service/src/main/java/com/hoangtien2k3/promotion/viewmodel/PromotionPostVm.java
index 9c5f9293..8121c7d0 100644
--- a/promotion-service/src/main/java/com/hoangtien2k3/promotion/viewmodel/PromotionPostVm.java
+++ b/promotion-service/src/main/java/com/hoangtien2k3/promotion/viewmodel/PromotionPostVm.java
@@ -14,9 +14,9 @@
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Size;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
diff --git a/promotion-service/src/main/java/com/hoangtien2k3/promotion/viewmodel/PromotionPutVm.java b/promotion-service/src/main/java/com/hoangtien2k3/promotion/viewmodel/PromotionPutVm.java
index fca627e1..88523a82 100644
--- a/promotion-service/src/main/java/com/hoangtien2k3/promotion/viewmodel/PromotionPutVm.java
+++ b/promotion-service/src/main/java/com/hoangtien2k3/promotion/viewmodel/PromotionPutVm.java
@@ -13,9 +13,9 @@
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Size;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
diff --git a/rating-service/src/main/java/com/hoangtien2k3/rating/config/SecurityConfig.java b/rating-service/src/main/java/com/hoangtien2k3/rating/config/SecurityConfig.java
index 02d6f8a8..cffd65f5 100644
--- a/rating-service/src/main/java/com/hoangtien2k3/rating/config/SecurityConfig.java
+++ b/rating-service/src/main/java/com/hoangtien2k3/rating/config/SecurityConfig.java
@@ -1,48 +1,45 @@
package com.hoangtien2k3.rating.config;
-import java.util.Collection;
-import java.util.Map;
-import java.util.stream.Collectors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.core.convert.converter.Converter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
+@EnableWebSecurity
public class SecurityConfig {
+
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
- .authorizeRequests(auth -> auth
- .antMatchers("/actuator/prometheus", "/actuator/health/**",
- "/swagger-ui", "/swagger-ui/**", "/error", "/v3/api-docs/**").permitAll()
- .antMatchers("/storefront/**").permitAll()
- .antMatchers("/backoffice/**").hasRole("ADMIN")
- .anyRequest().authenticated())
- .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
- .csrf().disable();
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers(
+ "/actuator/prometheus",
+ "/actuator/health/**",
+ "/swagger-ui",
+ "/swagger-ui/**",
+ "/swagger-ui.html",
+ "/error",
+ "/v3/api-docs/**"
+ ).permitAll()
+ .requestMatchers("/storefront/**").permitAll()
+ .requestMatchers("/backoffice/**").hasRole("ADMIN")
+ .anyRequest().authenticated()
+ )
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(jwt -> jwt
+ .jwtAuthenticationConverter(jwtAuthenticationConverterForKeycloak())
+ )
+ )
+ .csrf(csrf -> csrf.disable());
+
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() {
- Converter> jwtGrantedAuthoritiesConverter = jwt -> {
- Map> realmAccess = jwt.getClaim("realm_access");
- Collection roles = realmAccess.get("roles");
- return roles.stream()
- .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
- .collect(Collectors.toList());
- };
-
- var jwtAuthenticationConverter = new JwtAuthenticationConverter();
- jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
-
- return jwtAuthenticationConverter;
+ return new JwtAuthenticationConverter();
}
-}
+}
\ No newline at end of file
diff --git a/search-service/src/main/java/com/hoangtien2k3/search/config/SecurityConfig.java b/search-service/src/main/java/com/hoangtien2k3/search/config/SecurityConfig.java
index 80e32cf1..8ba0862a 100644
--- a/search-service/src/main/java/com/hoangtien2k3/search/config/SecurityConfig.java
+++ b/search-service/src/main/java/com/hoangtien2k3/search/config/SecurityConfig.java
@@ -8,7 +8,6 @@
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
@@ -18,17 +17,35 @@
@Configuration
@EnableWebSecurity
public class SecurityConfig {
+
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
- .authorizeRequests(auth -> auth
- .antMatchers("/actuator/prometheus", "/actuator/health/**",
- "/swagger-ui", "/swagger-ui/**", "/error", "/v3/api-docs/**").permitAll()
- .antMatchers("/storefront/**").permitAll()
- .antMatchers("/backoffice/**").hasRole("ADMIN")
- .anyRequest().authenticated())
- .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
- .csrf().disable();
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers(
+ "/actuator/prometheus",
+ "/actuator/health/**",
+ "/actuator/info",
+ "/swagger-ui",
+ "/swagger-ui/**",
+ "/swagger-ui.html",
+ "/error",
+ "/v3/api-docs/**",
+ "/api-docs/**",
+ "/webjars/**",
+ "/swagger-resources/**"
+ ).permitAll()
+ .requestMatchers("/storefront/**").permitAll()
+ .requestMatchers("/backoffice/**").hasRole("ADMIN")
+ .anyRequest().authenticated()
+ )
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(jwt -> jwt
+ .jwtAuthenticationConverter(jwtAuthenticationConverterForKeycloak())
+ )
+ )
+ .csrf(csrf -> csrf.disable());
+
return http.build();
}
@@ -36,15 +53,24 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() {
Converter> jwtGrantedAuthoritiesConverter = jwt -> {
Map> realmAccess = jwt.getClaim("realm_access");
+
+ if (realmAccess == null) {
+ return java.util.Collections.emptyList();
+ }
+
Collection roles = realmAccess.get("roles");
+ if (roles == null) {
+ return java.util.Collections.emptyList();
+ }
+
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
};
- var jwtAuthenticationConverter = new JwtAuthenticationConverter();
+ JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
-}
+}
\ No newline at end of file
diff --git a/search-service/src/test/java/com/hoangtien2k3/search/service/ProductServiceTest.java b/search-service/src/test/java/com/hoangtien2k3/search/service/ProductServiceTest.java
index e74bf18f..bd1fd859 100644
--- a/search-service/src/test/java/com/hoangtien2k3/search/service/ProductServiceTest.java
+++ b/search-service/src/test/java/com/hoangtien2k3/search/service/ProductServiceTest.java
@@ -33,7 +33,7 @@
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
-import javax.validation.constraints.NotNull;
+import jakarta.validation.constraints.NotNull;
class ProductServiceTest {
diff --git a/shipping-service/src/main/java/com/hoangtien2k3/shippingservice/ShippingServiceApplication.java b/shipping-service/src/main/java/com/hoangtien2k3/shippingservice/ShippingServiceApplication.java
index 150f76ca..ead06396 100644
--- a/shipping-service/src/main/java/com/hoangtien2k3/shippingservice/ShippingServiceApplication.java
+++ b/shipping-service/src/main/java/com/hoangtien2k3/shippingservice/ShippingServiceApplication.java
@@ -2,11 +2,9 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
-@EnableEurekaClient
-public class ShippingServiceApplication {
+class ShippingServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ShippingServiceApplication.class, args);
diff --git a/tax-service/pom.xml b/tax-service/pom.xml
index e11fe267..4c528bad 100644
--- a/tax-service/pom.xml
+++ b/tax-service/pom.xml
@@ -52,6 +52,11 @@
org.liquibase
liquibase-core
+
+ io.github.resilience4j
+ resilience4j-spring-boot3
+ 2.2.0
+
io.projectreactor.addons
reactor-extra
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/config/SecurityConfig.java b/tax-service/src/main/java/com/hoangtien2k3/tax/config/SecurityConfig.java
index 0788cfc3..da29593f 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/config/SecurityConfig.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/config/SecurityConfig.java
@@ -6,9 +6,8 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
-import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
@@ -16,19 +15,34 @@
import org.springframework.security.web.SecurityFilterChain;
@Configuration
+@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
- .authorizeRequests(auth -> auth
- .antMatchers("/actuator/prometheus", "/actuator/health/**",
- "/swagger-ui", "/swagger-ui/**", "/error", "/v3/api-docs/**").permitAll()
- .antMatchers("/storefront/**").permitAll()
- .antMatchers("/backoffice/**").hasRole("ADMIN")
- .anyRequest().authenticated())
- .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
- .csrf().disable();
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers(
+ "/actuator/prometheus",
+ "/actuator/health/**",
+ "/swagger-ui",
+ "/swagger-ui/**",
+ "/swagger-ui.html",
+ "/error",
+ "/v3/api-docs/**",
+ "/api-docs/**"
+ ).permitAll()
+ .requestMatchers("/storefront/**").permitAll()
+ .requestMatchers("/backoffice/**").hasRole("ADMIN")
+ .anyRequest().authenticated()
+ )
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(jwt -> jwt
+ .jwtAuthenticationConverter(jwtAuthenticationConverterForKeycloak())
+ )
+ )
+ .csrf(csrf -> csrf.disable());
+
return http.build();
}
@@ -36,7 +50,16 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() {
Converter> jwtGrantedAuthoritiesConverter = jwt -> {
Map> realmAccess = jwt.getClaim("realm_access");
+
+ if (realmAccess == null) {
+ return java.util.Collections.emptyList();
+ }
+
Collection roles = realmAccess.get("roles");
+ if (roles == null) {
+ return java.util.Collections.emptyList();
+ }
+
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
@@ -47,4 +70,4 @@ public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() {
return jwtAuthenticationConverter;
}
-}
+}
\ No newline at end of file
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/controller/TaxClassController.java b/tax-service/src/main/java/com/hoangtien2k3/tax/controller/TaxClassController.java
index 70af1e6e..238cee90 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/controller/TaxClassController.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/controller/TaxClassController.java
@@ -25,7 +25,7 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
-import javax.validation.Valid;
+import jakarta.validation.Valid;
@RestController
@RequestMapping(ApiConstant.TAX_CLASS_URL)
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/controller/TaxRateController.java b/tax-service/src/main/java/com/hoangtien2k3/tax/controller/TaxRateController.java
index baf6eb0f..4636b1ec 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/controller/TaxRateController.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/controller/TaxRateController.java
@@ -25,7 +25,7 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
-import javax.validation.Valid;
+import jakarta.validation.Valid;
@RestController
@RequestMapping(ApiConstant.TAX_RATE_URL)
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/exception/ApiExceptionHandler.java b/tax-service/src/main/java/com/hoangtien2k3/tax/exception/ApiExceptionHandler.java
index 4b570d9d..df4cdf2d 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/exception/ApiExceptionHandler.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/exception/ApiExceptionHandler.java
@@ -14,7 +14,7 @@
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
-import javax.validation.ConstraintViolationException;
+import jakarta.validation.ConstraintViolationException;
@ControllerAdvice
@Slf4j
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/listener/CustomAuditingEntityListener.java b/tax-service/src/main/java/com/hoangtien2k3/tax/listener/CustomAuditingEntityListener.java
index ec99bbd4..f55a4038 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/listener/CustomAuditingEntityListener.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/listener/CustomAuditingEntityListener.java
@@ -6,8 +6,8 @@
import org.springframework.data.auditing.AuditingHandler;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
-import javax.persistence.PrePersist;
-import javax.persistence.PreUpdate;
+import jakarta.persistence.PrePersist;
+import jakarta.persistence.PreUpdate;
@Configurable
public class CustomAuditingEntityListener extends AuditingEntityListener {
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/model/AbstractAuditEntity.java b/tax-service/src/main/java/com/hoangtien2k3/tax/model/AbstractAuditEntity.java
index f63dece0..a0aaf118 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/model/AbstractAuditEntity.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/model/AbstractAuditEntity.java
@@ -9,8 +9,8 @@
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
-import javax.persistence.EntityListeners;
-import javax.persistence.MappedSuperclass;
+import jakarta.persistence.EntityListeners;
+import jakarta.persistence.MappedSuperclass;
@MappedSuperclass
@Getter
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/model/TaxClass.java b/tax-service/src/main/java/com/hoangtien2k3/tax/model/TaxClass.java
index bbdb35b4..b0d8c372 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/model/TaxClass.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/model/TaxClass.java
@@ -7,7 +7,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
-import javax.persistence.*;
+import jakarta.persistence.*;
@Entity
@Table(name = "tax_class")
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/model/TaxRate.java b/tax-service/src/main/java/com/hoangtien2k3/tax/model/TaxRate.java
index ebd01ed9..f9b3bf30 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/model/TaxRate.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/model/TaxRate.java
@@ -7,7 +7,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
-import javax.persistence.*;
+import jakarta.persistence.*;
@Entity
@Table(name = "tax_rate")
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/service/LocationService.java b/tax-service/src/main/java/com/hoangtien2k3/tax/service/LocationService.java
index 9483b557..253d2496 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/service/LocationService.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/service/LocationService.java
@@ -2,43 +2,59 @@
import com.hoangtien2k3.tax.config.ServiceUrlConfig;
import com.hoangtien2k3.tax.viewmodel.location.StateOrProvinceAndCountryGetNameVm;
-import java.net.URI;
-import java.util.List;
-
+import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import lombok.RequiredArgsConstructor;
import org.springframework.core.ParameterizedTypeReference;
-import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestClient;
+import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
@Service
@RequiredArgsConstructor
-public class LocationService extends AbstractCircuitBreakFallbackHandler {
- private final RestClient restClient;
+public class LocationService {
+ private final RestTemplate restTemplate;
private final ServiceUrlConfig serviceUrlConfig;
@Retry(name = "restApi")
@CircuitBreaker(name = "restCircuitBreaker", fallbackMethod = "handleLocationNameListFallback")
public List getStateOrProvinceAndCountryNames(List stateOrProvinceIds) {
+ // Convert List to array for query params
+ Long[] idsArray = stateOrProvinceIds.toArray(new Long[0]);
+
final URI url = UriComponentsBuilder.fromHttpUrl(serviceUrlConfig.location())
- .path("/backoffice/state-or-provinces/state-country-names")
- .queryParam("stateOrProvinceIds", stateOrProvinceIds).build().toUri();
- final String jwt =
- ((Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getTokenValue();
- return restClient.get()
- .uri(url)
- .headers(h -> h.setBearerAuth(jwt))
- .retrieve()
- .body(new ParameterizedTypeReference>() {
- });
+ .path("/backoffice/state-or-provinces/state-country-names")
+ .queryParam("stateOrProvinceIds", (Object[]) idsArray)
+ .build()
+ .toUri();
+
+ final Jwt jwt = (Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setBearerAuth(jwt.getTokenValue());
+ HttpEntity> entity = new HttpEntity<>(headers);
+
+ return restTemplate.exchange(
+ url,
+ HttpMethod.GET,
+ entity,
+ new ParameterizedTypeReference>() {}
+ ).getBody();
}
- protected List handleLocationNameListFallback(Throwable throwable)
- throws Throwable {
- return handleTypedFallback(throwable);
+ protected List handleLocationNameListFallback(Throwable throwable) {
+ // Log error (use proper logger in production)
+ System.err.println("Circuit breaker fallback triggered: " + throwable.getMessage());
+ // Return empty list as fallback
+ return Collections.emptyList();
}
-}
+}
\ No newline at end of file
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/viewmodel/taxclass/TaxClassPostVm.java b/tax-service/src/main/java/com/hoangtien2k3/tax/viewmodel/taxclass/TaxClassPostVm.java
index db7659d8..862828f0 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/viewmodel/taxclass/TaxClassPostVm.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/viewmodel/taxclass/TaxClassPostVm.java
@@ -2,8 +2,8 @@
import com.hoangtien2k3.tax.model.TaxClass;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.Size;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
public record TaxClassPostVm(@NotBlank String id, @Size(min = 1, max = 450) String name) {
diff --git a/tax-service/src/main/java/com/hoangtien2k3/tax/viewmodel/taxrate/TaxRatePostVm.java b/tax-service/src/main/java/com/hoangtien2k3/tax/viewmodel/taxrate/TaxRatePostVm.java
index 9c15e187..6b7958eb 100644
--- a/tax-service/src/main/java/com/hoangtien2k3/tax/viewmodel/taxrate/TaxRatePostVm.java
+++ b/tax-service/src/main/java/com/hoangtien2k3/tax/viewmodel/taxrate/TaxRatePostVm.java
@@ -1,7 +1,7 @@
package com.hoangtien2k3.tax.viewmodel.taxrate;
-import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Size;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
public record TaxRatePostVm(@NotNull Double rate,
@Size(max = 25) String zipCode,
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/UserServiceApplication.java b/user-service/src/main/java/com/hoangtien2k3/userservice/UserServiceApplication.java
index 34b23a77..19f8ba23 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/UserServiceApplication.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/UserServiceApplication.java
@@ -2,10 +2,8 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
-@EnableEurekaClient
public class UserServiceApplication {
public static void main(String[] args) {
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/api/UserManager.java b/user-service/src/main/java/com/hoangtien2k3/userservice/api/UserManager.java
index ec591f5a..bc2fdc57 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/api/UserManager.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/api/UserManager.java
@@ -8,7 +8,11 @@
import com.hoangtien2k3.userservice.model.dto.response.ResponseMessage;
import com.hoangtien2k3.userservice.security.jwt.JwtProvider;
import com.hoangtien2k3.userservice.service.UserService;
-import io.swagger.annotations.*;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,115 +26,108 @@
@Slf4j
@RestController
@RequestMapping("/api/manager")
-@Api(value = "User API", description = "Operations related to users")
+@Tag(name = "User Management", description = "User management APIs")
public class UserManager {
- private final ModelMapper modelMapper;
+ private final ModelMapper modelMapper;
+ private final UserService userService;
+ private final HeaderGenerator headerGenerator;
+ private final JwtProvider jwtProvider;
- private final UserService userService;
- private final HeaderGenerator headerGenerator;
- private final JwtProvider jwtProvider;
+ @Autowired
+ public UserManager(UserService userService, HeaderGenerator headerGenerator, JwtProvider jwtProvider,
+ ModelMapper modelMapper) {
+ this.userService = userService;
+ this.headerGenerator = headerGenerator;
+ this.jwtProvider = jwtProvider;
+ this.modelMapper = modelMapper;
+ }
- @Autowired
- public UserManager(UserService userService, HeaderGenerator headerGenerator, JwtProvider jwtProvider,
- ModelMapper modelMapper) {
- this.userService = userService;
- this.headerGenerator = headerGenerator;
- this.jwtProvider = jwtProvider;
- this.modelMapper = modelMapper;
- }
+ @Operation(summary = "Update user information", description = "Update the user information with the provided details.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "User updated successfully"),
+ @ApiResponse(responseCode = "400", description = "Bad Request")
+ })
+ @PutMapping("update/{id}")
+ @PreAuthorize("isAuthenticated() and hasAuthority('USER')")
+ public Mono> update(@PathVariable("id") Long id,
+ @RequestBody SignUp updateDTO) {
+ return userService.update(id, updateDTO)
+ .flatMap(user -> Mono.just(new ResponseEntity<>(
+ new ResponseMessage("Update user: " + updateDTO.getUsername() + " successfully."),
+ HttpStatus.OK)))
+ .onErrorResume(error -> Mono.just(new ResponseEntity<>(
+ new ResponseMessage("Update user: " + updateDTO.getUsername() + " failed " + error.getMessage()),
+ HttpStatus.BAD_REQUEST)));
+ }
- @ApiOperation(value = "Update user information", notes = "Update the user information with the provided details.")
- @ApiResponses({
- @ApiResponse(code = 200, message = "User updated successfully", response = ResponseMessage.class),
- @ApiResponse(code = 400, message = "Bad Request", response = ResponseMessage.class)
- })
- @PutMapping("update/{id}")
- @PreAuthorize("isAuthenticated() and hasAuthority('USER')")
- public Mono> update(@PathVariable("id") Long id,
- @RequestBody SignUp updateDTO) {
- return userService.update(id, updateDTO)
- .flatMap(user -> Mono.just(new ResponseEntity<>(
- new ResponseMessage("Update user: " + updateDTO.getUsername()
- + " successfully."),
- HttpStatus.OK)))
- .onErrorResume(
- error -> Mono.just(new ResponseEntity<>(
- new ResponseMessage("Update user: "
- + updateDTO.getUsername() + " failed "
- + error.getMessage()),
- HttpStatus.BAD_REQUEST)));
- }
+ @Operation(summary = "Change user password", description = "Change the password for the authenticated user.")
+ @ApiResponse(responseCode = "200", description = "Password changed successfully")
+ @PutMapping("/change-password")
+ @PreAuthorize("isAuthenticated() and hasAuthority('USER')")
+ public Mono changePassword(@RequestBody ChangePasswordRequest request) {
+ return userService.changePassword(request);
+ }
- @ApiOperation(value = "Change user password", notes = "Change the password for the authenticated user.")
- @ApiResponse(code = 200, message = "Password changed successfully", response = String.class)
- @PutMapping("/change-password")
- @PreAuthorize("isAuthenticated() and hasAuthority('USER')")
- public Mono changePassword(@RequestBody ChangePasswordRequest request) {
- return userService.changePassword(request);
- }
+ @Operation(summary = "Delete user", description = "Delete a user with the specified ID.")
+ @DeleteMapping("delete/{id}")
+ @PreAuthorize("isAuthenticated() and (hasAuthority('USER') or hasAuthority('ADMIN'))")
+ public Mono delete(@PathVariable("id") Long id) {
+ return userService.delete(id);
+ }
- @ApiOperation(value = "Delete user", notes = "Delete a user with the specified ID.")
- @DeleteMapping("delete/{id}")
- @PreAuthorize("isAuthenticated() and (hasAuthority('USER') or hasAuthority('ADMIN'))")
- public Mono delete(@PathVariable("id") Long id) {
- return userService.delete(id);
- }
+ @Operation(summary = "Get user by username", description = "Retrieve user information based on the provided username.")
+ @GetMapping("/user")
+ @PreAuthorize("(isAuthenticated() and (hasAuthority('USER') and principal.username == #username) or hasAuthority('ADMIN'))")
+ public Mono> getUserByUsername(@RequestParam(value = "username") String username) {
+ return userService.findByUsername(username)
+ .map(user -> modelMapper.map(user, UserDto.class))
+ .map(userDto -> new ResponseEntity<>(userDto,
+ headerGenerator.getHeadersForSuccessGetMethod(), HttpStatus.OK))
+ .defaultIfEmpty(new ResponseEntity<>(null, headerGenerator.getHeadersForError(),
+ HttpStatus.NOT_FOUND));
+ }
- @ApiOperation(value = "Get user by username", notes = "Retrieve user information based on the provided username.")
- @GetMapping("/user")
- @PreAuthorize("(isAuthenticated() and (hasAuthority('USER') and principal.username == #username) or hasAuthority('ADMIN'))")
- public Mono> getUserByUsername(@RequestParam(value = "username") String username) {
- return userService.findByUsername(username)
- .map(user -> modelMapper.map(user, UserDto.class))
- .map(userDto -> new ResponseEntity<>(userDto,
- headerGenerator.getHeadersForSuccessGetMethod(), HttpStatus.OK))
- .defaultIfEmpty(new ResponseEntity<>(null, headerGenerator.getHeadersForError(),
- HttpStatus.NOT_FOUND));
- }
+ @Operation(summary = "Get user by ID", description = "Retrieve user information based on the provided ID.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "User retrieved successfully"),
+ @ApiResponse(responseCode = "404", description = "User not found")
+ })
+ @GetMapping("/user/{id}")
+ @PreAuthorize("hasAuthority('ADMIN') or hasAuthority('USER') and principal.id == #id")
+ public Mono> getUserById(@PathVariable("id") Long id) {
+ return userService.findById(id)
+ .map(user -> modelMapper.map(user, UserDto.class))
+ .map(userDto -> new ResponseEntity<>(userDto,
+ headerGenerator.getHeadersForSuccessGetMethod(), HttpStatus.OK))
+ .defaultIfEmpty(new ResponseEntity<>(null, headerGenerator.getHeadersForError(),
+ HttpStatus.NOT_FOUND));
+ }
- @ApiOperation(value = "Get user by ID", notes = "Retrieve user information based on the provided ID.")
- @ApiResponses({
- @ApiResponse(code = 200, message = "User retrieved successfully", response = UserDto.class),
- @ApiResponse(code = 404, message = "User not found", response = ResponseEntity.class)
- })
- @GetMapping("/user/{id}")
- @PreAuthorize("hasAuthority('ADMIN') or hasAuthority('USER') and principal.id == #id")
- public Mono> getUserById(@PathVariable("id") Long id) {
- return userService.findById(id)
- .map(user -> modelMapper.map(user, UserDto.class))
- .map(userDto -> new ResponseEntity<>(userDto,
- headerGenerator.getHeadersForSuccessGetMethod(), HttpStatus.OK))
- .defaultIfEmpty(new ResponseEntity<>(null, headerGenerator.getHeadersForError(),
- HttpStatus.NOT_FOUND));
- }
+ @Operation(summary = "Get all users", description = "Retrieve all users with pagination",
+ security = @SecurityRequirement(name = "Bearer Authentication"))
+ @GetMapping("/all")
+ @PreAuthorize("hasAuthority('ADMIN')")
+ public Mono>> getAllUsers(@RequestParam(defaultValue = "0") int page,
+ @RequestParam(defaultValue = "10") int size,
+ @RequestParam(defaultValue = "id") String sortBy,
+ @RequestParam(defaultValue = "ASC") String sortOrder) {
+ return userService.findAllUsers(page, size, sortBy, sortOrder)
+ .map(usersPage -> new ResponseEntity<>(usersPage,
+ headerGenerator.getHeadersForSuccessGetMethod(), HttpStatus.OK));
+ }
- @ApiOperation(value = "Get a secure user resource", authorizations = { @Authorization(value = "JWT") })
- @GetMapping("/all")
- @PreAuthorize("hasAuthority('ADMIN')")
- public Mono>> getAllUsers(@RequestParam(defaultValue = "0") int page,
- @RequestParam(defaultValue = "10") int size,
- @RequestParam(defaultValue = "id") String sortBy,
- @RequestParam(defaultValue = "ASC") String sortOrder) {
-
- return userService.findAllUsers(page, size, sortBy, sortOrder)
- .map(usersPage -> new ResponseEntity<>(usersPage,
- headerGenerator.getHeadersForSuccessGetMethod(), HttpStatus.OK));
- }
-
- @ApiOperation(value = "Get user information from token", notes = "Retrieve user information based on the provided JWT token.")
- @ApiResponses({
- @ApiResponse(code = 200, message = "User information retrieved successfully", response = UserDto.class),
- @ApiResponse(code = 404, message = "User not found", response = ResponseEntity.class)
- })
- @GetMapping("/info")
- public Mono> getUserInfo(@RequestHeader("Authorization") String token) {
- String username = jwtProvider.getUserNameFromToken(token);
- return userService.findByUsername(username)
- .map(user -> modelMapper.map(user, UserDto.class))
- .map(userDto -> new ResponseEntity<>(userDto,
- headerGenerator.getHeadersForSuccessGetMethod(), HttpStatus.OK))
- .switchIfEmpty(Mono
- .error(new TokenErrorOrAccessTimeOut("Token error or access timeout")));
- }
-
-}
+ @Operation(summary = "Get user information from token", description = "Retrieve user information based on the provided JWT token.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "User information retrieved successfully"),
+ @ApiResponse(responseCode = "404", description = "User not found")
+ })
+ @GetMapping("/info")
+ public Mono> getUserInfo(@RequestHeader("Authorization") String token) {
+ String username = jwtProvider.getUserNameFromToken(token);
+ return userService.findByUsername(username)
+ .map(user -> modelMapper.map(user, UserDto.class))
+ .map(userDto -> new ResponseEntity<>(userDto,
+ headerGenerator.getHeadersForSuccessGetMethod(), HttpStatus.OK))
+ .switchIfEmpty(Mono.error(new TokenErrorOrAccessTimeOut("Token error or access timeout")));
+ }
+}
\ No newline at end of file
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/config/Swagger2Config.java b/user-service/src/main/java/com/hoangtien2k3/userservice/config/Swagger2Config.java
index e327c1d9..967edc9d 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/config/Swagger2Config.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/config/Swagger2Config.java
@@ -1,94 +1,80 @@
package com.hoangtien2k3.userservice.config;
-import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
-import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
-import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
-import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
-import org.springframework.boot.actuate.endpoint.web.*;
-import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
-import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
-import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.core.env.Environment;
-import org.springframework.util.StringUtils;
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.service.*;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spi.service.contexts.SecurityContext;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-import java.util.*;
+import java.util.List;
@Configuration
-@EnableSwagger2
public class Swagger2Config {
+ @Value("${server.port:8080}")
+ private String serverPort;
+
@Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2)
- .select()
- .apis(RequestHandlerSelectors.any())
- .paths(PathSelectors.any())
- .build()
- .apiInfo(apiEndPointsInfo())
- .securitySchemes(List.of(apiKey()))
- .securityContexts(Collections.singletonList(securityContext()));
+ public OpenAPI customOpenAPI() {
+ return new OpenAPI()
+ .info(apiInfo())
+ .addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
+ .components(new Components()
+ .addSecuritySchemes("Bearer Authentication", createBearerSecurityScheme()))
+ .servers(getServers());
}
- private ApiInfo apiEndPointsInfo() {
- return new ApiInfoBuilder()
- .title("USER-SERVICE API DOCUMENTATIONS")
- .description("API Documentation's USER-SERVICE")
- .contact(new Contact("hoangtien2k3", "https://hoangtien2k3qx1.github.io/", "hoangtien2k3qx1@gmail.com"))
- .license("MIT")
- .licenseUrl("https://mit-license.org/")
+ private Info apiInfo() {
+ return new Info()
+ .title("USER SERVICE API")
+ .description("""
+ ## User Service API Documentation
+
+ This service handles all user-related operations including:
+ - User registration and authentication
+ - Profile management
+ - Role and permission management
+
+ ### Authentication
+ All endpoints require JWT Bearer token authentication.
+ Include the token in the Authorization header as: `Bearer {token}`
+ """)
.version("1.0.0")
- .build();
+ .contact(new Contact()
+ .name("hoangtien2k3")
+ .url("https://hoangtien2k3qx1.github.io/")
+ .email("hoangtien2k3qx1@gmail.com"))
+ .license(new License()
+ .name("MIT License")
+ .url("https://mit-license.org/"))
+ .termsOfService("https://hoangtien2k3qx1.github.io/terms");
}
- private ApiKey apiKey() {
- return new ApiKey("JWT", "Authorization", "header");
+ private SecurityScheme createBearerSecurityScheme() {
+ return new SecurityScheme()
+ .name("Bearer Authentication")
+ .type(SecurityScheme.Type.HTTP)
+ .scheme("bearer")
+ .bearerFormat("JWT")
+ .description("Enter your JWT token in the format: Bearer {token}")
+ .in(SecurityScheme.In.HEADER);
}
- private SecurityContext securityContext() {
- return SecurityContext.builder()
- .securityReferences(defaultAuth())
- .build();
- }
+ private List getServers() {
+ Server localServer = new Server()
+ .url("http://localhost:" + serverPort)
+ .description("Local development server");
- private List defaultAuth() {
- AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
- SecurityReference reference = new SecurityReference("JWT", new AuthorizationScope[]{authorizationScope});
- return List.of(reference);
- }
+ Server productionServer = new Server()
+ .url("https://api.yourdomain.com/user-service")
+ .description("Production server");
- @Bean
- public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
- ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
- EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
- WebEndpointProperties webEndpointProperties, Environment environment) {
- List> allEndpoints = new ArrayList<>();
- Collection webEndpoints = webEndpointsSupplier.getEndpoints();
- allEndpoints.addAll(webEndpoints);
- allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
- allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
- String basePath = webEndpointProperties.getBasePath();
- EndpointMapping endpointMapping = new EndpointMapping(basePath);
- boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment,
- basePath);
- return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
- corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
- shouldRegisterLinksMapping, null);
+ return List.of(localServer, productionServer);
}
-
- private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment,
- String basePath) {
- return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath)
- || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
- }
-
}
\ No newline at end of file
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/config/WebSecurityConfig.java b/user-service/src/main/java/com/hoangtien2k3/userservice/config/WebSecurityConfig.java
index 41fa850b..118fa17e 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/config/WebSecurityConfig.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/config/WebSecurityConfig.java
@@ -9,22 +9,21 @@
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
-import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
-@EnableGlobalMethodSecurity(prePostEnabled = true)
-public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+@EnableMethodSecurity(prePostEnabled = true)
+public class WebSecurityConfig {
private final UserDetailService userDetailService;
private final JwtEntryPoint jwtEntryPoint;
@@ -46,21 +45,13 @@ public PasswordEncoder passwordEncoder() {
}
@Bean
- @Override
- public AuthenticationManager authenticationManager() throws Exception {
- return super.authenticationManager();
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public UserDetailsService userDetailsService() {
- return new UserDetailService();
- }
-
- @Override
- public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
- authenticationManagerBuilder
- .userDetailsService(userDetailService)
- .passwordEncoder(passwordEncoder());
+ return userDetailService;
}
@Bean
@@ -71,29 +62,40 @@ public AuthenticationProvider authenticationProvider() {
return authenticationProvider;
}
- @Override
- protected void configure(HttpSecurity httpSecurity) throws Exception {
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
- .csrf(AbstractHttpConfigurer::disable)
- .authorizeRequests()
- .antMatchers("/api/auth/**").permitAll()
- .antMatchers("/api/manager/token").permitAll()
- .antMatchers("/api/manager/change-password").authenticated()
- .antMatchers("/api/manager/delete/**").authenticated()
- .antMatchers("/api/auth/logout").authenticated()
- .antMatchers("/api/manager/user/**").permitAll()
- .antMatchers("/v2/api-docs", "/swagger-ui/**", "/swagger-resources/**", "/webjars/**").permitAll()
- .anyRequest().authenticated()
- .and()
- .sessionManagement()
- .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- .exceptionHandling().authenticationEntryPoint(jwtEntryPoint)
- .and()
+ .csrf(csrf -> csrf.disable())
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers(
+ "/api/auth/**",
+ "/api/manager/token",
+ "/api/manager/user/**"
+ ).permitAll()
+ .requestMatchers(
+ "/api/manager/change-password",
+ "/api/manager/delete/**",
+ "/api/auth/logout"
+ ).authenticated()
+ .requestMatchers(
+ "/v3/api-docs/**",
+ "/swagger-ui/**",
+ "/swagger-ui.html",
+ "/swagger-resources/**",
+ "/webjars/**",
+ "/api-docs/**"
+ ).permitAll()
+ .anyRequest().authenticated()
+ )
+ .sessionManagement(session -> session
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ )
+ .exceptionHandling(exception -> exception
+ .authenticationEntryPoint(jwtEntryPoint)
+ )
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
- httpSecurity.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
+ return httpSecurity.build();
}
-
-}
+}
\ No newline at end of file
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/http/HeaderGenerator.java b/user-service/src/main/java/com/hoangtien2k3/userservice/http/HeaderGenerator.java
index aca8b47a..c4f9d47e 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/http/HeaderGenerator.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/http/HeaderGenerator.java
@@ -3,7 +3,7 @@
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
-import javax.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
import java.net.URI;
import java.net.URISyntaxException;
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/model/dto/request/SignUp.java b/user-service/src/main/java/com/hoangtien2k3/userservice/model/dto/request/SignUp.java
index 12b9ad17..697f6f8f 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/model/dto/request/SignUp.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/model/dto/request/SignUp.java
@@ -5,9 +5,9 @@
import lombok.RequiredArgsConstructor;
import lombok.Setter;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.Pattern;
-import javax.validation.constraints.Size;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
import java.util.Set;
@Setter
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/model/dto/response/ResponseMessage.java b/user-service/src/main/java/com/hoangtien2k3/userservice/model/dto/response/ResponseMessage.java
index 4a9e74c3..0fa352bc 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/model/dto/response/ResponseMessage.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/model/dto/response/ResponseMessage.java
@@ -2,7 +2,7 @@
import lombok.*;
-import javax.validation.constraints.Size;
+import jakarta.validation.constraints.Size;
@Getter
@Setter
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/model/entity/Role.java b/user-service/src/main/java/com/hoangtien2k3/userservice/model/entity/Role.java
index 495bab55..6a7c3394 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/model/entity/Role.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/model/entity/Role.java
@@ -4,7 +4,7 @@
import lombok.experimental.Accessors;
import org.hibernate.annotations.NaturalId;
-import javax.persistence.*;
+import jakarta.persistence.*;
@Getter
@Setter
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/model/entity/User.java b/user-service/src/main/java/com/hoangtien2k3/userservice/model/entity/User.java
index fec8bd8e..51a1a2fd 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/model/entity/User.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/model/entity/User.java
@@ -4,8 +4,8 @@
import lombok.*;
import org.hibernate.annotations.NaturalId;
-import javax.persistence.*;
-import javax.validation.constraints.*;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
import java.util.HashSet;
import java.util.Set;
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/security/jwt/JwtEntryPoint.java b/user-service/src/main/java/com/hoangtien2k3/userservice/security/jwt/JwtEntryPoint.java
index 2066464e..0d89c72d 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/security/jwt/JwtEntryPoint.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/security/jwt/JwtEntryPoint.java
@@ -6,9 +6,9 @@
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/security/jwt/JwtTokenFilter.java b/user-service/src/main/java/com/hoangtien2k3/userservice/security/jwt/JwtTokenFilter.java
index 7da5aeb3..66187059 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/security/jwt/JwtTokenFilter.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/security/jwt/JwtTokenFilter.java
@@ -10,10 +10,10 @@
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtTokenFilter extends OncePerRequestFilter {
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/security/userprinciple/UserDetailService.java b/user-service/src/main/java/com/hoangtien2k3/userservice/security/userprinciple/UserDetailService.java
index e9f93669..c6a31bc7 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/security/userprinciple/UserDetailService.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/security/userprinciple/UserDetailService.java
@@ -7,8 +7,7 @@
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
-
-import javax.transaction.Transactional;
+import org.springframework.transaction.annotation.Transactional;
@Service
public class UserDetailService implements UserDetailsService {
@@ -40,4 +39,4 @@ public UserDetails loadUserByPhone(String phone) {
return UserPrinciple.build(user);
}
-}
+}
\ No newline at end of file
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/service/UserService.java b/user-service/src/main/java/com/hoangtien2k3/userservice/service/UserService.java
index 8a965949..39229059 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/service/UserService.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/service/UserService.java
@@ -11,21 +11,16 @@
public interface UserService {
Mono register(SignUp signUp);
-
Mono login(Login signInForm);
-
Mono logout();
-
Mono update(Long userId, SignUp update);
-
Mono changePassword(ChangePasswordRequest request);
-
- // Mono resetPassword(ResetPasswordRequest resetPasswordRequest);
Mono delete(Long id);
-
Mono findById(Long userId);
-
Mono findByUsername(String userName);
-
Mono> findAllUsers(int page, int size, String sortBy, String sortOrder);
+
+ boolean existsByUsername(String username);
+ boolean existsByEmail(String email);
+ boolean existsByPhoneNumber(String phone);
}
\ No newline at end of file
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/service/impl/RoleServiceImpl.java b/user-service/src/main/java/com/hoangtien2k3/userservice/service/impl/RoleServiceImpl.java
index 00f5aa4b..0dd9a824 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/service/impl/RoleServiceImpl.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/service/impl/RoleServiceImpl.java
@@ -11,7 +11,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import javax.transaction.Transactional;
+import jakarta.transaction.Transactional;
+
import java.util.*;
@Service
diff --git a/user-service/src/main/java/com/hoangtien2k3/userservice/service/impl/UserServiceImpl.java b/user-service/src/main/java/com/hoangtien2k3/userservice/service/impl/UserServiceImpl.java
index 6456ef21..1397e514 100644
--- a/user-service/src/main/java/com/hoangtien2k3/userservice/service/impl/UserServiceImpl.java
+++ b/user-service/src/main/java/com/hoangtien2k3/userservice/service/impl/UserServiceImpl.java
@@ -20,7 +20,6 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.*;
-import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -31,13 +30,14 @@
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
-import javax.transaction.Transactional;
+import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@Service
+@Transactional
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@@ -45,30 +45,31 @@ public class UserServiceImpl implements UserService {
private final UserDetailService userDetailsService;
private final ModelMapper modelMapper;
private final RoleService roleService;
-
- Gson gson = new Gson(); // google.code.gson
- @Autowired
- EventProducer eventProducer;
-
- @Autowired
- private WebClient.Builder webClientBuilder;
-
+ private final WebClient.Builder webClientBuilder;
+ private final EventProducer eventProducer;
+
+ private final Gson gson = new Gson();
+
@Value("${refresh.token.url}")
private String refreshTokenUrl;
@Autowired
public UserServiceImpl(UserRepository userRepository,
- PasswordEncoder passwordEncoder,
- JwtProvider jwtProvider,
- UserDetailService userDetailsService,
- ModelMapper modelMapper,
- RoleService roleService) {
+ PasswordEncoder passwordEncoder,
+ JwtProvider jwtProvider,
+ UserDetailService userDetailsService,
+ ModelMapper modelMapper,
+ RoleService roleService,
+ WebClient.Builder webClientBuilder,
+ EventProducer eventProducer) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.jwtProvider = jwtProvider;
this.userDetailsService = userDetailsService;
this.modelMapper = modelMapper;
this.roleService = roleService;
+ this.webClientBuilder = webClientBuilder;
+ this.eventProducer = eventProducer;
}
@Override
@@ -112,7 +113,7 @@ private RoleName mapToRoleName(String roleName) {
public Mono login(Login signInForm) {
return Mono.fromCallable(() -> {
String usernameOrEmail = signInForm.getUsername();
- boolean isEmail = usernameOrEmail.contains("@gmail.com");
+ boolean isEmail = usernameOrEmail.contains("@");
UserDetails userDetails;
if (isEmail) {
@@ -156,7 +157,7 @@ public Mono login(Login signInForm) {
.roles(userPrinciple.roles())
.build())
.build();
- }).subscribeOn(Schedulers.boundedElastic()).onErrorResume(Mono::error);
+ }).subscribeOn(Schedulers.boundedElastic());
}
@Override
@@ -168,7 +169,7 @@ public Mono logout() {
String currentToken = getCurrentToken();
- if (authentication != null && authentication.isAuthenticated()) {
+ if (authentication != null && authentication.isAuthenticated() && currentToken != null) {
// Invalidate the current token by reducing its expiration time
String updatedToken = jwtProvider.reduceTokenExpiration(currentToken);
}
@@ -191,7 +192,6 @@ private String getCurrentToken() {
return null;
}
- @Transactional
@Override
public Mono update(Long id, SignUp updateDTO) {
return Mono.fromCallable(() -> {
@@ -205,7 +205,6 @@ public Mono update(Long id, SignUp updateDTO) {
}).subscribeOn(Schedulers.boundedElastic());
}
- @Transactional
@Override
public Mono changePassword(ChangePasswordRequest request) {
return Mono.fromCallable(() -> {
@@ -271,23 +270,19 @@ private boolean validateNewPassword(String newPassword, String confirmPassword)
return Objects.equals(newPassword, confirmPassword);
}
- @Transactional
@Override
public Mono delete(Long id) {
- return Mono.fromRunnable(() -> userRepository.findById(id)
- .ifPresentOrElse(
- user -> {
- try {
- userRepository.delete(user);
- } catch (DataAccessException e) {
- throw new RuntimeException("Error deleting user with userId: " + id, e);
- }
- },
- () -> {
- throw new UserNotFoundException("User not found for userId: " + id);
- }))
- .subscribeOn(Schedulers.boundedElastic())
- .then(Mono.just("User with id " + id + " deleted successfully."));
+ return Mono.fromCallable(() -> {
+ User user = userRepository.findById(id)
+ .orElseThrow(() -> new UserNotFoundException("User not found for userId: " + id));
+
+ try {
+ userRepository.delete(user);
+ return "User with id " + id + " deleted successfully.";
+ } catch (DataAccessException e) {
+ throw new RuntimeException("Error deleting user with userId: " + id, e);
+ }
+ }).subscribeOn(Schedulers.boundedElastic());
}
public Mono refreshToken(String refreshToken) {
@@ -296,10 +291,12 @@ public Mono refreshToken(String refreshToken) {
.uri(refreshTokenUrl)
.header("Refresh-Token", refreshToken)
.retrieve()
- .onStatus(HttpStatus::is4xxClientError,
- clientResponse -> Mono.error(new IllegalArgumentException("Refresh token không hợp lệ")))
+ .onStatus(
+ status -> status.value() >= 400 && status.value() < 500,
+ response -> Mono.error(new IllegalArgumentException("Refresh token không hợp lệ"))
+ )
.bodyToMono(JwtResponseMessage.class)
- .map(JwtResponseMessage::getAccessToken); // Sử dụng getAccessToken để lấy token từ JwtResponse
+ .map(JwtResponseMessage::getAccessToken);
}
@Override
@@ -326,16 +323,18 @@ public Mono> findAllUsers(int page, int size, String sortBy, Strin
}).subscribeOn(Schedulers.boundedElastic());
}
+ @Override
public boolean existsByUsername(String username) {
return userRepository.existsByUsername(username);
}
+ @Override
public boolean existsByEmail(String email) {
return userRepository.existsByEmail(email);
}
+ @Override
public boolean existsByPhoneNumber(String phone) {
return userRepository.existsByPhoneNumber(phone);
}
-
-}
+}
\ No newline at end of file