├── payment-service └── src │ ├── test │ ├── resources │ │ ├── __files │ │ │ └── path │ │ │ │ └── json │ │ │ │ └── orderResponse.json │ │ ├── schema.sql │ │ └── application.yml │ └── java │ │ └── com │ │ └── nasr │ │ └── paymentservice │ │ ├── PaymentServiceApplicationTests.java │ │ ├── config │ │ ├── WireMockConfig.java │ │ └── ProjectConfig.java │ │ ├── TestServiceInstanceListSupplier.java │ │ └── repository │ │ └── TransactionRepositoryTest.java │ └── main │ ├── java │ └── com │ │ └── nasr │ │ └── paymentservice │ │ ├── domain │ │ ├── enumeration │ │ │ ├── PaymentStatus.java │ │ │ └── PaymentMode.java │ │ └── Transaction.java │ │ ├── exception │ │ ├── ErrorResponse.java │ │ ├── EntityNotFoundException.java │ │ ├── InvalidPaymentException.java │ │ ├── PaymentNotFoundException.java │ │ ├── ExternalServiceException.java │ │ └── RestResponseEntityExceptionHandler.java │ │ ├── service │ │ ├── PaymentService.java │ │ ├── TransactionService.java │ │ └── impl │ │ │ └── StripePaymentServiceImpl.java │ │ ├── constant │ │ └── ConstantField.java │ │ ├── base │ │ ├── mapper │ │ │ └── BaseMapper.java │ │ ├── service │ │ │ ├── BaseService.java │ │ │ └── impl │ │ │ │ └── BaseServiceImpl.java │ │ └── domain │ │ │ └── BaseEntity.java │ │ ├── dto │ │ ├── request │ │ │ ├── AccountInfo.java │ │ │ └── PaymentRequest.java │ │ └── response │ │ │ └── PaymentResponse.java │ │ ├── mapper │ │ └── PaymentMapper.java │ │ ├── external │ │ └── response │ │ │ └── OrderResponse.java │ │ ├── PaymentServiceApplication.java │ │ ├── config │ │ ├── OpenApiConfig.java │ │ ├── SchemaConfig.java │ │ ├── WebclientConfig.java │ │ └── ResourceServerConfig.java │ │ ├── util │ │ └── Oauth2TokenUtil.java │ │ ├── repository │ │ └── TransactionRepository.java │ │ ├── intercept │ │ └── WebclientInterceptor.java │ │ └── controller │ │ └── PaymentController.java │ └── resources │ ├── schema-init.sql │ └── application.yml ├── order-service ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── nasr │ │ │ │ └── orderservice │ │ │ │ ├── domain │ │ │ │ ├── enumeration │ │ │ │ │ ├── PaymentStatus.java │ │ │ │ │ ├── OrderStatus.java │ │ │ │ │ └── PaymentMode.java │ │ │ │ ├── OrderDetail.java │ │ │ │ └── Order.java │ │ │ │ ├── exception │ │ │ │ ├── EntityNotFoundException.java │ │ │ │ ├── EntityNotValidException.java │ │ │ │ ├── OrderNotValidException.java │ │ │ │ ├── OrderNotFoundException.java │ │ │ │ ├── OrderDetailNotFoundException.java │ │ │ │ ├── ErrorResponse.java │ │ │ │ ├── ExternalServiceException.java │ │ │ │ └── RestResponseEntityExceptionHandler.java │ │ │ │ ├── repository │ │ │ │ ├── OrderRepository.java │ │ │ │ └── OrderDetailRepository.java │ │ │ │ ├── base │ │ │ │ ├── mapper │ │ │ │ │ └── BaseMapper.java │ │ │ │ ├── service │ │ │ │ │ ├── BaseService.java │ │ │ │ │ └── impl │ │ │ │ │ │ └── BaseServiceImpl.java │ │ │ │ └── domain │ │ │ │ │ └── BaseEntity.java │ │ │ │ ├── dto │ │ │ │ ├── request │ │ │ │ │ ├── OrderPlaceRequest.java │ │ │ │ │ ├── OrderDetailRequest.java │ │ │ │ │ └── OrderRequest.java │ │ │ │ └── response │ │ │ │ │ ├── OrderDetailResponse.java │ │ │ │ │ └── OrderResponse.java │ │ │ │ ├── constant │ │ │ │ └── ConstantField.java │ │ │ │ ├── external │ │ │ │ ├── request │ │ │ │ │ ├── TriggerDescriptorRequest.java │ │ │ │ │ ├── DecreaseProductQuantityRequest.java │ │ │ │ │ └── JobDescriptorRequest.java │ │ │ │ └── response │ │ │ │ │ ├── ProductResponse.java │ │ │ │ │ └── PaymentResponse.java │ │ │ │ ├── mapper │ │ │ │ ├── OrderMapper.java │ │ │ │ └── OrderDetailMapper.java │ │ │ │ ├── OrderServiceApplication.java │ │ │ │ ├── config │ │ │ │ ├── OpenApiConfig.java │ │ │ │ ├── SchemaConfig.java │ │ │ │ ├── CircuitBreakerConfig.java │ │ │ │ ├── WebclientConfig.java │ │ │ │ └── ResourceServerConfig.java │ │ │ │ ├── service │ │ │ │ ├── OrderDetailService.java │ │ │ │ ├── OrderService.java │ │ │ │ └── impl │ │ │ │ │ └── OrderDetailServiceImpl.java │ │ │ │ ├── util │ │ │ │ └── Oauth2TokenUtil.java │ │ │ │ ├── intercept │ │ │ │ └── WebclientInterceptor.java │ │ │ │ └── controller │ │ │ │ └── OrderController.java │ │ └── resources │ │ │ ├── schema-init.sql │ │ │ └── application.yml │ └── test │ │ ├── resources │ │ ├── __files │ │ │ └── path │ │ │ │ └── json │ │ │ │ ├── OrderProductResponses.json │ │ │ │ └── OrderHandlerJobResponse.json │ │ ├── schema.sql │ │ └── application.yml │ │ └── java │ │ └── com │ │ └── nasr │ │ └── orderservice │ │ ├── OrderServiceApplicationTests.java │ │ ├── config │ │ ├── WireMockConfig.java │ │ ├── WebclientConfig.java │ │ └── ProjectConfig.java │ │ ├── TestServiceInstanceSupplier.java │ │ ├── repository │ │ └── OrderDetailRepositoryTest.java │ │ └── service │ │ └── impl │ │ └── OrderDetailServiceImplTest.java └── circuit-breaker-note.txt ├── api-gateway └── src │ └── main │ └── java │ └── com │ └── nasr │ └── apigateway │ ├── constant │ └── ConstantField.java │ ├── external │ ├── service │ │ ├── ExternalUserService.java │ │ └── impl │ │ │ └── ExternalUserServiceImpl.java │ └── response │ │ └── UserResponseDto.java │ ├── dto │ └── response │ │ ├── UserInfoResponseDto.java │ │ └── TokenInfoResponseDto.java │ ├── fallback │ ├── OrderFallbackController.java │ ├── ProductFallbackController.java │ └── PaymentFallbackController.java │ ├── util │ └── Oauth2TokenUtil.java │ ├── config │ └── Oauth2LoginConfig.java │ ├── ApiGatewayApplication.java │ └── controller │ └── GatewayLoginController.java ├── authorization-server ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── nasr │ │ │ │ └── authorizationserver │ │ │ │ ├── constant │ │ │ │ └── ConstantField.java │ │ │ │ ├── dto │ │ │ │ └── response │ │ │ │ │ ├── RoleResponseDto.java │ │ │ │ │ ├── UserResponseDto.java │ │ │ │ │ └── UserRoleResponseDto.java │ │ │ │ ├── AuthorizationServerApplication.java │ │ │ │ ├── service │ │ │ │ ├── RoleService.java │ │ │ │ ├── UserService.java │ │ │ │ └── impl │ │ │ │ │ ├── UserServiceImpl.java │ │ │ │ │ └── RoleServiceImpl.java │ │ │ │ ├── repository │ │ │ │ ├── RoleRepository.java │ │ │ │ └── UserRepository.java │ │ │ │ ├── domain │ │ │ │ ├── Role.java │ │ │ │ └── User.java │ │ │ │ ├── config │ │ │ │ ├── CustomUserDetailService.java │ │ │ │ ├── AccessTokenDecoderConfig.java │ │ │ │ ├── CustomUserDetail.java │ │ │ │ ├── CustomAuthenticationProvider.java │ │ │ │ ├── KeyPairConfig.java │ │ │ │ └── SecurityConfig.java │ │ │ │ ├── controller │ │ │ │ └── UserController.java │ │ │ │ ├── init │ │ │ │ └── DataInitializer.java │ │ │ │ └── filter │ │ │ │ └── AccessTokenAuthenticationFilter.java │ │ └── resources │ │ │ └── application.yml │ └── test │ │ └── java │ │ └── com │ │ └── nasr │ │ └── authorizationserver │ │ └── AuthorizationServerApplicationTests.java └── pom.xml ├── order-handler-service └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── nasr │ │ │ └── orderhandlerservice │ │ │ ├── constant │ │ │ └── ConstantField.java │ │ │ ├── model │ │ │ ├── enumeration │ │ │ │ ├── PaymentStatus.java │ │ │ │ └── PaymentMode.java │ │ │ ├── request │ │ │ │ ├── RevertProductRequest.java │ │ │ │ ├── TriggerDescriptorRequest.java │ │ │ │ └── JobDescriptorRequest.java │ │ │ └── response │ │ │ │ ├── OrderDetailResponse.java │ │ │ │ └── OrderResponse.java │ │ │ ├── config │ │ │ ├── OpenApiConfig.java │ │ │ ├── SchedulerConfig.java │ │ │ ├── ResourceServerConfig.java │ │ │ └── WebclientConfig.java │ │ │ ├── OrderHandlerServiceApplication.java │ │ │ ├── intercept │ │ │ └── WebclientInterceptor.java │ │ │ ├── AutowiringSpringBeanJobFactory.java │ │ │ ├── service │ │ │ └── OrderPlacedHandlerService.java │ │ │ └── controller │ │ │ └── OrderPlacedHandlerController.java │ └── resources │ │ ├── schema.sql │ │ └── application.yml │ └── test │ └── java │ └── com │ └── nasr │ └── orderhandlerservice │ └── OrderHandlerServiceApplicationTests.java ├── service-registry ├── src │ ├── main │ │ ├── resources │ │ │ └── application.yml │ │ └── java │ │ │ └── com │ │ │ └── nasr │ │ │ └── serviceregistry │ │ │ └── ServiceRegistryApplication.java │ └── test │ │ └── java │ │ └── com │ │ └── nasr │ │ └── serviceregistry │ │ └── ServiceRegistryApplicationTests.java └── pom.xml ├── product-service └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── nasr │ │ │ └── productservice │ │ │ ├── exception │ │ │ ├── EntityNotFoundException.java │ │ │ ├── EntityNotValidException.java │ │ │ ├── ProductNotValidException.java │ │ │ ├── ProductNotFoundException.java │ │ │ └── RestResponseEntityExceptionHandler.java │ │ │ ├── constant │ │ │ └── ConstantField.java │ │ │ ├── base │ │ │ ├── mapper │ │ │ │ └── BaseMapper.java │ │ │ ├── service │ │ │ │ ├── BaseService.java │ │ │ │ └── impl │ │ │ │ │ └── BaseServiceImpl.java │ │ │ └── domain │ │ │ │ └── BaseEntity.java │ │ │ ├── dto │ │ │ ├── request │ │ │ │ ├── RevertProductRequest.java │ │ │ │ ├── DecreaseProductQuantityRequest.java │ │ │ │ └── ProductRequest.java │ │ │ └── response │ │ │ │ ├── ProductCategoryResponse.java │ │ │ │ └── ProductResponse.java │ │ │ ├── mapper │ │ │ └── ProductMapper.java │ │ │ ├── ProductServiceApplication.java │ │ │ ├── config │ │ │ ├── OpenApiConfig.java │ │ │ ├── SchemaConfig.java │ │ │ └── ResourceServerConfig.java │ │ │ ├── domain │ │ │ ├── ProductCategory.java │ │ │ └── Product.java │ │ │ ├── repository │ │ │ └── ProductRepository.java │ │ │ ├── service │ │ │ └── ProductService.java │ │ │ └── controller │ │ │ └── ProductController.java │ └── resources │ │ ├── schema.sql │ │ └── application.yml │ └── test │ ├── resources │ ├── schema.sql │ └── application.yml │ └── java │ └── com │ └── nasr │ └── productservice │ ├── ProductServiceApplicationTest.java │ └── repository │ └── ProductRepositoryTest.java ├── config-server ├── src │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── nasr │ │ │ └── configserver │ │ │ └── ConfigServerApplicationTests.java │ └── main │ │ ├── resources │ │ ├── application.yml │ │ └── config │ │ │ └── application.yml │ │ └── java │ │ └── com │ │ └── nasr │ │ └── configserver │ │ └── ConfigServerApplication.java └── pom.xml ├── .gitignore ├── README.md └── LICENSE /payment-service/src/test/resources/__files/path/json/orderResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "orderDate": "2022-10-23T14:00:00", 4 | "totalPrice": 768000, 5 | "orderStatus": "COMPLETED" 6 | } -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/domain/enumeration/PaymentStatus.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.domain.enumeration; 2 | 3 | public enum PaymentStatus { 4 | SUCCESS,FAIL,IN_PROGRESS 5 | } 6 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/constant/ConstantField.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.constant; 2 | 3 | public class ConstantField { 4 | 5 | public static final String TOKEN_PREFIX="Bearer "; 6 | } 7 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/constant/ConstantField.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.constant; 2 | 3 | public class ConstantField { 4 | public static final String BEARER_PREFIX="Bearer "; 5 | } 6 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/constant/ConstantField.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.constant; 2 | 3 | public class ConstantField { 4 | public static final String TOKEN_PREFIX="Bearer "; 5 | } 6 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/domain/enumeration/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.domain.enumeration; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum OrderStatus { 7 | 8 | NEW,COMPLETED 9 | 10 | } 11 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/domain/enumeration/PaymentStatus.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.domain.enumeration; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum PaymentStatus { 7 | 8 | SUCCESS,FAIL 9 | } 10 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/exception/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.exception; 2 | 3 | 4 | import org.springframework.http.HttpStatus; 5 | 6 | public record ErrorResponse (String message , HttpStatus errorCode) { 7 | } 8 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/domain/enumeration/PaymentMode.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.domain.enumeration; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum PaymentMode { 7 | CASH,PAYPAL,DEBIT_CARD,CREDIT_CARD,APPLE_PAY 8 | } 9 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/domain/enumeration/PaymentMode.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.domain.enumeration; 2 | 3 | public enum PaymentMode { 4 | 5 | CASH, 6 | PAYPAL, 7 | DEBIT_CARD, 8 | CREDIT_CARD, 9 | APPLE_PAY 10 | } 11 | -------------------------------------------------------------------------------- /service-registry/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: eureka-server 4 | server: 5 | port: 8761 6 | eureka: 7 | instance: 8 | hostname: localhost 9 | client: 10 | fetch-registry: false 11 | register-with-eureka: false 12 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/model/enumeration/PaymentStatus.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.model.enumeration; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum PaymentStatus { 7 | 8 | SUCCESS,FAIL,IN_PROGRESS 9 | } 10 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/model/enumeration/PaymentMode.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.model.enumeration; 2 | 3 | public enum PaymentMode { 4 | 5 | CASH, 6 | PAYPAL, 7 | DEBIT_CARD, 8 | CREDIT_CARD, 9 | APPLE_PAY 10 | } 11 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/exception/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.exception; 2 | 3 | public class EntityNotFoundException extends RuntimeException{ 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/exception/EntityNotValidException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.exception; 2 | 3 | public class EntityNotValidException extends RuntimeException{ 4 | public EntityNotValidException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/exception/OrderNotValidException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.exception; 2 | 3 | public class OrderNotValidException extends EntityNotValidException{ 4 | public OrderNotValidException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /order-service/src/test/resources/__files/path/json/OrderProductResponses.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id": 1,"name": "iphone" , "quantity" : 23, "price" : 46000000 }, 3 | {"id": 2,"name": "handsFree" , "quantity" : 14, "price" : 230000 }, 4 | {"id": 3,"name": "logitech mouse" , "quantity" : 16, "price" : 350000 } 5 | ] -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/exception/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.exception; 2 | 3 | public class EntityNotFoundException extends RuntimeException{ 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/exception/InvalidPaymentException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.exception; 2 | 3 | public class InvalidPaymentException extends RuntimeException{ 4 | public InvalidPaymentException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/exception/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.exception; 2 | 3 | public class EntityNotFoundException extends RuntimeException{ 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/exception/EntityNotValidException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.exception; 2 | 3 | public class EntityNotValidException extends RuntimeException { 4 | public EntityNotValidException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/exception/OrderNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.exception; 2 | 3 | public class OrderNotFoundException extends EntityNotFoundException{ 4 | 5 | public OrderNotFoundException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/service/PaymentService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.service; 2 | 3 | import com.nasr.paymentservice.dto.request.PaymentRequest; 4 | 5 | public interface PaymentService { 6 | 7 | void doPayment(PaymentRequest paymentRequest) throws Exception; 8 | } 9 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/exception/ProductNotValidException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.exception; 2 | 3 | public class ProductNotValidException extends EntityNotValidException{ 4 | public ProductNotValidException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/exception/PaymentNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.exception; 2 | 3 | public class PaymentNotFoundException extends EntityNotFoundException{ 4 | 5 | public PaymentNotFoundException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/exception/ProductNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.exception; 2 | 3 | public class ProductNotFoundException extends EntityNotFoundException{ 4 | 5 | public ProductNotFoundException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/exception/OrderDetailNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.exception; 2 | 3 | public class OrderDetailNotFoundException extends EntityNotFoundException{ 4 | 5 | public OrderDetailNotFoundException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/constant/ConstantField.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.constant; 2 | 3 | import lombok.Getter; 4 | 5 | 6 | @Getter 7 | public class ConstantField { 8 | public static final String ROLE_PREFIX="ROLE_"; 9 | public static final String SCOPE_PREFIX="SCOPE_"; 10 | } 11 | -------------------------------------------------------------------------------- /order-service/src/test/resources/__files/path/json/OrderHandlerJobResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mc231v?>tRXL'@y&9", 3 | "orderId": 1, 4 | "productInfo": { 5 | "1": 3, 6 | "2": 5 7 | }, 8 | "triggers": [ 9 | { 10 | "name": "order_trigger", 11 | "group": "test", 12 | "hour": 1 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/external/service/ExternalUserService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.external.service; 2 | 3 | import com.nasr.apigateway.dto.response.UserInfoResponseDto; 4 | import reactor.core.publisher.Mono; 5 | 6 | public interface ExternalUserService { 7 | 8 | Mono getUser(String auth); 9 | } 10 | -------------------------------------------------------------------------------- /config-server/src/test/java/com/nasr/configserver/ConfigServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.nasr.configserver; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ConfigServerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/nasr/orderservice/OrderServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class OrderServiceApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /payment-service/src/test/java/com/nasr/paymentservice/PaymentServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class PaymentServiceApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /service-registry/src/test/java/com/nasr/serviceregistry/ServiceRegistryApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.nasr.serviceregistry; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ServiceRegistryApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/constant/ConstantField.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.constant; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class ConstantField { 7 | public static final String ROLE_PREFIX="ROLE_"; 8 | public static final String SCOPE_PREFIX="SCOPE_"; 9 | public static final String TOKEN_PREFIX="Bearer "; 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | -------------------------------------------------------------------------------- /product-service/src/test/resources/schema.sql: -------------------------------------------------------------------------------- 1 | 2 | 3 | create table if not exists product_category_table (id bigserial not null primary key , name varchar (50)); 4 | 5 | create table if not exists product_table(id bigserial not null primary key , name varchar (50) not null , quantity bigint not null , 6 | price double precision not null , category_id bigint references product_category_table(id)); -------------------------------------------------------------------------------- /authorization-server/src/test/java/com/nasr/authorizationserver/AuthorizationServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class AuthorizationServerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /order-handler-service/src/test/java/com/nasr/orderhandlerservice/OrderHandlerServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class OrderHandlerServiceApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /product-service/src/test/java/com/nasr/productservice/ProductServiceApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | public class ProductServiceApplicationTest { 8 | 9 | 10 | @Test 11 | public void contextLoad() { 12 | 13 | } 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /order-service/src/test/resources/schema.sql: -------------------------------------------------------------------------------- 1 | 2 | 3 | create table if not exists order_table(id bigserial not null primary key , 4 | order_date timestamp ,total_price double precision not null , order_status varchar(50) ); 5 | 6 | create table if not exists order_detail_table(id bigserial not null primary key , 7 | order_id bigint not null , 8 | product_id bigint not null ,product_number bigint not null ); -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.repository; 2 | 3 | import com.nasr.orderservice.domain.Order; 4 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface OrderRepository extends ReactiveCrudRepository { 9 | } 10 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/base/mapper/BaseMapper.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.base.mapper; 2 | 3 | import java.util.List; 4 | 5 | public interface BaseMapper { 6 | 7 | D convertEntityToDto(E entity); 8 | E convertViewToEntity(V viewDto); 9 | 10 | List convertEntitiesToDtoList(Iterable entities); 11 | List convertViewsToEntities(Iterable dtoList); 12 | } 13 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/dto/request/OrderPlaceRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.dto.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class OrderPlaceRequest { 11 | 12 | private Long productId; 13 | private Long productNumber; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/base/mapper/BaseMapper.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.base.mapper; 2 | 3 | import java.util.List; 4 | 5 | public interface BaseMapper { 6 | 7 | D convertEntityToDto(E entity); 8 | E convertViewToEntity(V viewDto); 9 | 10 | List convertEntitiesToDtoList(List entities); 11 | List convertViewsToEntities(List dtoList); 12 | } 13 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/dto/request/RevertProductRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.dto.request; 2 | 3 | import lombok.NoArgsConstructor; 4 | 5 | @NoArgsConstructor 6 | public class RevertProductRequest extends DecreaseProductQuantityRequest{ 7 | 8 | public RevertProductRequest( Long productId, Long productNumber) { 9 | super(productId, productNumber); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/base/mapper/BaseMapper.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.base.mapper; 2 | 3 | import java.util.List; 4 | 5 | public interface BaseMapper { 6 | 7 | D convertEntityToDto(E entity); 8 | E convertViewToEntity(V viewDto); 9 | 10 | List convertEntitiesToDtoList(Iterable entities); 11 | List convertViewsToEntities(Iterable dtoList); 12 | } 13 | -------------------------------------------------------------------------------- /config-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8888 3 | 4 | spring: 5 | application: 6 | name: CONFIG-SERVER 7 | cloud: 8 | config: 9 | server: 10 | git: 11 | uri: https://github.com/nasrmohammad4804/springboot-microservice.git 12 | search-paths: 13 | config-server/src/main/resources/config/ 14 | clone-on-start: true 15 | default-label: master 16 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/model/request/RevertProductRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.model.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class RevertProductRequest { 11 | 12 | private Long productId; 13 | private Long productNumber; 14 | } 15 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/dto/request/OrderDetailRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.dto.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class OrderDetailRequest { 11 | 12 | private Long productId; 13 | private Long orderId; 14 | private Long productNumber; 15 | } 16 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/dto/request/AccountInfo.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.dto.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class AccountInfo { 11 | 12 | private Long cardNumber; 13 | private String cvv2; 14 | private String expirationTime; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/dto/response/OrderDetailResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class OrderDetailResponse { 11 | 12 | private Long productId; 13 | private Long orderId; 14 | private Long productNumber; 15 | } 16 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/exception/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.exception; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.http.HttpStatus; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class ErrorResponse { 12 | 13 | private String message; 14 | private HttpStatus errorCode; 15 | } 16 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/dto/response/RoleResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class RoleResponseDto { 13 | 14 | private Long id; 15 | private String name; 16 | } 17 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/dto/response/ProductCategoryResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Builder 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class ProductCategoryResponse { 13 | 14 | private Long id; 15 | private String name; 16 | } 17 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/dto/response/UserInfoResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class UserInfoResponseDto { 11 | 12 | private Long ssoId; 13 | private String firstName; 14 | private String lastName; 15 | private String email; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/model/response/OrderDetailResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.model.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class OrderDetailResponse { 11 | 12 | private Long productId; 13 | private Long orderId; 14 | private Long productNumber; 15 | } 16 | -------------------------------------------------------------------------------- /product-service/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | 2 | create schema if not exists product_schema; 3 | 4 | create table if not exists product_schema.product_category_table (id bigserial not null primary key , name varchar (50)); 5 | 6 | create table if not exists product_schema.product_table(id bigserial not null primary key , name varchar (50) not null , quantity bigint not null , 7 | price double precision not null , category_id bigint references product_schema.product_category_table(id)); -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/constant/ConstantField.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.constant; 2 | 3 | 4 | public class ConstantField { 5 | 6 | public static final String ORDER_HANDLER_GROUP_NAME="order-handler"; 7 | public static final int ORDER_HANDLER_DEFAULT_HOUR=1; 8 | public static final String ROLE_PREFIX="ROLE_"; 9 | public static final String SCOPE_PREFIX="SCOPE_"; 10 | public static final String TOKEN_PREFIX="Bearer "; 11 | } 12 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/external/request/TriggerDescriptorRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.external.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class TriggerDescriptorRequest { 13 | 14 | private String name; 15 | private String group; 16 | private int hour; 17 | } 18 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/mapper/OrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.mapper; 2 | 3 | import com.nasr.orderservice.base.mapper.BaseMapper; 4 | import com.nasr.orderservice.domain.Order; 5 | import com.nasr.orderservice.dto.request.OrderRequest; 6 | import com.nasr.orderservice.dto.response.OrderResponse; 7 | import org.mapstruct.Mapper; 8 | 9 | 10 | @Mapper 11 | public interface OrderMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/external/response/UserResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.external.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class UserResponseDto { 13 | 14 | private Long id; 15 | private String firstName; 16 | private String lastName; 17 | private String email; 18 | 19 | } -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/AuthorizationServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class AuthorizationServerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(AuthorizationServerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/external/request/DecreaseProductQuantityRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.external.request; 2 | 3 | import com.nasr.orderservice.dto.request.OrderPlaceRequest; 4 | import lombok.NoArgsConstructor; 5 | 6 | 7 | @NoArgsConstructor 8 | public class DecreaseProductQuantityRequest extends OrderPlaceRequest { 9 | 10 | public DecreaseProductQuantityRequest(Long productId, Long productNumber) { 11 | super(productId, productNumber); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /payment-service/src/test/java/com/nasr/paymentservice/config/WireMockConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.config; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Getter 9 | @Setter 10 | @Component 11 | @ConfigurationProperties(prefix = "wiremock-server") 12 | public class WireMockConfig { 13 | 14 | private int port; 15 | private String host; 16 | } 17 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/mapper/PaymentMapper.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.mapper; 2 | 3 | import com.nasr.paymentservice.base.mapper.BaseMapper; 4 | import com.nasr.paymentservice.domain.Transaction; 5 | import com.nasr.paymentservice.dto.request.PaymentRequest; 6 | import com.nasr.paymentservice.dto.response.PaymentResponse; 7 | import org.mapstruct.Mapper; 8 | 9 | @Mapper 10 | public interface PaymentMapper extends BaseMapper { 11 | } 12 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/mapper/ProductMapper.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.mapper; 2 | 3 | import com.nasr.productservice.base.mapper.BaseMapper; 4 | import com.nasr.productservice.domain.Product; 5 | import com.nasr.productservice.dto.request.ProductRequest; 6 | import com.nasr.productservice.dto.response.ProductResponse; 7 | import org.mapstruct.Mapper; 8 | 9 | 10 | @Mapper 11 | public interface ProductMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/dto/response/UserResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class UserResponseDto { 13 | 14 | private Long id; 15 | private String firstName; 16 | private String lastName; 17 | private String email; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/mapper/OrderDetailMapper.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.mapper; 2 | 3 | import com.nasr.orderservice.base.mapper.BaseMapper; 4 | import com.nasr.orderservice.domain.OrderDetail; 5 | import com.nasr.orderservice.dto.request.OrderDetailRequest; 6 | import com.nasr.orderservice.dto.response.OrderDetailResponse; 7 | import org.mapstruct.Mapper; 8 | 9 | @Mapper 10 | public interface OrderDetailMapper extends BaseMapper { 11 | } 12 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/base/service/BaseService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.base.service; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | 6 | import java.io.Serializable; 7 | 8 | public interface BaseService { 9 | 10 | Mono saveOrUpdate(V view); 11 | 12 | Mono getById(ID id); 13 | 14 | Flux getAll(); 15 | 16 | Mono deleteById(ID id); 17 | 18 | Flux saveAll(Iterable view); 19 | } 20 | -------------------------------------------------------------------------------- /payment-service/src/test/resources/schema.sql: -------------------------------------------------------------------------------- 1 | 2 | create table if not exists transaction_table 3 | ( 4 | id bigserial not null primary key, 5 | payment_status varchar(30) check ( payment_status in ('SUCCESS', 'FAIL', 'IN_PROGRESS') ), 6 | payment_mode varchar(30) check ( payment_mode in ('CASH', 'PAYPAL', 'DEBIT_CARD', 'CREDIT_CARD', 'APPLE_PAY') ), 7 | payment_date timestamp , 8 | cardNumber numeric, 9 | cvv2 varchar(10), 10 | order_id bigint unique not null 11 | ); 12 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/dto/response/UserRoleResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class UserRoleResponseDto { 13 | private Long id; 14 | private String userName; 15 | private String password; 16 | private RoleResponseDto role; 17 | } 18 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/dto/response/OrderResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class OrderResponse { 13 | 14 | private Long id; 15 | 16 | private LocalDateTime orderDate; 17 | 18 | private Double totalPrice; 19 | 20 | private String orderStatus; 21 | } 22 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/service/RoleService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.service; 2 | 3 | import com.nasr.authorizationserver.domain.Role; 4 | import com.nasr.authorizationserver.dto.response.RoleResponseDto; 5 | 6 | import java.util.List; 7 | 8 | public interface RoleService { 9 | 10 | RoleResponseDto getRoleById(Long id); 11 | 12 | Role getRoleByName(String name); 13 | 14 | Boolean isExists(); 15 | 16 | List saveAll(Iterable roles); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/external/response/OrderResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.external.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class OrderResponse { 13 | 14 | private Long id; 15 | 16 | private LocalDateTime orderDate; 17 | 18 | private Double totalPrice; 19 | 20 | private String orderStatus; 21 | } 22 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/model/response/OrderResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.model.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class OrderResponse { 13 | 14 | private Long id; 15 | 16 | private LocalDateTime orderDate; 17 | 18 | private Double totalPrice; 19 | 20 | private String orderStatus; 21 | } -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/OrderServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaClient 9 | public class OrderServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(OrderServiceApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/config/OpenApiConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.config; 2 | 3 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 4 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @SecurityScheme( 9 | name = "Bearer Authentication", 10 | type = SecuritySchemeType.HTTP, 11 | bearerFormat = "JWT", 12 | scheme = "bearer" 13 | ) 14 | public class OpenApiConfig { 15 | } 16 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/dto/response/ProductResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class ProductResponse { 13 | 14 | private Long id; 15 | private String name; 16 | private Long quantity; 17 | private Double price; 18 | private ProductCategoryResponse category; 19 | } 20 | -------------------------------------------------------------------------------- /order-service/src/main/resources/schema-init.sql: -------------------------------------------------------------------------------- 1 | 2 | create schema if not exists order_schema; 3 | 4 | create table if not exists order_schema.order_table(id bigserial not null primary key , order_date timestamp ,total_price double precision not null , order_status varchar(50) ); 5 | 6 | create table if not exists order_schema.order_detail_table(id bigserial not null primary key , 7 | order_id bigint not null references order_schema.order_table (id) , 8 | product_id bigint not null references product_schema.product_table (id) ,product_number bigint not null ); 9 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/nasr/orderservice/config/WireMockConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.config; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Getter 10 | @Setter 11 | @Component 12 | @ConfigurationProperties(prefix = "wiremock-server") 13 | public class WireMockConfig { 14 | 15 | private String host; 16 | private int port; 17 | } 18 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/PaymentServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaClient 9 | public class PaymentServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(PaymentServiceApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/config/OpenApiConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.config; 2 | 3 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 4 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @SecurityScheme( 9 | name = "Bearer Authentication", 10 | type = SecuritySchemeType.HTTP, 11 | bearerFormat = "JWT", 12 | scheme = "bearer" 13 | ) 14 | public class OpenApiConfig { 15 | } 16 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/ProductServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaClient 9 | public class ProductServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ProductServiceApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/config/OpenApiConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.config; 2 | 3 | 4 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 5 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | @SecurityScheme( 10 | name = "Bearer Authentication", 11 | type = SecuritySchemeType.HTTP, 12 | bearerFormat = "JWT", 13 | scheme = "bearer" 14 | ) 15 | public class OpenApiConfig { 16 | } 17 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.service; 2 | 3 | import com.nasr.authorizationserver.domain.User; 4 | import com.nasr.authorizationserver.dto.response.UserResponseDto; 5 | 6 | import java.util.List; 7 | 8 | public interface UserService { 9 | 10 | User getUserWithRoleByUserName(String email) throws Exception; 11 | 12 | Boolean isExists(); 13 | 14 | List saveAll(Iterable users); 15 | 16 | UserResponseDto getUserByUserName(String name); 17 | } 18 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/config/OpenApiConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.config; 2 | 3 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 4 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @SecurityScheme( 9 | name = "Bearer Authentication", 10 | type = SecuritySchemeType.HTTP, 11 | bearerFormat = "JWT", 12 | scheme = "bearer") 13 | public class OpenApiConfig { 14 | } 15 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/base/service/BaseService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.base.service; 2 | 3 | import com.nasr.orderservice.base.domain.BaseEntity; 4 | import reactor.core.publisher.Flux; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.io.Serializable; 8 | 9 | public interface BaseService { 10 | 11 | Mono saveOrUpdate(V view); 12 | 13 | Mono getById(ID id); 14 | 15 | Flux getAll(); 16 | 17 | Mono deleteById(ID id); 18 | 19 | Flux saveAll(Iterable view); 20 | } 21 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/nasr/orderservice/config/WebclientConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.config; 2 | 3 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.reactive.function.client.WebClient; 7 | 8 | @Configuration 9 | public class WebclientConfig { 10 | 11 | @Bean 12 | @LoadBalanced 13 | public WebClient.Builder webclient() { 14 | return WebClient.builder(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /service-registry/src/main/java/com/nasr/serviceregistry/ServiceRegistryApplication.java: -------------------------------------------------------------------------------- 1 | package com.nasr.serviceregistry; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaServer 9 | public class ServiceRegistryApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ServiceRegistryApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/OrderHandlerServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaClient 9 | public class OrderHandlerServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(OrderHandlerServiceApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/external/response/ProductResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.external.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class ProductResponse { 11 | 12 | private Long id; 13 | private String name; 14 | private Long quantity; 15 | private Double price; 16 | 17 | public ProductResponse(Long id, Long quantity) { 18 | this.id = id; 19 | this.quantity = quantity; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/service/TransactionService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.service; 2 | 3 | import com.nasr.paymentservice.base.service.BaseService; 4 | import com.nasr.paymentservice.dto.request.PaymentRequest; 5 | import com.nasr.paymentservice.dto.response.PaymentResponse; 6 | import reactor.core.publisher.Mono; 7 | 8 | public interface TransactionService extends BaseService { 9 | 10 | Mono getByOrderId(Long orderId); 11 | 12 | Mono doPayment(PaymentRequest paymentRequest); 13 | } 14 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.repository; 2 | 3 | import com.nasr.authorizationserver.domain.Role; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import java.util.Optional; 8 | 9 | public interface RoleRepository extends JpaRepository { 10 | 11 | Optional findByName(String name); 12 | 13 | @Query("select case when count(r.id)>0 then true else false end from Role as r") 14 | Boolean isExists(); 15 | } 16 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/base/service/BaseService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.base.service; 2 | 3 | import com.nasr.productservice.base.domain.BaseEntity; 4 | import reactor.core.publisher.Flux; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.io.Serializable; 8 | 9 | public interface BaseService< E extends BaseEntity,D,ID extends Serializable> { 10 | 11 | Mono saveOrUpdate(E entity); 12 | 13 | Mono getById(ID id); 14 | 15 | Flux getAll(); 16 | 17 | Mono deleteById(ID id); 18 | 19 | Flux saveAll(Iterable entities); 20 | } 21 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/external/response/PaymentResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.external.response; 2 | 3 | import com.nasr.orderservice.domain.enumeration.PaymentMode; 4 | import com.nasr.orderservice.domain.enumeration.PaymentStatus; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class PaymentResponse { 13 | 14 | private Long id; 15 | private PaymentMode mode; 16 | private PaymentStatus status; 17 | // .. etc contain card number - cvv2 - ... 18 | } 19 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/base/domain/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.base.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.annotation.Transient; 8 | 9 | import java.io.Serializable; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class BaseEntity implements Serializable{ 15 | 16 | @Id 17 | protected ID id; 18 | 19 | @Transient 20 | protected Boolean isDeleted; 21 | } 22 | -------------------------------------------------------------------------------- /payment-service/src/main/resources/schema-init.sql: -------------------------------------------------------------------------------- 1 | create schema if not exists payment_schema; 2 | 3 | create table if not exists payment_schema.transaction_table 4 | ( 5 | id bigserial not null primary key, 6 | payment_status varchar(30) check ( payment_status in ('SUCCESS', 'FAIL', 'IN_PROGRESS') ), 7 | payment_mode varchar(30) check ( payment_mode in ('CASH', 'PAYPAL', 'DEBIT_CARD', 'CREDIT_CARD', 'APPLE_PAY') ), 8 | payment_date timestamp , 9 | cardNumber numeric, 10 | cvv2 varchar(10), 11 | order_id bigint unique references order_schema.order_table (id) 12 | ); 13 | 14 | -------------------------------------------------------------------------------- /config-server/src/main/resources/config/application.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | instance: 3 | prefer-ip-address: true 4 | client: 5 | serviceUrl: 6 | defaultZone: ${EUREKA_SERVER_URI:http://localhost:8761/eureka} 7 | fetch-registry: true 8 | register-with-eureka: true 9 | 10 | 11 | zipkin: 12 | base-url: ${ZIPKIN_SERVER_URI:http://localhost:9411/} 13 | 14 | cloud: 15 | config: 16 | fail-fast: true 17 | retry: 18 | max-attempts: 20 19 | max-interval: 1500 20 | initial-interval: 10000 21 | 22 | management: 23 | endpoints: 24 | web: 25 | exposure: 26 | include: '*' -------------------------------------------------------------------------------- /config-server/src/main/java/com/nasr/configserver/ConfigServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.nasr.configserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.config.server.EnableConfigServer; 6 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 7 | 8 | @SpringBootApplication 9 | @EnableConfigServer 10 | @EnableEurekaClient 11 | public class ConfigServerApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(ConfigServerApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /product-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | application: 5 | name: PRODUCT-SERVICE 6 | 7 | config: 8 | import: optional:configserver:${CONFIG_SERVER_URI:http://localhost:8888} 9 | 10 | r2dbc: 11 | username: user 12 | password: 1111 13 | url: r2dbc:h2:mem:///testdb 14 | 15 | security: 16 | oauth2: 17 | resourceserver: 18 | jwt: 19 | issuer-uri: ${AUTHORIZATION_SERVER_URI:http://auth-server:9000} 20 | logging: 21 | level: 22 | io.r2dbc.postgresql.QUERY: DEBUG 23 | io.r2dbc.postgresql.PARAM: DEBUG 24 | eureka: 25 | client: 26 | enabled: false -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/service/OrderDetailService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.service; 2 | 3 | import com.nasr.orderservice.base.service.BaseService; 4 | import com.nasr.orderservice.dto.request.OrderDetailRequest; 5 | import com.nasr.orderservice.dto.response.OrderDetailResponse; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | 9 | public interface OrderDetailService extends BaseService { 10 | 11 | Flux getOrderDetailsByOrderId(Long orderId); 12 | 13 | Mono deleteOrderDetailByOrderId(Long orderId); 14 | } 15 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/fallback/OrderFallbackController.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.fallback; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | import reactor.core.publisher.Mono; 7 | 8 | @RestController 9 | @RequestMapping("/order-service-fallback") 10 | public class OrderFallbackController { 11 | 12 | @GetMapping 13 | public Mono orderFallbackService() { 14 | return Mono.just("
order service is unavailable !!
"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/dto/request/OrderRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.dto.request; 2 | 3 | import com.nasr.orderservice.domain.enumeration.PaymentMode; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotNull; 9 | import java.util.List; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class OrderRequest { 15 | 16 | private List orderPlaceRequestDtoList; 17 | 18 | @NotNull(message = "total amount is mandatory . when income from client") 19 | private Double totalPrice; 20 | } 21 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/exception/ExternalServiceException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.exception; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | @Setter 8 | @Getter 9 | public class ExternalServiceException extends RuntimeException{ 10 | private HttpStatus errorCode; 11 | 12 | public ExternalServiceException(String message, HttpStatus errorCode) { 13 | super(message); 14 | this.errorCode = errorCode; 15 | } 16 | 17 | public ExternalServiceException(HttpStatus errorCode) { 18 | this.errorCode = errorCode; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/external/request/JobDescriptorRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.external.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Builder 16 | public class JobDescriptorRequest { 17 | 18 | private String name; 19 | private long orderId; 20 | private List triggers = new ArrayList<>(); 21 | private Map productInfo; 22 | } 23 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/util/Oauth2TokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.util; 2 | 3 | import org.springframework.http.server.reactive.ServerHttpRequest; 4 | 5 | import java.util.Objects; 6 | 7 | public class Oauth2TokenUtil { 8 | 9 | 10 | public static String getAuth(ServerHttpRequest request){ 11 | 12 | return Objects.requireNonNull(request.getHeaders() 13 | .get("Authorization")) 14 | .stream() 15 | .findFirst() 16 | .orElseThrow(() -> new IllegalStateException("dont extract access token from header of request")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/dto/request/DecreaseProductQuantityRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.dto.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.Min; 8 | import javax.validation.constraints.NotNull; 9 | import java.io.Serializable; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class DecreaseProductQuantityRequest implements Serializable { 15 | 16 | 17 | @NotNull 18 | @Min(1) 19 | private Long productId; 20 | 21 | @NotNull 22 | @Min(1) 23 | private Long productNumber; 24 | } 25 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/fallback/ProductFallbackController.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.fallback; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | import reactor.core.publisher.Mono; 7 | 8 | 9 | @RestController 10 | @RequestMapping("/product-service-fallback") 11 | public class ProductFallbackController { 12 | 13 | @GetMapping 14 | public Mono productFallbackService() { 15 | return Mono.just("
product service is unavailable !!
"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/util/Oauth2TokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.util; 2 | 3 | import org.springframework.http.server.reactive.ServerHttpRequest; 4 | 5 | import java.util.Objects; 6 | 7 | public class Oauth2TokenUtil { 8 | 9 | 10 | public static String getAuth(ServerHttpRequest request){ 11 | 12 | return Objects.requireNonNull(request.getHeaders() 13 | .get("Authorization")) 14 | .stream() 15 | .findFirst() 16 | .orElseThrow(() -> new IllegalStateException("dont extract access token from header of request")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/exception/ExternalServiceException.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.exception; 2 | 3 | import lombok.Data; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.springframework.http.HttpStatus; 7 | 8 | @Setter 9 | @Getter 10 | public class ExternalServiceException extends RuntimeException{ 11 | private HttpStatus errorCode; 12 | 13 | public ExternalServiceException(String message, HttpStatus errorCode) { 14 | super(message); 15 | this.errorCode = errorCode; 16 | } 17 | 18 | public ExternalServiceException(HttpStatus errorCode) { 19 | this.errorCode = errorCode; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/base/domain/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.base.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.annotation.Transient; 8 | import org.springframework.data.relational.core.mapping.Column; 9 | 10 | import java.io.Serializable; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class BaseEntity implements Serializable{ 16 | 17 | @Id 18 | protected ID id; 19 | 20 | /* @Transient 21 | protected Boolean isDeleted;*/ 22 | } 23 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/domain/ProductCategory.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.domain; 2 | 3 | import com.nasr.productservice.base.domain.BaseEntity; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | import org.springframework.data.relational.core.mapping.Table; 9 | 10 | @Setter 11 | @Getter 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Table(name = ProductCategory.PRODUCT_CATEGORY_TABLE) 15 | public class ProductCategory extends BaseEntity { 16 | 17 | public static final String PRODUCT_CATEGORY_TABLE="product_category_table"; 18 | protected String name; 19 | } 20 | -------------------------------------------------------------------------------- /product-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | application: 5 | name: PRODUCT-SERVICE 6 | 7 | config: 8 | import: optional:configserver:${CONFIG_SERVER_URI:http://localhost:8888} 9 | 10 | r2dbc: 11 | username: postgres 12 | password: MohammadN@sr13804804 13 | url: r2dbc:postgresql://${DB_HOST:localhost}:5432/microservice-ecommerce?schema=product_schema 14 | 15 | security: 16 | oauth2: 17 | resourceserver: 18 | jwt: 19 | issuer-uri: ${AUTHORIZATION_SERVER_URI:http://auth-server:9000} 20 | 21 | logging: 22 | level: 23 | io.r2dbc.postgresql.QUERY: DEBUG 24 | io.r2dbc.postgresql.PARAM: DEBUG 25 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/fallback/PaymentFallbackController.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.fallback; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | @RequestMapping("/payment-service-fallback") 10 | public class PaymentFallbackController { 11 | 12 | @GetMapping 13 | public ResponseEntity paymentFallbackService(){ 14 | return ResponseEntity.ok( 15 | "payment service un available !!" 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/dto/response/PaymentResponse.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.dto.response; 2 | 3 | import com.nasr.paymentservice.domain.enumeration.PaymentMode; 4 | import com.nasr.paymentservice.domain.enumeration.PaymentStatus; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Builder 16 | public class PaymentResponse { 17 | 18 | private Long id; 19 | private PaymentMode mode; 20 | private PaymentStatus status; 21 | private LocalDateTime paymentDate; 22 | } 23 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/repository/OrderDetailRepository.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.repository; 2 | 3 | import com.nasr.orderservice.domain.OrderDetail; 4 | import org.springframework.data.r2dbc.repository.Modifying; 5 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 6 | import org.springframework.stereotype.Repository; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | @Repository 11 | public interface OrderDetailRepository extends ReactiveCrudRepository { 12 | 13 | Flux findAllByOrderId(Long orderId); 14 | 15 | @Modifying 16 | Mono deleteAllByOrderId(Long orderId); 17 | } 18 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/repository/TransactionRepository.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.repository; 2 | 3 | import com.nasr.paymentservice.domain.Transaction; 4 | import com.nasr.paymentservice.dto.response.PaymentResponse; 5 | import org.springframework.data.r2dbc.repository.Query; 6 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 7 | import org.springframework.stereotype.Repository; 8 | import reactor.core.publisher.Mono; 9 | 10 | @Repository 11 | public interface TransactionRepository extends ReactiveCrudRepository { 12 | 13 | @Query("select p.* from transaction_table as p where p.order_Id= :orderId ") 14 | Mono findByOrderId(Long orderId); 15 | } 16 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.repository; 2 | 3 | import com.nasr.productservice.domain.Product; 4 | import org.springframework.data.domain.Sort; 5 | import org.springframework.data.r2dbc.repository.Query; 6 | import org.springframework.data.repository.query.Param; 7 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | 11 | public interface ProductRepository extends ReactiveCrudRepository { 12 | 13 | @Query("select count(p.id)>0 from product_table as p where p.name = :name ") 14 | Mono isExistsByName(String name); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/base/domain/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.base.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import org.springframework.data.annotation.Id; 8 | import org.springframework.data.annotation.Transient; 9 | 10 | import java.io.Serializable; 11 | 12 | @Setter 13 | @Getter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public abstract class BaseEntity implements Serializable { 17 | 18 | @Id 19 | protected ID id; 20 | 21 | @Transient 22 | protected Boolean isDeleted; 23 | 24 | public BaseEntity(ID id) { 25 | this.id = id; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.service; 2 | 3 | import com.nasr.orderservice.base.service.BaseService; 4 | import com.nasr.orderservice.dto.request.OrderRequest; 5 | import com.nasr.orderservice.dto.response.OrderResponse; 6 | import com.nasr.orderservice.external.response.ProductResponse; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | public interface OrderService extends BaseService { 11 | 12 | Mono completeOrderPlacedStatus(Long orderId); 13 | 14 | Flux getOrderPlacedProducts(Long orderId); 15 | 16 | Mono placeOrder(OrderRequest orderRequest); 17 | } 18 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/dto/response/TokenInfoResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.dto.response; 2 | 3 | import com.nasr.apigateway.external.response.UserResponseDto; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.time.Instant; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Builder 17 | public class TokenInfoResponseDto { 18 | 19 | private UserInfoResponseDto userInfo; 20 | private String accessToken; 21 | private String refreshToken; 22 | private Instant accessTokenExpireAt; 23 | private List authorities = new ArrayList<>(); 24 | } 25 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/nasr/orderservice/config/ProjectConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.config; 2 | 3 | import com.nasr.orderservice.TestServiceInstanceSupplier; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.TestConfiguration; 6 | import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @TestConfiguration 10 | public class ProjectConfig { 11 | 12 | @Autowired 13 | private WireMockConfig wiremock; 14 | 15 | @Bean 16 | public ServiceInstanceListSupplier supplier() { 17 | return new TestServiceInstanceSupplier(wiremock.getPort(), wiremock.getHost()); 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/dto/request/PaymentRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.dto.request; 2 | 3 | import com.nasr.paymentservice.domain.enumeration.PaymentMode; 4 | import com.nasr.paymentservice.domain.enumeration.PaymentStatus; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.validation.constraints.Min; 10 | import javax.validation.constraints.NotNull; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class PaymentRequest { 16 | 17 | @NotNull 18 | private PaymentMode mode; 19 | 20 | private AccountInfo accountInfo; 21 | 22 | private Double totalAmount; 23 | 24 | @NotNull 25 | @Min(value = 1) 26 | private Long orderId; 27 | } 28 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/dto/request/ProductRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.dto.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.Min; 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.NotNull; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class ProductRequest { 15 | 16 | @NotBlank(message = "productName is mandatory") 17 | private String name; 18 | 19 | @NotNull(message = "quantity of product is mandatory") 20 | @Min(value = 1) 21 | private Long quantity; 22 | 23 | @NotNull(message = "product price is mandatory") 24 | private Double price; 25 | } 26 | -------------------------------------------------------------------------------- /authorization-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9000 3 | 4 | spring: 5 | application: 6 | name: AUTHORIZATION-SERVER 7 | 8 | datasource: 9 | username: root 10 | password: MohammadN@sr13804804 11 | url: jdbc:mysql://${DB_HOST:localhost}:3306/microservice-ecommerce-users 12 | driver-class-name: com.mysql.cj.jdbc.Driver 13 | 14 | config: 15 | import: optional:configserver:${CONFIG_SERVER_URI:http://localhost:8888} 16 | jpa: 17 | show-sql: true 18 | hibernate: 19 | ddl-auto: update 20 | database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 21 | 22 | logging: 23 | level: 24 | root: INFO 25 | org.springframework.web: INFO 26 | org.springframework.security: trace 27 | org.springframework.security.jackson2: INFO -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/intercept/WebclientInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.intercept; 2 | 3 | import org.springframework.web.reactive.function.client.ClientRequest; 4 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 5 | import reactor.core.publisher.Mono; 6 | 7 | import static com.nasr.orderservice.constant.ConstantField.TOKEN_PREFIX; 8 | import static org.springframework.http.HttpHeaders.AUTHORIZATION; 9 | 10 | public interface WebclientInterceptor { 11 | 12 | static ExchangeFilterFunction interceptor(String token) { 13 | return ExchangeFilterFunction.ofRequestProcessor(request -> 14 | Mono.just(ClientRequest.from(request) 15 | .header(AUTHORIZATION, TOKEN_PREFIX.concat(token)).build())); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/intercept/WebclientInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.intercept; 2 | 3 | import org.springframework.web.reactive.function.client.ClientRequest; 4 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 5 | import reactor.core.publisher.Mono; 6 | 7 | import static com.nasr.paymentservice.constant.ConstantField.TOKEN_PREFIX; 8 | import static org.springframework.http.HttpHeaders.AUTHORIZATION; 9 | 10 | public interface WebclientInterceptor { 11 | 12 | static ExchangeFilterFunction interceptor(String token) { 13 | return ExchangeFilterFunction.ofRequestProcessor(request -> 14 | Mono.just(ClientRequest.from(request) 15 | .header(AUTHORIZATION, TOKEN_PREFIX.concat(token)).build())); 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/intercept/WebclientInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.intercept; 2 | 3 | import org.springframework.web.reactive.function.client.ClientRequest; 4 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 5 | import reactor.core.publisher.Mono; 6 | 7 | import static com.nasr.orderhandlerservice.constant.ConstantField.TOKEN_PREFIX; 8 | import static org.springframework.http.HttpHeaders.AUTHORIZATION; 9 | 10 | public interface WebclientInterceptor { 11 | 12 | static ExchangeFilterFunction interceptor(String token) { 13 | return ExchangeFilterFunction.ofRequestProcessor(request -> 14 | Mono.just(ClientRequest.from(request) 15 | .header(AUTHORIZATION, TOKEN_PREFIX.concat(token)).build())); 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /order-handler-service/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create schema if not exists order_schema; 2 | 3 | create table if not exists order_schema.order_table 4 | ( 5 | id 6 | bigserial 7 | not 8 | null 9 | primary 10 | key, 11 | order_date 12 | timestamp, 13 | total_price 14 | double 15 | precision 16 | not 17 | null, 18 | order_status 19 | varchar 20 | ( 21 | 50 22 | ) ); 23 | 24 | create table if not exists order_schema.order_detail_table 25 | ( 26 | id 27 | bigserial 28 | not 29 | null 30 | primary 31 | key, 32 | order_id 33 | bigint 34 | not 35 | null 36 | references 37 | order_schema 38 | . 39 | order_table 40 | ( 41 | id 42 | ) , 43 | product_id bigint not null references product_schema.product_table 44 | ( 45 | id 46 | ) ,product_number bigint not null ); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # springboot-microservice 2 | 3 | ![microservice-diagram](https://user-images.githubusercontent.com/76038143/199672306-b2ac8001-8e3b-404e-bb42-d0b036b44aa1.jpeg) 4 | 5 | #### this repository for learn better microservice with cool library called quartz (for scheduling job and trigger) also using spring reactive (for better performance in microservices architecture and back pressure) and component such as (service discovery - feign client - api gateway - resilience4j with circuit breaker - distribute log tracing 6 | #### such as zipkin & sleuth - and centralize configuration with config server) - and also secure token base application with oauth2 and openid connect - and use devops process for deploy application with automation tool such as docker - k8s - ci / cd pipeline 7 | #### and using rdbms with postgresql & MySQL -and inmemory such as redis - and also testing with junit and mockito - wiremock & assertj 8 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/util/Oauth2TokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.util; 2 | 3 | import org.springframework.security.core.GrantedAuthority; 4 | import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; 5 | import org.springframework.security.oauth2.core.user.OAuth2User; 6 | 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | import static com.nasr.apigateway.constant.ConstantField.TOKEN_PREFIX; 11 | 12 | public class Oauth2TokenUtil { 13 | 14 | public static List extractAuthority(OAuth2User oAuth2User) { 15 | return oAuth2User.getAuthorities() 16 | .stream().map(GrantedAuthority::getAuthority) 17 | .filter(authority -> authority.startsWith("ROLE_")) 18 | .collect(Collectors.toList()); 19 | } 20 | public static String getAuth(String token){ 21 | return TOKEN_PREFIX.concat(token); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/service/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.service; 2 | 3 | import com.nasr.productservice.base.service.BaseService; 4 | import com.nasr.productservice.domain.Product; 5 | import com.nasr.productservice.dto.request.DecreaseProductQuantityRequest; 6 | import com.nasr.productservice.dto.request.RevertProductRequest; 7 | import com.nasr.productservice.dto.response.ProductResponse; 8 | import org.springframework.http.ResponseEntity; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | import java.util.List; 13 | 14 | public interface ProductService extends BaseService { 15 | 16 | Flux getProductByIds(List ids); 17 | 18 | 19 | Mono decreaseQuantity(List dtos); 20 | 21 | Mono revertProducts(List revertProductRequests); 22 | } 23 | -------------------------------------------------------------------------------- /order-handler-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8082 3 | 4 | spring: 5 | application: 6 | name: ORDER-HANDLER-SERVICE 7 | 8 | config: 9 | import: optional:configserver:${CONFIG_SERVER_URI:http://localhost:8888} 10 | 11 | security: 12 | oauth2: 13 | resourceserver: 14 | jwt: 15 | issuer-uri: ${AUTHORIZATION_SERVER_URI:http://auth-server:9000} 16 | 17 | client: 18 | registration: 19 | client-internal: 20 | client-id: ecommerce-client 21 | client-secret: ecommerce-secret 22 | authorization-grant-type: client_credentials 23 | scope: read,write,internal 24 | provider: cusom-auth 25 | provider: 26 | cusom-auth: 27 | issuer-uri: ${AUTHORIZATION_SERVER_URI:http://auth-server:9000} 28 | 29 | logging: 30 | level: 31 | io.r2dbc.postgresql.QUERY: DEBUG 32 | io.r2dbc.postgresql.PARAM: DEBUG 33 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/domain/Role.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.*; 8 | 9 | import static com.nasr.authorizationserver.domain.Role.NAME; 10 | import static com.nasr.authorizationserver.domain.Role.TABLE_NAME; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Table(name = TABLE_NAME,uniqueConstraints = @UniqueConstraint(name = "define_unique",columnNames = NAME)) 16 | @Entity 17 | public class Role { 18 | 19 | public static final String TABLE_NAME="role_table"; 20 | public static final String NAME="name"; 21 | 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | 26 | @Column(name = NAME) 27 | private String name; 28 | 29 | public Role(String name) { 30 | this.name = name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/config/Oauth2LoginConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 5 | import org.springframework.security.config.web.server.ServerHttpSecurity; 6 | import org.springframework.security.web.server.SecurityWebFilterChain; 7 | 8 | @EnableWebFluxSecurity 9 | public class Oauth2LoginConfig { 10 | 11 | @Bean 12 | public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http){ 13 | http.csrf(ServerHttpSecurity.CsrfSpec::disable) 14 | .cors(ServerHttpSecurity.CorsSpec::disable) 15 | .authorizeExchange() 16 | .pathMatchers("/authenticate") 17 | .authenticated() 18 | .anyExchange() 19 | .permitAll(); 20 | 21 | http.oauth2Login(); 22 | 23 | return http.build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /order-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | spring: 3 | application: 4 | name: ORDER-SERVICE 5 | r2dbc: 6 | url: r2dbc:h2:mem:///orderdb 7 | username: user 8 | password: 1111 9 | 10 | security: 11 | oauth2: 12 | resourceserver: 13 | jwt: 14 | issuer-uri: ${AUTHORIZATION_SERVER_URI:http://auth-server:9000} 15 | # dont need to configure oauth2 client because connection to external service mocked via wiremock 16 | 17 | config: 18 | import: optional:configserver:${CONFIG_SERVER_URI:http://localhost:8888} 19 | 20 | profiles: 21 | default: test 22 | 23 | zipkin: 24 | enabled: false 25 | 26 | # we don't user resilience and circuit breaker because we dont call another service and we use wiremock 27 | 28 | logging: 29 | level: 30 | io.r2dbc.postgresql.QUERY: DEBUG 31 | io.r2dbc.postgresql.PARAM: DEBUG 32 | 33 | wiremock-server: 34 | port: 6060 35 | host: ${WIREMOCK_SERVER:localhost} 36 | eureka: 37 | client: 38 | enabled: false 39 | -------------------------------------------------------------------------------- /payment-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: PAYMENT-SERVICE 4 | r2dbc: 5 | url: r2dbc:h2:mem:///paymentdb 6 | username: user 7 | password: 1111 8 | 9 | security: 10 | oauth2: 11 | resourceserver: 12 | jwt: 13 | issuer-uri: ${AUTHORIZATION_SERVER_URI:http://auth-server:9000} 14 | # dont need to configure oauth2 client because connection to external service mocked via wiremock 15 | 16 | config: 17 | import: optional:configserver:${CONFIG_SERVER_URI:http://localhost:8888} 18 | 19 | zipkin: 20 | enabled: false 21 | 22 | profiles: 23 | default: test 24 | 25 | # we don't user resilience and circuit breaker because we dont call another service and we use wiremock 26 | 27 | logging: 28 | level: 29 | io.r2dbc.postgresql.QUERY: DEBUG 30 | io.r2dbc.postgresql.PARAM: DEBUG 31 | 32 | wiremock-server: 33 | port: 5050 34 | host: ${WIREMOCK_SERVER:localhost} 35 | eureka: 36 | client: 37 | enabled: false 38 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/config/SchemaConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.config; 2 | 3 | import io.r2dbc.spi.ConnectionFactory; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.io.ClassPathResource; 7 | import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer; 8 | import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; 9 | 10 | @Configuration 11 | public class SchemaConfig { 12 | 13 | @Bean 14 | ConnectionFactoryInitializer initializer( ConnectionFactory connectionFactory) { 15 | ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer(); 16 | initializer.setConnectionFactory(connectionFactory); 17 | ResourceDatabasePopulator resource = new ResourceDatabasePopulator(new ClassPathResource("schema.sql")); 18 | initializer.setDatabasePopulator(resource); 19 | return initializer; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.repository; 2 | 3 | import com.nasr.authorizationserver.domain.User; 4 | import com.nasr.authorizationserver.dto.response.UserResponseDto; 5 | import org.springframework.data.jpa.repository.EntityGraph; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Query; 8 | 9 | import java.util.Optional; 10 | 11 | public interface UserRepository extends JpaRepository { 12 | 13 | 14 | @EntityGraph(attributePaths = "role") 15 | Optional findWithRoleByEmail(String email); 16 | 17 | @Query(" select case when count(u.id)> 0 then true else false end from User as u") 18 | Boolean isExists(); 19 | 20 | @Query("select new com.nasr.authorizationserver.dto.response.UserResponseDto(u.id,u.firstName,u.lastName,u.email) " + 21 | "from User as u where u.email = :email") 22 | UserResponseDto findByUserName(String email); 23 | } 24 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/config/SchedulerConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.config; 2 | 3 | import com.nasr.orderhandlerservice.AutowiringSpringBeanJobFactory; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.scheduling.quartz.SchedulerFactoryBean; 8 | 9 | @Configuration 10 | public class SchedulerConfig { 11 | 12 | @Bean 13 | public SchedulerFactoryBean schedulerFactory(ApplicationContext applicationContext) { 14 | SchedulerFactoryBean factoryBean = new SchedulerFactoryBean(); 15 | AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); 16 | jobFactory.setApplicationContext(applicationContext); 17 | 18 | factoryBean.setJobFactory(jobFactory); 19 | factoryBean.setApplicationContextSchedulerContextKey("applicationContext"); 20 | return factoryBean; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/service/impl/StripePaymentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.service.impl; 2 | 3 | import com.nasr.paymentservice.domain.enumeration.PaymentMode; 4 | import com.nasr.paymentservice.dto.request.PaymentRequest; 5 | import com.nasr.paymentservice.service.PaymentService; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class StripePaymentServiceImpl implements PaymentService { 10 | 11 | @Override 12 | public void doPayment(PaymentRequest paymentRequest) throws Exception{ 13 | 14 | // we pay this order with accountInfo and base on payment mode 15 | // we can use third party payment service for do this 16 | // if payment is successfully then every thing ok 17 | //otherwise we throw exception which from third party payment 18 | // in this section we skip actual payment 19 | 20 | if (paymentRequest.getMode().equals(PaymentMode.CASH)) 21 | return; 22 | 23 | // connect to stripe api 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/domain/Product.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.domain; 2 | 3 | import com.nasr.productservice.base.domain.BaseEntity; 4 | import lombok.*; 5 | import org.springframework.data.relational.core.mapping.Table; 6 | 7 | @Setter 8 | @Getter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | @Table(name = Product.PRODUCT_TABLE) 13 | public class Product extends BaseEntity { 14 | 15 | public static final String PRODUCT_TABLE = "product_table"; 16 | 17 | private String name; 18 | private Long quantity; 19 | private Double price; 20 | 21 | private Long categoryId; 22 | 23 | public Product(String name, Long quantity, Double price) { 24 | this.name = name; 25 | this.quantity = quantity; 26 | this.price = price; 27 | } 28 | 29 | public Product(Long id, String name, Long quantity, Double price) { 30 | super(id); 31 | this.name = name; 32 | this.quantity = quantity; 33 | this.price = price; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /payment-service/src/test/java/com/nasr/paymentservice/config/ProjectConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.config; 2 | 3 | import com.nasr.paymentservice.TestServiceInstanceListSupplier; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.TestConfiguration; 6 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 7 | import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.web.reactive.function.client.WebClient; 10 | 11 | @TestConfiguration 12 | public class ProjectConfig { 13 | 14 | @Autowired 15 | private WireMockConfig wireMock; 16 | 17 | @Bean 18 | public ServiceInstanceListSupplier supplier() { 19 | return new TestServiceInstanceListSupplier(wireMock.getPort(), wireMock.getHost()); 20 | } 21 | 22 | @Bean 23 | @LoadBalanced 24 | public WebClient.Builder webClientBuilder() { 25 | return WebClient.builder(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/config/SchemaConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.config; 2 | 3 | import io.r2dbc.spi.ConnectionFactory; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.core.io.ClassPathResource; 8 | import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer; 9 | import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; 10 | 11 | @Configuration 12 | public class SchemaConfig { 13 | 14 | @Bean 15 | @Profile("default") 16 | ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) { 17 | ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer(); 18 | initializer.setConnectionFactory(connectionFactory); 19 | ResourceDatabasePopulator resource = new ResourceDatabasePopulator(new ClassPathResource("schema-init.sql")); 20 | initializer.setDatabasePopulator(resource); 21 | return initializer; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/config/SchemaConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.config; 2 | 3 | import io.r2dbc.spi.ConnectionFactory; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.core.io.ClassPathResource; 8 | import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer; 9 | import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; 10 | 11 | @Configuration 12 | @Profile("default") 13 | public class SchemaConfig { 14 | 15 | @Bean 16 | ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) { 17 | ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer(); 18 | initializer.setConnectionFactory(connectionFactory); 19 | ResourceDatabasePopulator resource = new ResourceDatabasePopulator(new ClassPathResource("schema-init.sql")); 20 | initializer.setDatabasePopulator(resource); 21 | return initializer; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 mohammad nasr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/domain/OrderDetail.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.domain; 2 | 3 | import com.nasr.orderservice.base.domain.BaseEntity; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | import org.springframework.data.relational.core.mapping.Column; 9 | import org.springframework.data.relational.core.mapping.Table; 10 | 11 | import static com.nasr.orderservice.domain.OrderDetail.TABLE_NAME; 12 | 13 | @Setter 14 | @Getter 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Table(name =TABLE_NAME ) 18 | public class OrderDetail extends BaseEntity { 19 | 20 | public static final String ORDER_ID="order_id"; 21 | public static final String PRODUCT_ID="product_id"; 22 | public static final String PRODUCT_NUMBER="product_number"; 23 | public static final String TABLE_NAME ="order_detail_table"; 24 | 25 | @Column(value = ORDER_ID) 26 | private Long orderId; 27 | 28 | @Column(value = PRODUCT_ID) 29 | private Long productId; 30 | 31 | @Column(value = PRODUCT_NUMBER) 32 | private Long productNumber; 33 | } 34 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/AutowiringSpringBeanJobFactory.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice; 2 | 3 | import org.quartz.spi.TriggerFiredBundle; 4 | import org.springframework.beans.BeansException; 5 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.context.ApplicationContextAware; 8 | import org.springframework.scheduling.quartz.SpringBeanJobFactory; 9 | 10 | public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { 11 | private transient AutowireCapableBeanFactory beanFactory; 12 | 13 | @Override 14 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 15 | beanFactory = applicationContext.getAutowireCapableBeanFactory(); 16 | } 17 | 18 | @Override 19 | protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { 20 | final Object job = super.createJobInstance(bundle); 21 | beanFactory.autowireBean(job); 22 | return job; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/config/CustomUserDetailService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.config; 2 | 3 | import com.nasr.authorizationserver.domain.User; 4 | import com.nasr.authorizationserver.service.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class CustomUserDetailService implements UserDetailsService { 13 | 14 | @Autowired 15 | private UserService userService; 16 | 17 | @Override 18 | public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { 19 | 20 | try { 21 | User user = userService.getUserWithRoleByUserName(email); 22 | return new CustomUserDetail(user); 23 | 24 | } catch (Exception e) { 25 | throw new UsernameNotFoundException(e.getMessage()); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.controller; 2 | 3 | import com.nasr.authorizationserver.dto.response.UserResponseDto; 4 | import com.nasr.authorizationserver.service.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | @RequestMapping("/users") 14 | public class UserController { 15 | 16 | @Autowired 17 | private UserService userService; 18 | 19 | /** 20 | * 21 | * @param authentication as authenticated user taken from SecurityContext 22 | * @return user info of logged in authorization server 23 | */ 24 | @GetMapping 25 | public ResponseEntity getUser(Authentication authentication){ 26 | UserResponseDto user = userService.getUserByUserName(authentication.getName()); 27 | return ResponseEntity.ok(user); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/config/AccessTokenDecoderConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.authentication.AuthenticationManager; 6 | import org.springframework.security.authentication.ProviderManager; 7 | import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; 8 | import org.springframework.security.oauth2.jwt.JwtDecoder; 9 | import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; 10 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; 11 | 12 | import java.security.interfaces.RSAPublicKey; 13 | 14 | @Configuration 15 | public class AccessTokenDecoderConfig { 16 | 17 | @Bean 18 | public AuthenticationManager bearerTokenAuthenticationManager() { 19 | 20 | JwtDecoder decoder= NimbusJwtDecoder.withPublicKey((RSAPublicKey) KeyPairConfig.getKeyPair().getPublic()) 21 | .signatureAlgorithm(SignatureAlgorithm.RS256) 22 | .build(); 23 | 24 | return new ProviderManager(new JwtAuthenticationProvider(decoder)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/nasr/orderservice/TestServiceInstanceSupplier.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice; 2 | 3 | 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.cloud.client.DefaultServiceInstance; 6 | import org.springframework.cloud.client.ServiceInstance; 7 | import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; 8 | import reactor.core.publisher.Flux; 9 | 10 | import java.util.List; 11 | 12 | @RequiredArgsConstructor 13 | public class TestServiceInstanceSupplier implements ServiceInstanceListSupplier { 14 | 15 | 16 | private final int port; 17 | 18 | private final String host; 19 | 20 | @Override 21 | public String getServiceId() { 22 | return "ORDER-SERVICE"; 23 | } 24 | 25 | @Override 26 | public Flux> get() { 27 | List serviceInstances = List.of( 28 | 29 | new DefaultServiceInstance("PRODUCT-SERVICE","PRODUCT-SERVICE", host,port,false), 30 | new DefaultServiceInstance("ORDER_HANDLER-SERVICE","ORDER_HANDLER-SERVICE", host,port,false), 31 | new DefaultServiceInstance("PAYMENT-SERVICE","PAYMENT-SERVICE", host,port,false) 32 | ); 33 | 34 | return Flux.just(serviceInstances); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /payment-service/src/test/java/com/nasr/paymentservice/TestServiceInstanceListSupplier.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.cloud.client.DefaultServiceInstance; 6 | import org.springframework.cloud.client.ServiceInstance; 7 | import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; 8 | import reactor.core.publisher.Flux; 9 | 10 | import java.util.List; 11 | 12 | @RequiredArgsConstructor 13 | @Getter 14 | public class TestServiceInstanceListSupplier implements ServiceInstanceListSupplier { 15 | 16 | private final int port; 17 | private final String host; 18 | 19 | @Override 20 | public String getServiceId() { 21 | return "PAYMENT-SERVICE"; 22 | } 23 | 24 | @Override 25 | public Flux> get() { 26 | 27 | List serviceInstances = List.of( 28 | new DefaultServiceInstance("ORDER-SERVICE","ORDER-SERVICE",host,port,false), 29 | new DefaultServiceInstance("PRODUCT-SERVICE","PRODUCT-SERVICE",host,port,false), 30 | new DefaultServiceInstance("ORDER-HANDLER-SERVICE","ORDER-HANDLER-SERVICE",host,port,false) 31 | ); 32 | 33 | return Flux.just(serviceInstances); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/config/CircuitBreakerConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.config; 2 | 3 | import io.github.resilience4j.timelimiter.TimeLimiterConfig; 4 | import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory; 5 | import org.springframework.cloud.client.circuitbreaker.Customizer; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | public class CircuitBreakerConfig { 11 | 12 | @Bean 13 | public Customizer productServiceCustomizer(){ 14 | 15 | return factory -> factory.configure(builder -> 16 | builder.circuitBreakerConfig(io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.ofDefaults()) 17 | .timeLimiterConfig(TimeLimiterConfig.ofDefaults()),"productService"); 18 | 19 | } 20 | @Bean 21 | public Customizer orderHandlerServiceCustomizer(){ 22 | 23 | return factory -> factory.configure(builder -> 24 | builder.circuitBreakerConfig(io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.ofDefaults()) 25 | .timeLimiterConfig(TimeLimiterConfig.ofDefaults()),"orderHandlerService"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/model/request/TriggerDescriptorRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.model.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import org.quartz.DateBuilder; 8 | import org.quartz.Trigger; 9 | 10 | import javax.validation.constraints.Min; 11 | import javax.validation.constraints.NotNull; 12 | import java.util.UUID; 13 | 14 | import static org.quartz.DateBuilder.futureDate; 15 | import static org.quartz.TriggerBuilder.newTrigger; 16 | import static org.springframework.util.StringUtils.isEmpty; 17 | 18 | 19 | @Getter 20 | @Setter 21 | @NoArgsConstructor 22 | @AllArgsConstructor 23 | public class TriggerDescriptorRequest { 24 | 25 | @NotNull(message = "name of job is mandatory !") 26 | private String name; 27 | 28 | private String group; 29 | 30 | @Min(value = 1) 31 | private int hour; 32 | 33 | private String buildName(){ 34 | return isEmpty(name) ? UUID.randomUUID().toString() : name; 35 | } 36 | 37 | public Trigger buildTrigger(){ 38 | return newTrigger() 39 | .withIdentity(buildName(),group) 40 | .startAt(futureDate(hour, DateBuilder.IntervalUnit.HOUR)) 41 | .build(); 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/service/OrderPlacedHandlerService.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.service; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.nasr.orderhandlerservice.model.request.JobDescriptorRequest; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.extern.log4j.Log4j2; 7 | import org.quartz.JobDetail; 8 | import org.quartz.Scheduler; 9 | import org.quartz.Trigger; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.Set; 13 | 14 | @Service 15 | @RequiredArgsConstructor 16 | @Log4j2 17 | public class OrderPlacedHandlerService { 18 | 19 | private final Scheduler scheduler; 20 | 21 | public JobDescriptorRequest createJob(JobDescriptorRequest descriptor) throws JsonProcessingException { 22 | JobDetail jobDetail = descriptor.buildJobDetail(); 23 | Set triggers = descriptor.buildTriggers(); 24 | 25 | try { 26 | scheduler.scheduleJob(jobDetail,triggers,false); 27 | log.info(" job with key : {} successfully saved !",jobDetail.getKey()); 28 | }catch (Exception e){ 29 | log.error("could not save job with key : {} ",jobDetail.getKey()); 30 | throw new IllegalStateException(e.getMessage()); 31 | } 32 | return descriptor; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /order-service/circuit-breaker-note.txt: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------------------------------------- 2 | import note of circuit breaker 3 | if specific exception occurred and you want to consider as fail and consider as business exception 4 | for example error 404 or 400 you use ignoreException for circuit breaker but fallback method work similar 5 | try catch block and you need to restrict this exception from block or you dont access to do this maybe you dont use sequential programming 6 | to declare multiple fallback method you can throw this exception but on next step check this is business exception or as failure 7 | --- very important note --- dont think if fall back method executed then certainly request was failed - in my case get error 400 8 | for cant decrease product from stock in product service and convert to ExternalServiceException then circuit breaker understand 9 | to ignore externalServiceException as failure but we declare every Exception in fallback method then fallback method called while request 10 | dont consider as failure - for this reason better to restrict domain of exception in fallback method or handle this exception in fallback method 11 | ----------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/domain/Order.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.domain; 2 | 3 | import com.nasr.orderservice.base.domain.BaseEntity; 4 | import lombok.*; 5 | import org.springframework.data.relational.core.mapping.Column; 6 | import org.springframework.data.relational.core.mapping.Table; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | import static com.nasr.orderservice.domain.Order.TABLE_NAME; 11 | 12 | @Setter 13 | @Getter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Table(name = TABLE_NAME) 17 | @Builder 18 | public class Order extends BaseEntity { 19 | 20 | public static final String TABLE_NAME = "order_table"; 21 | public static final String ORDER_DATE ="order_date"; 22 | public static final String TOTAL_PRICE ="total_price"; 23 | public static final String ORDER_STATUS ="order_status"; 24 | 25 | @Column(value = ORDER_DATE) 26 | private LocalDateTime orderDate; 27 | 28 | @Column(value = TOTAL_PRICE) 29 | private Double totalPrice; 30 | 31 | @Column(value = ORDER_STATUS) 32 | private String orderStatus; 33 | 34 | public Order(Long id, LocalDateTime orderDate, Double totalPrice, String orderStatus) { 35 | super(id); 36 | this.orderDate = orderDate; 37 | this.totalPrice = totalPrice; 38 | this.orderStatus = orderStatus; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/config/CustomUserDetail.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.config; 2 | 3 | import com.nasr.authorizationserver.domain.User; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | 9 | import java.util.Collection; 10 | import java.util.Collections; 11 | 12 | @RequiredArgsConstructor 13 | public class CustomUserDetail implements UserDetails { 14 | 15 | private final User user; 16 | 17 | @Override 18 | public Collection getAuthorities() { 19 | return Collections.singletonList( 20 | new SimpleGrantedAuthority(user.getRole().getName()) 21 | ); 22 | } 23 | 24 | @Override 25 | public String getPassword() { 26 | return user.getPassword(); 27 | } 28 | 29 | @Override 30 | public String getUsername() { 31 | return user.getEmail(); 32 | } 33 | 34 | @Override 35 | public boolean isAccountNonExpired() { 36 | return false; 37 | } 38 | 39 | @Override 40 | public boolean isAccountNonLocked() { 41 | return false; 42 | } 43 | 44 | @Override 45 | public boolean isCredentialsNonExpired() { 46 | return false; 47 | } 48 | 49 | @Override 50 | public boolean isEnabled() { 51 | return true; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/controller/PaymentController.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.controller; 2 | 3 | import com.nasr.paymentservice.dto.request.PaymentRequest; 4 | import com.nasr.paymentservice.dto.response.PaymentResponse; 5 | import com.nasr.paymentservice.service.TransactionService; 6 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.security.access.prepost.PreAuthorize; 10 | import org.springframework.web.bind.annotation.*; 11 | import reactor.core.publisher.Mono; 12 | 13 | import javax.validation.Valid; 14 | 15 | @RestController 16 | @RequestMapping("/api/v1/payment") 17 | @SecurityRequirement(name = "Bearer Authentication") 18 | public class PaymentController { 19 | 20 | @Autowired 21 | private TransactionService paymentService; 22 | 23 | @PostMapping("/doPayment") 24 | @PreAuthorize("hasRole('USER')") 25 | public Mono> doPayment(@RequestBody @Valid PaymentRequest paymentRequest) { 26 | return paymentService.doPayment(paymentRequest) 27 | .map(ResponseEntity::ok); 28 | } 29 | 30 | @GetMapping("/{orderId}") 31 | @PreAuthorize("hasAnyRole('USER','ADMIN','SUPER_ADMIN')") 32 | public Mono> getPaymentByOrderId(@PathVariable Long orderId){ 33 | return paymentService.getByOrderId(orderId) 34 | .map(ResponseEntity::ok); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/domain/Transaction.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.domain; 2 | 3 | import com.nasr.paymentservice.base.domain.BaseEntity; 4 | import com.nasr.paymentservice.domain.enumeration.PaymentMode; 5 | import com.nasr.paymentservice.domain.enumeration.PaymentStatus; 6 | import lombok.*; 7 | import org.springframework.data.relational.core.mapping.Column; 8 | import org.springframework.data.relational.core.mapping.Table; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | import static com.nasr.paymentservice.domain.Transaction.TABLE_NAME; 13 | 14 | @Setter 15 | @Getter 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @Table(TABLE_NAME) 19 | @Builder 20 | public class Transaction extends BaseEntity implements Cloneable { 21 | 22 | public static final String ORDER_ID = "order_id"; 23 | public static final String TABLE_NAME = "transaction_table"; 24 | public static final String PAYMENT_MODE = "payment_mode"; 25 | public static final String PAYMENT_STATUS = "payment_status"; 26 | public static final String PAYMENT_DATE = "payment_date"; 27 | 28 | @Column(value = PAYMENT_MODE) 29 | private PaymentMode mode; 30 | 31 | @Column(value = PAYMENT_STATUS) 32 | private PaymentStatus status; 33 | 34 | @Column(value = PAYMENT_DATE) 35 | private LocalDateTime paymentDate; 36 | 37 | @Column(value = ORDER_ID) 38 | private Long orderId; 39 | 40 | @Override 41 | public Object clone() throws CloneNotSupportedException { 42 | return super.clone(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/ApiGatewayApplication.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 6 | import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; 7 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 8 | import org.springframework.context.ConfigurableApplicationContext; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.web.reactive.function.client.WebClient; 11 | import reactor.core.publisher.Mono; 12 | 13 | @SpringBootApplication 14 | @EnableEurekaClient 15 | public class ApiGatewayApplication { 16 | 17 | public static void main(String[] args) { 18 | ConfigurableApplicationContext context = SpringApplication.run(ApiGatewayApplication.class, args); 19 | 20 | } 21 | 22 | @Bean(name = "userKeyResolver") 23 | // with this key we can keep track of all user rate limiter information 24 | // for example we store information with user1 have 3 request per second 25 | // we usually use principal name of session - because there are difference for every use 26 | // and that we able to add restriction of rate limiter base on every user for example 27 | // 28 | KeyResolver userKeyResolver() { 29 | return exchange -> Mono.just("userKey"); 30 | } 31 | 32 | @Bean 33 | @LoadBalanced 34 | public WebClient.Builder webClientBuilder(){ 35 | return WebClient.builder(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.*; 8 | 9 | import static com.nasr.authorizationserver.domain.User.EMAIL; 10 | import static com.nasr.authorizationserver.domain.User.TABLE_NAME; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Entity 16 | @Table(name = TABLE_NAME,uniqueConstraints = @UniqueConstraint(name = "define_unique",columnNames = EMAIL)) 17 | 18 | public class User { 19 | 20 | public static final String TABLE_NAME="user_table"; 21 | private static final String FIRST_NAME="first_name"; 22 | private static final String LAST_NAME="last_name"; 23 | public static final String EMAIL="email"; 24 | private static final String PASSWORD="password"; 25 | private static final String ROLE_ID="role_id"; 26 | 27 | @Id 28 | @GeneratedValue(strategy = GenerationType.IDENTITY) 29 | private Long id; 30 | 31 | @Column(name = FIRST_NAME,nullable = false) 32 | private String firstName; 33 | 34 | @Column(name = LAST_NAME,nullable = false) 35 | private String lastName; 36 | 37 | @Column(name = EMAIL) 38 | private String email; 39 | 40 | @Column(name = PASSWORD ,nullable = false) 41 | private String password; 42 | 43 | @ManyToOne(fetch = FetchType.LAZY) 44 | @JoinColumn(name = ROLE_ID) 45 | private Role role; 46 | 47 | public User(String firstName, String lastName, String email, String password) { 48 | this.firstName = firstName; 49 | this.lastName = lastName; 50 | this.email = email; 51 | this.password = password; 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /payment-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8083 3 | 4 | spring: 5 | application: 6 | name: PAYMENT-SERVICE 7 | r2dbc: 8 | url: r2dbc:postgresql://${DB_HOST:localhost:5432}/microservice-ecommerce?schema=payment_schema 9 | username: postgres 10 | password: MohammadN@sr13804804 11 | 12 | security: 13 | oauth2: 14 | resourceserver: 15 | jwt: 16 | issuer-uri: ${AUTHORIZATION_SERVER_URI:http://auth-server:9000} 17 | 18 | client: 19 | registration: 20 | client-internal: 21 | provider: spring 22 | client-id: ecommerce-client 23 | client-secret: ecommerce-secret 24 | authorization-grant-type: client_credentials 25 | scope: internal,read,write 26 | provider: 27 | spring: 28 | issuer-uri: ${AUTHORIZATION_SERVER_URI:http://auth-server:9000} 29 | 30 | config: 31 | import: optional:configserver:${CONFIG_SERVER_URI:http://localhost:8888} 32 | 33 | logging: 34 | level: 35 | io.r2dbc.postgresql.QUERY: DEBUG 36 | io.r2dbc.postgresql.PARAM: DEBUG 37 | 38 | resilience4j: 39 | circuitbreaker: 40 | configs: 41 | default: 42 | slidingWindowSize: 10 43 | slidingWindowType: COUNT_BASED 44 | permittedNumberOfCallsInHalfOpenState: 4 45 | failureRateThreshold: 50 46 | waitDurationInOpenState: 10s 47 | registerHealthIndicator: true 48 | automaticTransitionFromOpenToHalfOpenEnabled: true 49 | ignoreExceptions: 50 | - com.nasr.paymentservice.exception.ExternalServiceException 51 | instances: 52 | orderService: 53 | baseConfig: default 54 | management: 55 | health: 56 | circuitbreakers: 57 | enabled: true 58 | endpoint: 59 | health: 60 | show-details: always -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/exception/RestResponseEntityExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.exception; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.security.access.AccessDeniedException; 11 | import org.springframework.web.bind.annotation.ExceptionHandler; 12 | import org.springframework.web.bind.annotation.RestControllerAdvice; 13 | 14 | @RestControllerAdvice 15 | @Slf4j 16 | public class RestResponseEntityExceptionHandler { 17 | 18 | @ExceptionHandler(EntityNotFoundException.class) 19 | public ResponseEntity entityNotFoundExceptionHandler(EntityNotFoundException e){ 20 | return ResponseEntity.status(HttpStatus.NOT_FOUND) 21 | .body(new ErrorResponse(HttpStatus.NOT_FOUND,e.getMessage())); 22 | } 23 | 24 | @ExceptionHandler(EntityNotValidException.class) 25 | public ResponseEntity entityNotValidExceptionHandler(EntityNotValidException e){ 26 | return ResponseEntity.status(HttpStatus.BAD_REQUEST) 27 | .body(new ErrorResponse(HttpStatus.BAD_REQUEST,e.getMessage())); 28 | } 29 | 30 | @ExceptionHandler(AccessDeniedException.class) 31 | public ResponseEntity accessDeniedExceptionHandler(AccessDeniedException e){ 32 | return ResponseEntity.status(HttpStatus.FORBIDDEN) 33 | .body(new ErrorResponse(HttpStatus.FORBIDDEN,e.getMessage())); 34 | } 35 | } 36 | 37 | @Data 38 | @NoArgsConstructor 39 | @AllArgsConstructor 40 | @Builder 41 | class ErrorResponse { 42 | private HttpStatus errorCode; 43 | private String message; 44 | } -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/service/impl/OrderDetailServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.service.impl; 2 | 3 | import com.nasr.orderservice.base.mapper.BaseMapper; 4 | import com.nasr.orderservice.base.service.impl.BaseServiceImpl; 5 | import com.nasr.orderservice.domain.OrderDetail; 6 | import com.nasr.orderservice.dto.request.OrderDetailRequest; 7 | import com.nasr.orderservice.dto.response.OrderDetailResponse; 8 | import com.nasr.orderservice.exception.OrderDetailNotFoundException; 9 | import com.nasr.orderservice.exception.OrderNotFoundException; 10 | import com.nasr.orderservice.repository.OrderDetailRepository; 11 | import com.nasr.orderservice.service.OrderDetailService; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.transaction.annotation.Transactional; 14 | import reactor.core.publisher.Flux; 15 | import reactor.core.publisher.Mono; 16 | 17 | @Service 18 | @Transactional(readOnly = true) 19 | public class OrderDetailServiceImpl extends BaseServiceImpl implements OrderDetailService { 20 | 21 | 22 | public OrderDetailServiceImpl(OrderDetailRepository repository, BaseMapper mapper) { 23 | super(repository, mapper); 24 | } 25 | 26 | @Override 27 | public Class getEntityClass() { 28 | return OrderDetail.class; 29 | } 30 | 31 | @Override 32 | public Flux getOrderDetailsByOrderId(Long orderId) { 33 | return repository.findAllByOrderId(orderId) 34 | .map(mapper::convertEntityToDto) 35 | .log(); 36 | } 37 | 38 | @Override 39 | @Transactional 40 | public Mono deleteOrderDetailByOrderId(Long orderId) { 41 | return repository.deleteAllByOrderId(orderId) 42 | .log(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/controller/OrderPlacedHandlerController.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.controller; 2 | 3 | import com.nasr.orderhandlerservice.model.request.JobDescriptorRequest; 4 | import com.nasr.orderhandlerservice.service.OrderPlacedHandlerService; 5 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import javax.validation.Valid; 12 | 13 | @RestController 14 | @RequestMapping("/api/v1/orderPlaceHandler") 15 | @SecurityRequirement(name = "Bearer Authentication") 16 | public class OrderPlacedHandlerController { 17 | 18 | @Autowired 19 | private OrderPlacedHandlerService orderPlacedHandlerService; 20 | 21 | /** 22 | * 23 | * @param jobDescriptor as body we can schedule on 24 | * @param group name of job 25 | * @return job 26 | * description 27 | * if customer order product, and we decrease product quantity accordingly customer ordered 28 | * but after ordering customer don't pay then we give him 1 hour to continue payment order 29 | * delivered to shipment system otherwise we delete this order and revert which products to stock 30 | */ 31 | 32 | @PostMapping("/groups/{group}/jobs") 33 | public ResponseEntity createJob(@RequestBody @Valid JobDescriptorRequest jobDescriptor, @PathVariable String group){ 34 | jobDescriptor.setGroup(group); 35 | try { 36 | JobDescriptorRequest job = orderPlacedHandlerService.createJob(jobDescriptor); 37 | return ResponseEntity.ok(job); 38 | 39 | }catch (Exception e){ 40 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) 41 | .body(e.getMessage()); 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.service.impl; 2 | 3 | import com.nasr.authorizationserver.domain.User; 4 | import com.nasr.authorizationserver.dto.response.UserResponseDto; 5 | import com.nasr.authorizationserver.repository.UserRepository; 6 | import com.nasr.authorizationserver.service.UserService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | @Service 15 | @Transactional(readOnly = true) 16 | public class UserServiceImpl implements UserService { 17 | 18 | @Autowired 19 | private UserRepository userRepository; 20 | 21 | @Override 22 | public User getUserWithRoleByUserName(String email) throws Exception { 23 | return userRepository.findWithRoleByEmail(email) 24 | .orElseThrow(() -> new IllegalStateException("dont find any user with email : " + email)); 25 | 26 | } 27 | 28 | @Override 29 | public Boolean isExists() { 30 | return userRepository.isExists(); 31 | } 32 | 33 | @Override 34 | @Transactional 35 | public List saveAll(Iterable users) { 36 | return userRepository.saveAll(users) 37 | .stream() 38 | .map(user -> UserResponseDto.builder() 39 | .id(user.getId()) 40 | .firstName(user.getFirstName()) 41 | .lastName(user.getLastName()) 42 | .email(user.getEmail()) 43 | .build()) 44 | .collect(Collectors.toList()); 45 | } 46 | 47 | @Override 48 | public UserResponseDto getUserByUserName(String email) { 49 | return userRepository.findByUserName(email); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /payment-service/src/test/java/com/nasr/paymentservice/repository/TransactionRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.repository; 2 | 3 | import com.nasr.paymentservice.domain.Transaction; 4 | import com.nasr.paymentservice.domain.enumeration.PaymentMode; 5 | import com.nasr.paymentservice.domain.enumeration.PaymentStatus; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest; 10 | import reactor.core.publisher.Mono; 11 | import reactor.test.StepVerifier; 12 | 13 | import java.time.LocalDateTime; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | 17 | @DataR2dbcTest 18 | class TransactionRepositoryTest { 19 | 20 | @Autowired 21 | private TransactionRepository underTest; 22 | 23 | private void transactionInitializer(Transaction transaction){ 24 | Mono mono = underTest.save(transaction); 25 | 26 | StepVerifier.create(mono) 27 | .expectNextCount(1) 28 | .verifyComplete(); 29 | } 30 | 31 | @Test 32 | @DisplayName("this unit test for get transaction detail by order id") 33 | void itShouldFindByOrderId() { 34 | 35 | // given 36 | Transaction transaction = Transaction.builder() 37 | .orderId(1L) 38 | .mode(PaymentMode.CASH) 39 | .paymentDate(LocalDateTime.now()) 40 | .status(PaymentStatus.SUCCESS) 41 | .build(); 42 | 43 | // when 44 | transactionInitializer(transaction); 45 | Mono result = underTest.findByOrderId(1L); 46 | //then 47 | 48 | StepVerifier.create(result) 49 | .assertNext(response -> { 50 | assertThat(response).isNotNull(); 51 | assertThat(response.getId()).isNotNull(); 52 | }) 53 | .verifyComplete(); 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/config/CustomAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Lazy; 5 | import org.springframework.security.authentication.AuthenticationProvider; 6 | import org.springframework.security.authentication.BadCredentialsException; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.crypto.password.PasswordEncoder; 13 | import org.springframework.stereotype.Component; 14 | 15 | @Component 16 | public class CustomAuthenticationProvider implements AuthenticationProvider { 17 | 18 | @Autowired 19 | private UserDetailsService userDetailsService; 20 | 21 | @Autowired 22 | @Lazy 23 | private PasswordEncoder passwordEncoder; 24 | 25 | @Override 26 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 27 | String userName = authentication.getName(); 28 | String password = authentication.getCredentials().toString(); 29 | 30 | 31 | UserDetails userDetails = userDetailsService.loadUserByUsername(userName); 32 | 33 | if (!passwordEncoder.matches(password, userDetails.getPassword())) 34 | throw new BadCredentialsException("your password wrong !!!"); 35 | 36 | return new UsernamePasswordAuthenticationToken( 37 | userName, password, 38 | userDetails.getAuthorities() 39 | ); 40 | } 41 | 42 | @Override 43 | public boolean supports(Class authentication) { 44 | return authentication.equals(UsernamePasswordAuthenticationToken.class); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/service/impl/RoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.service.impl; 2 | 3 | import com.nasr.authorizationserver.domain.Role; 4 | import com.nasr.authorizationserver.dto.response.RoleResponseDto; 5 | import com.nasr.authorizationserver.repository.RoleRepository; 6 | import com.nasr.authorizationserver.service.RoleService; 7 | import org.springframework.beans.BeanUtils; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | @Service 16 | public class RoleServiceImpl implements RoleService { 17 | 18 | @Autowired 19 | private RoleRepository repository; 20 | 21 | @Override 22 | public RoleResponseDto getRoleById(Long id) { 23 | Role role = repository.findById(id) 24 | .orElseThrow(() -> new IllegalStateException("dont find any role with id : " + id)); 25 | 26 | RoleResponseDto dto =new RoleResponseDto(); 27 | BeanUtils.copyProperties(role,dto); 28 | return dto; 29 | } 30 | 31 | @Override 32 | public Role getRoleByName(String name) { 33 | 34 | return repository.findByName(name) 35 | .orElseThrow(() -> new IllegalStateException("dont find any role with name : " + name)); 36 | } 37 | 38 | @Override 39 | public Boolean isExists() { 40 | return repository.isExists(); 41 | } 42 | 43 | @Override 44 | @Transactional 45 | public List saveAll(Iterable roles) { 46 | 47 | return repository.saveAll(roles) 48 | .stream() 49 | .map(role -> { 50 | RoleResponseDto responseDto = new RoleResponseDto(); 51 | BeanUtils.copyProperties(role,responseDto); 52 | return responseDto; 53 | }) 54 | .collect(Collectors.toList()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/nasr/orderservice/repository/OrderDetailRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.repository; 2 | 3 | import com.nasr.orderservice.domain.OrderDetail; 4 | import org.junit.jupiter.api.*; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest; 7 | import org.springframework.test.context.ActiveProfiles; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | import reactor.test.StepVerifier; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | @DataR2dbcTest 15 | @TestInstance(value = TestInstance.Lifecycle.PER_CLASS) 16 | class OrderDetailRepositoryTest { 17 | 18 | @Autowired 19 | private OrderDetailRepository underTest; 20 | 21 | 22 | @BeforeAll 23 | void setUp() { 24 | Mono orderDetail = underTest.save(new OrderDetail(1L, 2L, 5L)); 25 | 26 | StepVerifier.create(orderDetail) 27 | .expectNextCount(1) 28 | .verifyComplete(); 29 | } 30 | 31 | @Test 32 | @DisplayName("this unit test for get all orderDetail by orderId") 33 | void itShouldFindAllByOrderId() { 34 | 35 | // given 36 | Long orderId = 1L; 37 | 38 | // when 39 | Flux orderDetails = underTest.findAllByOrderId(orderId); 40 | 41 | //then 42 | StepVerifier.create(orderDetails) 43 | .assertNext(order -> { 44 | assertThat(order).isNotNull(); 45 | assertThat(order.getId()).isOne(); 46 | }) 47 | .verifyComplete(); 48 | } 49 | 50 | @Test 51 | @DisplayName("this unit test for delete orderDetails by orderId") 52 | void itShouldDeleteAllByOrderId() { 53 | 54 | // given 55 | Long orderId = 1L; 56 | 57 | // when 58 | Mono mono = underTest.deleteAllByOrderId(orderId); 59 | 60 | //then 61 | StepVerifier.create(mono) 62 | .verifyComplete(); 63 | } 64 | } -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/config/KeyPairConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.config; 2 | 3 | import com.nimbusds.jose.jwk.JWKSet; 4 | import com.nimbusds.jose.jwk.RSAKey; 5 | import com.nimbusds.jose.jwk.source.JWKSource; 6 | import com.nimbusds.jose.proc.SecurityContext; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.core.annotation.Order; 10 | 11 | import java.security.KeyPair; 12 | import java.security.KeyPairGenerator; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.interfaces.RSAPrivateKey; 15 | import java.security.interfaces.RSAPublicKey; 16 | import java.util.UUID; 17 | 18 | @Configuration 19 | public class KeyPairConfig { 20 | 21 | private static KeyPair keyPair; 22 | 23 | static { 24 | try { 25 | keyPair = generateRsaKey(); 26 | } catch (NoSuchAlgorithmException e) { 27 | e.printStackTrace(); 28 | } 29 | } 30 | @Bean 31 | public JWKSource jwkSource() throws NoSuchAlgorithmException { 32 | RSAKey rsaKey = generateRsa(); 33 | JWKSet jwkSet = new JWKSet(rsaKey); 34 | 35 | return ((jwkSelector, context) -> jwkSelector.select(jwkSet)); 36 | } 37 | 38 | private RSAKey generateRsa() { 39 | 40 | RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); 41 | RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); 42 | 43 | return new RSAKey.Builder(publicKey) 44 | .privateKey(privateKey) 45 | .keyID(UUID.randomUUID().toString()) 46 | .build(); 47 | } 48 | 49 | public static KeyPair generateRsaKey() throws NoSuchAlgorithmException { 50 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); 51 | keyPairGenerator.initialize(2048); 52 | return keyPairGenerator.generateKeyPair(); 53 | } 54 | 55 | public static KeyPair getKeyPair() { 56 | return keyPair; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/config/ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.http.HttpMethod; 5 | import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; 6 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 7 | import org.springframework.security.config.web.server.ServerHttpSecurity; 8 | import org.springframework.security.web.server.SecurityWebFilterChain; 9 | import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository; 10 | 11 | @EnableWebFluxSecurity 12 | @EnableReactiveMethodSecurity 13 | public class ResourceServerConfig { 14 | 15 | @Bean 16 | public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { 17 | 18 | http.cors(ServerHttpSecurity.CorsSpec::disable) 19 | .csrf(ServerHttpSecurity.CsrfSpec::disable) 20 | .authorizeExchange() 21 | .pathMatchers("/v3/api-docs/**", "/webjars/**") 22 | .permitAll() 23 | .pathMatchers(HttpMethod.GET,"/**") 24 | .hasAuthority("SCOPE_read") 25 | .pathMatchers(HttpMethod.POST,"/**") 26 | .hasAuthority("SCOPE_write") 27 | .pathMatchers(HttpMethod.DELETE,"/**") 28 | .hasAuthority("SCOPE_write") 29 | .pathMatchers(HttpMethod.PATCH,"/**") 30 | .hasAuthority("SCOPE_write") 31 | .pathMatchers(HttpMethod.PUT,"/**") 32 | .hasAuthority("SCOPE_write") 33 | .pathMatchers("/api/v1/orderPlaceHandler/**") 34 | .hasAuthority("SCOPE_internal") 35 | .anyExchange() 36 | .authenticated() 37 | .and() 38 | .oauth2ResourceServer() 39 | .jwt(); 40 | 41 | http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); 42 | 43 | return http.build(); 44 | } 45 | } -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/exception/RestResponseEntityExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.security.access.AccessDeniedException; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.RestControllerAdvice; 8 | 9 | @RestControllerAdvice 10 | public class RestResponseEntityExceptionHandler { 11 | 12 | @ExceptionHandler(EntityNotFoundException.class) 13 | public ResponseEntity entityNotFoundExceptionHandler(EntityNotFoundException e){ 14 | return ResponseEntity.status(HttpStatus.NOT_FOUND) 15 | .body(new ErrorResponse(e.getMessage(),HttpStatus.NOT_FOUND)); 16 | } 17 | 18 | @ExceptionHandler(InvalidPaymentException.class) 19 | public ResponseEntity paymentDoesntExceptionHandler(InvalidPaymentException e){ 20 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) 21 | .body(new ErrorResponse(e.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR)); 22 | } 23 | @ExceptionHandler(ExternalServiceException.class) 24 | public ResponseEntity externalServiceExceptionHandler(ExternalServiceException e){ 25 | return ResponseEntity.status(e.getErrorCode()) 26 | .body(new ErrorResponse(e.getMessage(),e.getErrorCode())); 27 | } 28 | 29 | @ExceptionHandler(AccessDeniedException.class) 30 | public ResponseEntity accessDeniedExceptionHandler(AccessDeniedException e){ 31 | return ResponseEntity.status(HttpStatus.FORBIDDEN) 32 | .body(new ErrorResponse(e.getMessage(),HttpStatus.FORBIDDEN)); 33 | } 34 | 35 | @ExceptionHandler(Exception.class) 36 | public ResponseEntity genericExceptionHandler(Exception e){ 37 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) 38 | .body(new ErrorResponse(e.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/exception/RestResponseEntityExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.exception; 2 | 3 | import lombok.extern.log4j.Log4j2; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.access.AccessDeniedException; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | 10 | @RestControllerAdvice 11 | @Log4j2 12 | public class RestResponseEntityExceptionHandler { 13 | 14 | @ExceptionHandler(EntityNotValidException.class) 15 | public ResponseEntity entityNotValidExceptionHandler(EntityNotValidException e){ 16 | return ResponseEntity.status(HttpStatus.BAD_REQUEST) 17 | .body(new ErrorResponse(e.getMessage(),HttpStatus.BAD_REQUEST)); 18 | } 19 | 20 | @ExceptionHandler(EntityNotFoundException.class) 21 | public ResponseEntity entityNotFoundExceptionHandler(EntityNotFoundException e){ 22 | return ResponseEntity.status(HttpStatus.NOT_FOUND) 23 | .body(new ErrorResponse(e.getMessage(),HttpStatus.NOT_FOUND)); 24 | } 25 | 26 | @ExceptionHandler(ExternalServiceException.class) 27 | public ResponseEntity externalServiceException(ExternalServiceException e){ 28 | return ResponseEntity.status(e.getErrorCode()) 29 | .body(new ErrorResponse(e.getMessage(),e.getErrorCode())); 30 | } 31 | 32 | @ExceptionHandler(AccessDeniedException.class) 33 | public ResponseEntity accessDeniedExceptionHandler(AccessDeniedException e){ 34 | return ResponseEntity.status(HttpStatus.FORBIDDEN) 35 | .body(new ErrorResponse(e.getMessage(),HttpStatus.FORBIDDEN)); 36 | } 37 | 38 | @ExceptionHandler(Exception.class) 39 | public ResponseEntity genericExceptionHandler(Exception e){ 40 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) 41 | .body(new ErrorResponse(e.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.config; 2 | 3 | import com.nasr.authorizationserver.filter.AccessTokenAuthenticationFilter; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.authentication.AuthenticationProvider; 7 | import org.springframework.security.config.Customizer; 8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 12 | import org.springframework.security.crypto.password.PasswordEncoder; 13 | import org.springframework.security.web.SecurityFilterChain; 14 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 15 | 16 | @EnableWebSecurity 17 | public class SecurityConfig { 18 | 19 | @Autowired 20 | private AccessTokenAuthenticationFilter filter; 21 | 22 | @Bean 23 | public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception { 24 | 25 | httpSecurity.authorizeHttpRequests() 26 | .mvcMatchers("/login") 27 | .permitAll() 28 | .anyRequest() 29 | .authenticated() 30 | .and() 31 | .addFilterAfter(filter, UsernamePasswordAuthenticationFilter.class) 32 | .formLogin(Customizer.withDefaults()); 33 | 34 | return httpSecurity.build(); 35 | 36 | } 37 | 38 | @Bean 39 | public PasswordEncoder passwordEncoder() { 40 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 41 | } 42 | 43 | @Autowired 44 | public void bindAuthenticationProvider(AuthenticationManagerBuilder auth, AuthenticationProvider authenticationProvider) { 45 | auth.authenticationProvider(authenticationProvider); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/model/request/JobDescriptorRequest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.model.request; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.nasr.orderhandlerservice.job.OrderPlacedHandlerJob; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import org.quartz.JobDataMap; 12 | import org.quartz.JobDetail; 13 | import org.quartz.Trigger; 14 | 15 | import javax.validation.constraints.Min; 16 | import javax.validation.constraints.NotNull; 17 | import java.util.*; 18 | import java.util.stream.Collectors; 19 | 20 | import static org.quartz.JobBuilder.newJob; 21 | 22 | @Data 23 | @NoArgsConstructor 24 | @AllArgsConstructor 25 | public class JobDescriptorRequest { 26 | 27 | @NotNull 28 | private String name; 29 | 30 | @JsonProperty(value = "triggers") 31 | private List triggerDescriptors = new ArrayList<>(); 32 | 33 | @JsonIgnore 34 | private String group; 35 | 36 | @Min(value = 1) 37 | private int orderId; 38 | 39 | @NotNull 40 | private Map productInfo; 41 | 42 | private Map getData() throws JsonProcessingException { 43 | ObjectMapper mapper = new ObjectMapper(); 44 | Map data =new HashMap<>(); 45 | data.put("orderId",orderId); 46 | data.put("productInfo",mapper.writeValueAsString(productInfo)); 47 | 48 | return data; 49 | } 50 | 51 | 52 | public JobDetail buildJobDetail() throws JsonProcessingException { 53 | JobDataMap jobDataMap = new JobDataMap(getData()); 54 | 55 | return newJob(OrderPlacedHandlerJob.class) 56 | .withIdentity(name, group) 57 | .usingJobData(jobDataMap) 58 | .build(); 59 | } 60 | public Set buildTriggers(){ 61 | return this.triggerDescriptors.stream().map(TriggerDescriptorRequest::buildTrigger) 62 | .collect(Collectors.toSet()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /order-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | spring: 5 | application: 6 | name: ORDER-SERVICE 7 | r2dbc: 8 | url: r2dbc:postgresql://${DB_HOST:localhost:5432}/microservice-ecommerce?schema=order_schema 9 | username: postgres 10 | password: MohammadN@sr13804804 11 | security: 12 | oauth2: 13 | resourceserver: 14 | jwt: 15 | issuer-uri: ${AUTHORIZATION_SERVER_URI:http://auth-server:9000} 16 | client: 17 | registration: 18 | client-internal: 19 | provider: spring 20 | client-id: ecommerce-client 21 | client-secret: ecommerce-secret 22 | authorization-grant-type: client_credentials 23 | scope: internal,read,write 24 | 25 | provider: 26 | spring: 27 | issuer-uri: ${AUTHORIZATION_SERVER_URI:http://auth-server:9000} 28 | 29 | 30 | config: 31 | import: optional:configserver:${CONFIG_SERVER_URI:http://localhost:8888} 32 | 33 | management: 34 | health: 35 | circuitbreakers: 36 | enabled: true 37 | endpoint: 38 | health: 39 | show-details: always 40 | 41 | resilience4j: 42 | circuitbreaker: 43 | configs: 44 | default: 45 | slidingWindowSize: 10 46 | slidingWindowType: COUNT_BASED 47 | permittedNumberOfCallsInHalfOpenState: 6 48 | failureRateThreshold: 50 49 | waitDurationInOpenState: 10s 50 | registerHealthIndicator: true 51 | automaticTransitionFromOpenToHalfOpenEnabled: true 52 | ignoreExceptions: 53 | - com.nasr.orderservice.exception.ExternalServiceException 54 | 55 | instances: 56 | productService: 57 | baseConfig: default 58 | orderHandlerService: 59 | baseConfig: default 60 | 61 | timelimiter: 62 | configs: 63 | default: 64 | timeout-duration: 3s 65 | cancelRunningFuture: true 66 | instances: 67 | productService: 68 | baseConfig: default 69 | orderHandlerService: 70 | baseConfig: default 71 | retry: 72 | configs: 73 | default: 74 | maxAttempts: 3 75 | waitDuration: 500ms 76 | enableExponentialBackoff: true 77 | exponentialBackoffMultiplier: 2 78 | instances: 79 | productService: 80 | baseConfig: default 81 | 82 | logging: 83 | level: 84 | io.r2dbc.postgresql.QUERY: DEBUG 85 | io.r2dbc.postgresql.PARAM: DEBUG 86 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/init/DataInitializer.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.init; 2 | 3 | import com.nasr.authorizationserver.domain.Role; 4 | import com.nasr.authorizationserver.domain.User; 5 | import com.nasr.authorizationserver.service.RoleService; 6 | import com.nasr.authorizationserver.service.UserService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.annotation.PostConstruct; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | @Component 16 | public class DataInitializer { 17 | 18 | @Autowired 19 | private UserService userService; 20 | 21 | @Autowired 22 | private RoleService roleService; 23 | 24 | @Autowired 25 | private PasswordEncoder passwordEncoder; 26 | 27 | @PostConstruct 28 | public void init() { 29 | 30 | if (!roleService.isExists()) 31 | createDefaultRole(); 32 | 33 | if (!userService.isExists()) 34 | createDefaultUser(); 35 | 36 | } 37 | 38 | private void createDefaultUser() { 39 | 40 | List users = new ArrayList<>(); 41 | 42 | RoleConsumer consumer = (customRole, user) -> { 43 | user.setRole(customRole); 44 | users.add(user); 45 | }; 46 | 47 | consumer.consume( 48 | roleService.getRoleByName("ADMIN"), 49 | new User("mohammad", "nasr", "nasrmohammad4804@gmail.com", passwordEncoder.encode("1234") 50 | ) 51 | ); 52 | consumer.consume( 53 | roleService.getRoleByName("USER"), 54 | new User("javad", "zare", "javad@gmail.com", passwordEncoder.encode("4567") 55 | ) 56 | ); 57 | 58 | consumer.consume( 59 | roleService.getRoleByName("SUPER_ADMIN"), 60 | new User("aida", "fallah", "aida6529@gmail.com", passwordEncoder.encode("5629") 61 | ) 62 | ); 63 | 64 | userService.saveAll(users); 65 | } 66 | 67 | private void createDefaultRole() { 68 | List roles = List.of( 69 | new Role("ADMIN"), 70 | new Role("USER"), 71 | new Role("SUPER_ADMIN") 72 | ); 73 | roleService.saveAll(roles); 74 | } 75 | } 76 | 77 | @FunctionalInterface 78 | interface RoleConsumer { 79 | void consume(Role role, User user); 80 | } -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/base/service/impl/BaseServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.base.service.impl; 2 | 3 | import com.nasr.productservice.base.domain.BaseEntity; 4 | import com.nasr.productservice.base.mapper.BaseMapper; 5 | import com.nasr.productservice.base.service.BaseService; 6 | import com.nasr.productservice.domain.Product; 7 | import com.nasr.productservice.exception.EntityNotFoundException; 8 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | import java.io.Serializable; 13 | import java.util.List; 14 | 15 | public abstract class BaseServiceImpl,D,ID extends Serializable,R extends ReactiveCrudRepository,V> implements BaseService { 16 | 17 | protected final R repository; 18 | protected final BaseMapper mapper; 19 | 20 | 21 | public abstract Class getEntityClass(); 22 | 23 | public BaseServiceImpl(R repository, BaseMapper mapper) { 24 | this.repository = repository; 25 | this.mapper = mapper; 26 | } 27 | 28 | @Override 29 | public Mono saveOrUpdate(E entity) { 30 | return repository.save(entity) 31 | .map(mapper::convertEntityToDto) 32 | .log(); 33 | } 34 | 35 | @Override 36 | public Mono getById(ID id) { 37 | return repository.findById(id) 38 | .switchIfEmpty(Mono.error(() -> new EntityNotFoundException("dont find any "+getEntityClass().getSimpleName()+" with id : "+id))) 39 | .map(mapper::convertEntityToDto) 40 | .log(); 41 | } 42 | 43 | @Override 44 | public Flux getAll() { 45 | return repository.findAll() 46 | .collectList() 47 | .flatMapMany(entities -> { 48 | List dtoList = mapper.convertEntitiesToDtoList(entities); 49 | return Flux.fromIterable(dtoList); 50 | }); 51 | } 52 | 53 | @Override 54 | public Mono deleteById(ID id) { 55 | return repository.deleteById(id) 56 | .log(); 57 | } 58 | 59 | @Override 60 | public Flux saveAll(Iterable entities) { 61 | return repository.saveAll(entities) 62 | .collectList() 63 | .flatMapMany(entityList -> { 64 | List dtoList = mapper.convertEntitiesToDtoList(entityList); 65 | return Flux.fromIterable(dtoList); 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/base/service/impl/BaseServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.base.service.impl; 2 | 3 | import com.nasr.orderservice.base.domain.BaseEntity; 4 | import com.nasr.orderservice.base.mapper.BaseMapper; 5 | import com.nasr.orderservice.base.service.BaseService; 6 | import com.nasr.orderservice.exception.EntityNotFoundException; 7 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | 11 | import java.io.Serializable; 12 | import java.util.List; 13 | 14 | public abstract class BaseServiceImpl,ID extends Serializable,R extends ReactiveCrudRepository,D,V> implements BaseService { 15 | 16 | protected final R repository; 17 | protected final BaseMapper mapper; 18 | 19 | 20 | public abstract Class getEntityClass(); 21 | 22 | public BaseServiceImpl(R repository, BaseMapper mapper) { 23 | this.repository = repository; 24 | this.mapper = mapper; 25 | } 26 | 27 | @Override 28 | public Mono saveOrUpdate(V view) { 29 | return repository.save(mapper.convertViewToEntity(view)) 30 | .map(mapper::convertEntityToDto) 31 | .log(); 32 | } 33 | 34 | @Override 35 | public Mono getById(ID id) { 36 | return repository.findById(id) 37 | .switchIfEmpty(Mono.error(() -> new EntityNotFoundException("dont find any "+getEntityClass().getSimpleName()+" with id : "+id))) 38 | .map(mapper::convertEntityToDto) 39 | .log(); 40 | } 41 | 42 | @Override 43 | public Flux getAll() { 44 | return repository.findAll() 45 | .collectList() 46 | .flatMapMany(entities -> { 47 | List dtoList = mapper.convertEntitiesToDtoList(entities); 48 | return Flux.fromIterable(dtoList); 49 | }); 50 | } 51 | 52 | @Override 53 | public Mono deleteById(ID id) { 54 | return repository.deleteById(id) 55 | .log(); 56 | } 57 | 58 | @Override 59 | public Flux saveAll(Iterable views) { 60 | 61 | List list = mapper.convertViewsToEntities(views); 62 | return repository.saveAll(list) 63 | .collectList() 64 | .flatMapMany(entityList -> { 65 | List dtoList = mapper.convertEntitiesToDtoList(entityList); 66 | return Flux.fromIterable(dtoList); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/base/service/impl/BaseServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.base.service.impl; 2 | 3 | import com.nasr.paymentservice.base.domain.BaseEntity; 4 | import com.nasr.paymentservice.base.mapper.BaseMapper; 5 | import com.nasr.paymentservice.base.service.BaseService; 6 | import com.nasr.paymentservice.exception.EntityNotFoundException; 7 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | 11 | import java.io.Serializable; 12 | import java.util.List; 13 | 14 | public abstract class BaseServiceImpl,ID extends Serializable,R extends ReactiveCrudRepository,D,V> implements BaseService { 15 | 16 | protected final R repository; 17 | protected final BaseMapper mapper; 18 | 19 | 20 | public abstract Class getEntityClass(); 21 | 22 | public BaseServiceImpl(R repository, BaseMapper mapper) { 23 | this.repository = repository; 24 | this.mapper = mapper; 25 | } 26 | 27 | @Override 28 | public Mono saveOrUpdate(V view) { 29 | return repository.save(mapper.convertViewToEntity(view)) 30 | .map(mapper::convertEntityToDto) 31 | .log(); 32 | } 33 | 34 | @Override 35 | public Mono getById(ID id) { 36 | return repository.findById(id) 37 | .switchIfEmpty(Mono.error(() -> new EntityNotFoundException("dont find any "+getEntityClass().getSimpleName()+" with id : "+id))) 38 | .map(mapper::convertEntityToDto) 39 | .log(); 40 | } 41 | 42 | @Override 43 | public Flux getAll() { 44 | return repository.findAll() 45 | .collectList() 46 | .flatMapMany(entities -> { 47 | List dtoList = mapper.convertEntitiesToDtoList(entities); 48 | return Flux.fromIterable(dtoList); 49 | }); 50 | } 51 | 52 | @Override 53 | public Mono deleteById(ID id) { 54 | return repository.deleteById(id) 55 | .log(); 56 | } 57 | 58 | @Override 59 | public Flux saveAll(Iterable views) { 60 | 61 | List list = mapper.convertViewsToEntities(views); 62 | return repository.saveAll(list) 63 | .collectList() 64 | .flatMapMany(entityList -> { 65 | List dtoList = mapper.convertEntitiesToDtoList(entityList); 66 | return Flux.fromIterable(dtoList); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /product-service/src/test/java/com/nasr/productservice/repository/ProductRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.repository; 2 | 3 | import com.nasr.productservice.domain.Product; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest; 8 | import reactor.core.publisher.Mono; 9 | import reactor.test.StepVerifier; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | @DataR2dbcTest 15 | public class ProductRepositoryTest { 16 | 17 | @Autowired 18 | private ProductRepository underTest; 19 | 20 | @Test 21 | @DisplayName("this unit test for check product exists in stock by name ") 22 | void itShouldIsExistsByName() { 23 | // given 24 | Product product = new Product("hp", 23L, 29_000_000D); 25 | 26 | // when 27 | Mono productMono = underTest.save(product); 28 | Mono monoResult = underTest.isExistsByName("hp"); 29 | 30 | //then 31 | StepVerifier.create(productMono) 32 | .expectNextCount(1) 33 | .verifyComplete(); 34 | 35 | StepVerifier.create(monoResult) 36 | .expectNext(Boolean.TRUE) 37 | .verifyComplete(); 38 | } 39 | 40 | @Test 41 | @DisplayName("this unit test for check dont have any product with product name asus") 42 | public void itShouldNotExistsByName() { 43 | 44 | Product product = new Product("hp", 23L, 29_000_000D); 45 | 46 | Mono productMono = underTest.save(product); 47 | Mono monoResult = underTest.isExistsByName("asus"); 48 | 49 | StepVerifier.create(productMono) 50 | .expectNextCount(1L) 51 | .verifyComplete(); 52 | 53 | StepVerifier.create(monoResult) 54 | .expectNext(Boolean.FALSE) 55 | .verifyComplete(); 56 | } 57 | 58 | @Test 59 | @DisplayName("this unit test for check product was saved we call successfully fetched") 60 | void itShouldFindAllById() { 61 | // given 62 | Product hp = new Product("hp", 23L, 29_000_000D); 63 | Product macBook = new Product("mac book", 37L, 64_000_000D); 64 | // when 65 | underTest.saveAll(Arrays.asList(hp,macBook)).subscribe(); 66 | //then 67 | 68 | List ids = Arrays.asList(hp.getId(),macBook.getId()); 69 | StepVerifier.create(underTest.findAllById(ids)) 70 | .expectNextCount(2L) 71 | .verifyComplete(); 72 | } 73 | } -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/controller/GatewayLoginController.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.controller; 2 | 3 | import com.nasr.apigateway.dto.response.TokenInfoResponseDto; 4 | import com.nasr.apigateway.dto.response.UserInfoResponseDto; 5 | import com.nasr.apigateway.external.service.ExternalUserService; 6 | import lombok.extern.log4j.Log4j2; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 9 | import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; 10 | import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; 11 | import org.springframework.security.oauth2.core.oidc.user.OidcUser; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | import reactor.core.publisher.Mono; 15 | 16 | import java.util.Objects; 17 | 18 | import static com.nasr.apigateway.util.Oauth2TokenUtil.extractAuthority; 19 | import static com.nasr.apigateway.util.Oauth2TokenUtil.getAuth; 20 | 21 | @RestController 22 | @Log4j2 23 | public class GatewayLoginController { 24 | 25 | @Autowired 26 | private ExternalUserService externalUserService; 27 | 28 | @GetMapping("/authenticate") 29 | public Mono login(@RegisteredOAuth2AuthorizedClient("ecommerce-gateway") OAuth2AuthorizedClient client, 30 | @AuthenticationPrincipal OidcUser user ) { 31 | 32 | String jwtToken = client.getAccessToken().getTokenValue(); 33 | log.info("access token received from authorization server with value : {}", jwtToken); 34 | 35 | Mono userResponse = externalUserService.getUser(getAuth(jwtToken)); 36 | 37 | TokenInfoResponseDto tokenInfo = TokenInfoResponseDto.builder() 38 | .accessToken(client.getAccessToken().getTokenValue()) 39 | .refreshToken(client.getRefreshToken().getTokenValue()) 40 | .accessTokenExpireAt(client.getAccessToken().getExpiresAt()) 41 | .authorities(extractAuthority(user)) 42 | .build(); 43 | 44 | return Mono.just(tokenInfo) 45 | .zipWith(userResponse) 46 | .map(tuple -> { 47 | TokenInfoResponseDto tokenInfoResponseDto = tuple.getT1(); 48 | UserInfoResponseDto userInfoResponseDto = tuple.getT2(); 49 | tokenInfoResponseDto.setUserInfo(userInfoResponseDto); 50 | return tokenInfoResponseDto; 51 | }) 52 | .log(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /service-registry/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.4 9 | 10 | 11 | com.nasr 12 | service-registry 13 | 0.0.1-SNAPSHOT 14 | service-registry 15 | service-registry 16 | 17 | 17 18 | 2021.0.4 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-netflix-eureka-server 24 | 25 | 26 | 27 | org.projectlombok 28 | lombok 29 | true 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-test 34 | test 35 | 36 | 37 | 38 | 39 | 40 | org.springframework.cloud 41 | spring-cloud-dependencies 42 | ${spring-cloud.version} 43 | pom 44 | import 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-maven-plugin 54 | 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/nasr/authorizationserver/filter/AccessTokenAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.nasr.authorizationserver.filter; 2 | 3 | import lombok.extern.log4j.Log4j2; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Qualifier; 6 | import org.springframework.security.authentication.AuthenticationManager; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.filter.OncePerRequestFilter; 12 | 13 | import javax.servlet.FilterChain; 14 | import javax.servlet.ServletException; 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.io.IOException; 18 | 19 | import static com.nasr.authorizationserver.constant.ConstantField.BEARER_PREFIX; 20 | 21 | @Component 22 | @Log4j2 23 | public class AccessTokenAuthenticationFilter extends OncePerRequestFilter { 24 | 25 | @Autowired 26 | @Qualifier("bearerTokenAuthenticationManager") 27 | private AuthenticationManager authenticationManager; 28 | 29 | @Override 30 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 31 | 32 | if (request.getServletPath().contains("/users")) { 33 | String auth = request.getHeader("Authorization"); 34 | 35 | 36 | if (auth!=null && auth.startsWith(BEARER_PREFIX)){ 37 | String token = auth.replace(BEARER_PREFIX, ""); 38 | BearerTokenAuthenticationToken bearerToken = new BearerTokenAuthenticationToken(token); 39 | 40 | try { 41 | Authentication authenticate = authenticationManager.authenticate(bearerToken); 42 | SecurityContextHolder.getContext().setAuthentication(authenticate); 43 | 44 | filterChain.doFilter(request, response); 45 | } catch (Exception e) { 46 | 47 | log.error("error on decode token"); 48 | response.getOutputStream().write("invalid token".getBytes()); 49 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 50 | } 51 | } 52 | else { 53 | response.getOutputStream().write("invalid token !!!".getBytes()); 54 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 55 | } 56 | 57 | } else filterChain.doFilter(request, response); 58 | 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /config-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.4 9 | 10 | 11 | com.nasr 12 | config-server 13 | 0.0.1-SNAPSHOT 14 | config-server 15 | config-server 16 | 17 | 17 18 | 2021.0.4 19 | 20 | 21 | 22 | 23 | org.springframework.cloud 24 | spring-cloud-config-server 25 | 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | true 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-starter-netflix-eureka-client 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.springframework.cloud 48 | spring-cloud-dependencies 49 | ${spring-cloud.version} 50 | pom 51 | import 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-maven-plugin 61 | 62 | 63 | 64 | org.projectlombok 65 | lombok 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/nasr/apigateway/external/service/impl/ExternalUserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.nasr.apigateway.external.service.impl; 2 | 3 | import com.nasr.apigateway.dto.response.UserInfoResponseDto; 4 | import com.nasr.apigateway.external.response.UserResponseDto; 5 | import com.nasr.apigateway.external.service.ExternalUserService; 6 | import io.github.resilience4j.circuitbreaker.CallNotPermittedException; 7 | import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; 8 | import io.github.resilience4j.retry.annotation.Retry; 9 | import lombok.extern.log4j.Log4j2; 10 | import org.springframework.beans.BeanUtils; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.web.reactive.function.client.WebClient; 14 | import org.springframework.web.reactive.function.client.WebClientRequestException; 15 | import org.springframework.web.reactive.function.client.WebClientResponseException; 16 | import reactor.core.publisher.Mono; 17 | 18 | import static org.springframework.http.HttpHeaders.AUTHORIZATION; 19 | 20 | @Service 21 | @Log4j2 22 | public class ExternalUserServiceImpl implements ExternalUserService { 23 | 24 | @Autowired 25 | private WebClient.Builder webClientBuilder; 26 | 27 | @Override 28 | @CircuitBreaker(name = "authorizationServer",fallbackMethod = "authorizationServerFallback") 29 | @Retry(name = "authorizationServer") 30 | public Mono getUser(String auth) { 31 | 32 | return webClientBuilder.build().get() 33 | .uri(uriBuilder -> uriBuilder.path("/users") 34 | .host("AUTHORIZATION-SERVER") 35 | .build() 36 | ) 37 | .header(AUTHORIZATION, auth) 38 | .retrieve() 39 | .bodyToMono(UserResponseDto.class) 40 | .map(userResponseDto -> { 41 | UserInfoResponseDto dto = new UserInfoResponseDto(); 42 | BeanUtils.copyProperties(userResponseDto,dto); 43 | dto.setSsoId(userResponseDto.getId()); 44 | return dto; 45 | }) 46 | .log(); 47 | } 48 | private Mono authorizationServerFallback(String auth, CallNotPermittedException e){ 49 | return authorizationServerFallback(auth, (WebClientResponseException) null); 50 | } 51 | private Mono authorizationServerFallback(String auth, WebClientRequestException e){ 52 | return authorizationServerFallback(auth, (WebClientResponseException) null); 53 | } 54 | private Mono authorizationServerFallback(String auth, WebClientResponseException e){ 55 | log.error("dont able connect to authorization server for get user info"); 56 | return Mono.just(new UserInfoResponseDto()); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/config/WebclientConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.config; 2 | 3 | import com.nasr.paymentservice.intercept.WebclientInterceptor; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.security.oauth2.client.*; 10 | import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; 11 | import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction; 12 | import org.springframework.web.reactive.function.client.WebClient; 13 | import reactor.core.publisher.Mono; 14 | 15 | @Configuration 16 | @Profile("default") 17 | public class WebclientConfig { 18 | 19 | @Autowired 20 | private ReactiveClientRegistrationRepository clientRegistrationRepository; 21 | 22 | @Autowired 23 | private ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService; 24 | 25 | @Bean 26 | @LoadBalanced 27 | public WebClient.Builder webClientBuilder(){ 28 | 29 | Mono authorize = authorizedClientManager(clientRegistrationRepository,oAuth2AuthorizedClientService) 30 | .authorize(OAuth2AuthorizeRequest.withClientRegistrationId("client-internal") 31 | .principal("ecommerce-client").build()); 32 | 33 | WebClient.Builder builder = WebClient.builder(); 34 | authorize.doOnNext(auth -> builder.filter(WebclientInterceptor.interceptor(auth.getAccessToken().getTokenValue()))) 35 | .subscribe(); 36 | 37 | return builder; 38 | } 39 | 40 | @Bean 41 | WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) { 42 | 43 | ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = 44 | new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); 45 | 46 | 47 | return WebClient.builder() 48 | .filter(oauth2Client) 49 | .build(); 50 | 51 | } 52 | @Bean 53 | public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( 54 | final ReactiveClientRegistrationRepository clientRegistrationRepository, 55 | final ReactiveOAuth2AuthorizedClientService authorizedClientService) { 56 | final ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = 57 | ReactiveOAuth2AuthorizedClientProviderBuilder 58 | .builder() 59 | .clientCredentials() 60 | .build(); 61 | final AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = 62 | new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService); 63 | authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); 64 | return authorizedClientManager; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /order-handler-service/src/main/java/com/nasr/orderhandlerservice/config/WebclientConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderhandlerservice.config; 2 | 3 | import com.nasr.orderhandlerservice.intercept.WebclientInterceptor; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.oauth2.client.*; 9 | import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; 10 | import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction; 11 | import org.springframework.web.reactive.function.client.WebClient; 12 | import reactor.core.publisher.Mono; 13 | 14 | @Configuration 15 | public class WebclientConfig { 16 | 17 | @Autowired 18 | private ReactiveClientRegistrationRepository clientRegistrationRepository; 19 | 20 | @Autowired 21 | private ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService; 22 | 23 | 24 | @Bean 25 | @LoadBalanced 26 | public WebClient.Builder webclient() { 27 | WebClient.Builder builder = WebClient.builder(); 28 | 29 | ReactiveOAuth2AuthorizedClientManager authorizedClientManager = 30 | authorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService); 31 | 32 | Mono authorize = authorizedClientManager 33 | .authorize(OAuth2AuthorizeRequest.withClientRegistrationId("client-internal") 34 | .principal("ecommerce-client").build()); 35 | 36 | authorize.doOnNext(auth -> builder.filter(WebclientInterceptor.interceptor(auth.getAccessToken().getTokenValue()))) 37 | .subscribe(); 38 | 39 | return builder; 40 | } 41 | 42 | @Bean 43 | WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) { 44 | 45 | ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = 46 | new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); 47 | 48 | 49 | return WebClient.builder() 50 | .filter(oauth2Client) 51 | .build(); 52 | 53 | } 54 | 55 | @Bean 56 | public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( 57 | final ReactiveClientRegistrationRepository clientRegistrationRepository, 58 | final ReactiveOAuth2AuthorizedClientService authorizedClientService) { 59 | final ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = 60 | ReactiveOAuth2AuthorizedClientProviderBuilder 61 | .builder() 62 | .clientCredentials() 63 | .build(); 64 | final AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = 65 | new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService); 66 | authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); 67 | return authorizedClientManager; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/config/WebclientConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.config; 2 | 3 | import com.nasr.orderservice.intercept.WebclientInterceptor; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.security.oauth2.client.*; 10 | import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; 11 | import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction; 12 | import org.springframework.web.reactive.function.client.WebClient; 13 | import reactor.core.publisher.Mono; 14 | 15 | @Configuration 16 | @Profile("default") 17 | public class WebclientConfig { 18 | 19 | @Autowired 20 | private ReactiveClientRegistrationRepository clientRegistrationRepository; 21 | 22 | @Autowired 23 | private ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService; 24 | 25 | 26 | @Bean 27 | @LoadBalanced 28 | // we need to use interceptor for all request with webclient for service to service communication need to get token with internal scope 29 | public WebClient.Builder webClientBuilder() { 30 | 31 | ReactiveOAuth2AuthorizedClientManager authorizedClientManager = 32 | authorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService); 33 | 34 | Mono authorize = authorizedClientManager 35 | .authorize(OAuth2AuthorizeRequest.withClientRegistrationId("client-internal") 36 | .principal("ecommerce-client").build()); 37 | 38 | WebClient.Builder builder = WebClient.builder(); 39 | authorize.doOnNext(auth -> builder.filter(WebclientInterceptor.interceptor(auth.getAccessToken().getTokenValue()))) 40 | .subscribe(); 41 | 42 | return builder; 43 | } 44 | 45 | @Bean 46 | WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) { 47 | 48 | ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = 49 | new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); 50 | 51 | 52 | return WebClient.builder() 53 | .filter(oauth2Client) 54 | .build(); 55 | 56 | } 57 | @Bean 58 | public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( 59 | final ReactiveClientRegistrationRepository clientRegistrationRepository, 60 | final ReactiveOAuth2AuthorizedClientService authorizedClientService) { 61 | final ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = 62 | ReactiveOAuth2AuthorizedClientProviderBuilder 63 | .builder() 64 | .clientCredentials() 65 | .build(); 66 | final AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = 67 | new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService); 68 | authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); 69 | return authorizedClientManager; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.controller; 2 | 3 | import com.nasr.orderservice.dto.request.OrderRequest; 4 | import com.nasr.orderservice.dto.response.OrderResponse; 5 | import com.nasr.orderservice.exception.ExternalServiceException; 6 | import com.nasr.orderservice.external.response.ProductResponse; 7 | import com.nasr.orderservice.service.OrderService; 8 | import com.nasr.orderservice.util.Oauth2TokenUtil; 9 | import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; 10 | import io.github.resilience4j.retry.annotation.Retry; 11 | import io.github.resilience4j.timelimiter.annotation.TimeLimiter; 12 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 13 | import lombok.extern.log4j.Log4j2; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.http.MediaType; 17 | import org.springframework.http.ResponseEntity; 18 | import org.springframework.http.server.reactive.ServerHttpRequest; 19 | import org.springframework.security.access.prepost.PreAuthorize; 20 | import org.springframework.web.bind.annotation.*; 21 | import reactor.core.publisher.Flux; 22 | import reactor.core.publisher.Mono; 23 | 24 | import javax.validation.Valid; 25 | 26 | import static com.nasr.orderservice.util.Oauth2TokenUtil.getAuth; 27 | 28 | @Log4j2 29 | @RestController 30 | @RequestMapping("/api/v1/order") 31 | @SecurityRequirement(name = "Bearer Authentication") 32 | public class OrderController { 33 | 34 | @Autowired 35 | private OrderService orderService; 36 | 37 | @PostMapping("/placeOrder") 38 | @PreAuthorize("hasRole('USER')") 39 | public Mono> placeOrder(@RequestBody @Valid OrderRequest orderRequest) { 40 | 41 | return orderService.saveOrUpdate(orderRequest) 42 | .map(ResponseEntity::ok); 43 | } 44 | 45 | @DeleteMapping("/cancelOrder/{orderId}") 46 | @PreAuthorize("hasAuthority('SCOPE_internal')") 47 | public Mono> cancelOrder(@PathVariable Long orderId) { 48 | return orderService.deleteById(orderId) 49 | .map(ResponseEntity::ok); 50 | } 51 | 52 | @GetMapping(value = "/{id}/products",produces = MediaType.TEXT_EVENT_STREAM_VALUE) 53 | @ResponseStatus(HttpStatus.OK) 54 | @CircuitBreaker(name = "productService",fallbackMethod = "productServiceFallback") 55 | @TimeLimiter(name = "productService") 56 | @Retry(name = "productService") 57 | public Flux getOrderPlaceProducts(@PathVariable("id") Long orderId){ 58 | return orderService.getOrderPlacedProducts(orderId); 59 | } 60 | 61 | // this is fallback method for product service 62 | public Flux productServiceFallback(Long orderId ,Exception e){ 63 | 64 | return Flux.error(() -> new ExternalServiceException( 65 | "product service unAvailable !",HttpStatus.SERVICE_UNAVAILABLE 66 | )); 67 | } 68 | 69 | @PutMapping("/completeOrderStatus/{id}") 70 | @PreAuthorize("hasAuthority('SCOPE_internal')") 71 | public Mono completeOrderPlaceStatus(@PathVariable("id") Long orderId) { 72 | return orderService.completeOrderPlacedStatus(orderId); 73 | } 74 | 75 | @GetMapping("/{id}") 76 | public Mono getOrderPlaced(@PathVariable Long id) { 77 | return orderService.getById(id); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.controller; 2 | 3 | import com.nasr.productservice.domain.Product; 4 | import com.nasr.productservice.dto.request.DecreaseProductQuantityRequest; 5 | import com.nasr.productservice.dto.request.ProductRequest; 6 | import com.nasr.productservice.dto.request.RevertProductRequest; 7 | import com.nasr.productservice.dto.response.ProductResponse; 8 | import com.nasr.productservice.mapper.ProductMapper; 9 | import com.nasr.productservice.service.ProductService; 10 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.security.access.prepost.PreAuthorize; 16 | import org.springframework.security.core.Authentication; 17 | import org.springframework.web.bind.annotation.*; 18 | import reactor.core.publisher.Flux; 19 | import reactor.core.publisher.Mono; 20 | 21 | import javax.validation.Valid; 22 | import java.util.List; 23 | 24 | @RestController 25 | @RequestMapping("/api/v1/product") 26 | @SecurityRequirement(name = "Bearer Authentication") 27 | public class ProductController { 28 | 29 | @Autowired 30 | private ProductService productService; 31 | 32 | @Autowired 33 | private ProductMapper productMapper; 34 | 35 | /** 36 | * this endpoint access for user have ADMIN or SUPER_ADMIN role because ordinary customer dont allowed to define product in service 37 | * @param dto as product 38 | * @return product info 39 | */ 40 | @PostMapping 41 | @PreAuthorize(value = "hasAnyRole('ADMIN','SUPER_ADMIN')") 42 | public Mono> addProduct(@RequestBody @Valid ProductRequest dto) { 43 | Product product = productMapper.convertViewToEntity(dto); 44 | return productService.saveOrUpdate(product) 45 | .map(p -> ResponseEntity.status(HttpStatus.CREATED).body(p)); 46 | } 47 | 48 | @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) 49 | @ResponseStatus(HttpStatus.OK) 50 | public Flux getProducts() { 51 | return productService.getAll(); 52 | } 53 | 54 | @GetMapping("/{id}") 55 | public Mono> getProduct(@PathVariable Long id) { 56 | return productService.getById(id) 57 | .map(ResponseEntity::ok) 58 | .log(); 59 | } 60 | @GetMapping(path = "/all",produces = MediaType.TEXT_EVENT_STREAM_VALUE) 61 | @PreAuthorize("hasAuthority('SCOPE_internal') ") 62 | @ResponseStatus(HttpStatus.OK) 63 | public Flux getProductByIds(@RequestParam("id") List ids){ 64 | return productService.getProductByIds(ids); 65 | } 66 | 67 | @PutMapping(path = "/decreaseQuantity") 68 | @PreAuthorize("hasAuthority('SCOPE_internal') ") 69 | public Mono> decreaseProductQuantity(@RequestBody List< @Valid DecreaseProductQuantityRequest> dtos){ 70 | return productService.decreaseQuantity(dtos) 71 | .map(ResponseEntity::ok); 72 | } 73 | @PutMapping("/revertProduct") 74 | @PreAuthorize("hasAuthority('SCOPE_internal')") 75 | public Mono> revertProduct(@RequestBody List< @Valid RevertProductRequest> revertProductRequests){ 76 | return productService.revertProducts(revertProductRequests) 77 | .map(ResponseEntity::ok); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /product-service/src/main/java/com/nasr/productservice/config/ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.productservice.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.core.convert.converter.Converter; 5 | import org.springframework.http.HttpMethod; 6 | import org.springframework.security.authentication.AbstractAuthenticationToken; 7 | import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; 8 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 9 | import org.springframework.security.config.web.server.ServerHttpSecurity; 10 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 11 | import org.springframework.security.oauth2.jwt.Jwt; 12 | import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; 13 | import org.springframework.security.web.server.SecurityWebFilterChain; 14 | import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository; 15 | import reactor.core.publisher.Flux; 16 | import reactor.core.publisher.Mono; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | import static com.nasr.productservice.constant.ConstantField.ROLE_PREFIX; 23 | import static com.nasr.productservice.constant.ConstantField.SCOPE_PREFIX; 24 | 25 | @EnableWebFluxSecurity 26 | @EnableReactiveMethodSecurity 27 | public class ResourceServerConfig { 28 | 29 | @Bean 30 | public SecurityWebFilterChain filterChain(ServerHttpSecurity http){ 31 | http.csrf(ServerHttpSecurity.CsrfSpec::disable) 32 | .cors(ServerHttpSecurity.CorsSpec::disable) 33 | .authorizeExchange() 34 | .pathMatchers("/v3/api-docs/**","/webjars/**") 35 | .permitAll() 36 | .pathMatchers(HttpMethod.GET,"/**") 37 | .hasAuthority("SCOPE_read") 38 | .pathMatchers(HttpMethod.POST,"/**") 39 | .hasAuthority("SCOPE_write") 40 | .pathMatchers(HttpMethod.DELETE,"/**") 41 | .hasAuthority("SCOPE_write") 42 | .pathMatchers(HttpMethod.PATCH,"/**") 43 | .hasAuthority("SCOPE_write") 44 | .pathMatchers(HttpMethod.PUT,"/**") 45 | .hasAuthority("SCOPE_write") 46 | .anyExchange() 47 | .authenticated() 48 | .and() 49 | .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(converter()))); 50 | 51 | http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); 52 | return http.build(); 53 | } 54 | public Converter> converter(){ 55 | 56 | ReactiveJwtAuthenticationConverter converter = new ReactiveJwtAuthenticationConverter(); 57 | 58 | converter.setJwtGrantedAuthoritiesConverter(jwt -> { 59 | 60 | List roles =(List) jwt.getClaims().get("roles"); 61 | List scopes =(List) jwt.getClaims().get("scope"); 62 | 63 | List authorities = new ArrayList<>(); 64 | for (String role : roles) 65 | authorities.add(ROLE_PREFIX.concat(role)); 66 | 67 | for (String scope : scopes) 68 | authorities.add(SCOPE_PREFIX.concat(scope)); 69 | 70 | return Flux.fromIterable(authorities.stream().map(SimpleGrantedAuthority::new) 71 | .collect(Collectors.toList())); 72 | }); 73 | return converter; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/nasr/paymentservice/config/ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.paymentservice.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.core.convert.converter.Converter; 5 | import org.springframework.http.HttpMethod; 6 | import org.springframework.security.authentication.AbstractAuthenticationToken; 7 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 8 | import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; 9 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 10 | import org.springframework.security.config.web.server.ServerHttpSecurity; 11 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 12 | import org.springframework.security.oauth2.jwt.Jwt; 13 | import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; 14 | import org.springframework.security.web.server.SecurityWebFilterChain; 15 | import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository; 16 | import reactor.core.publisher.Flux; 17 | import reactor.core.publisher.Mono; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | import static com.nasr.paymentservice.constant.ConstantField.ROLE_PREFIX; 24 | import static com.nasr.paymentservice.constant.ConstantField.SCOPE_PREFIX; 25 | 26 | @EnableWebFluxSecurity 27 | @EnableReactiveMethodSecurity 28 | public class ResourceServerConfig { 29 | 30 | @Bean 31 | public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { 32 | 33 | http.cors(ServerHttpSecurity.CorsSpec::disable) 34 | .csrf(ServerHttpSecurity.CsrfSpec::disable) 35 | .authorizeExchange() 36 | .pathMatchers("/v3/api-docs/**", "/webjars/**") 37 | .permitAll() 38 | .pathMatchers(HttpMethod.GET,"/**") 39 | .hasAuthority("SCOPE_read") 40 | .pathMatchers(HttpMethod.POST,"/**") 41 | .hasAuthority("SCOPE_write") 42 | .pathMatchers(HttpMethod.DELETE,"/**") 43 | .hasAuthority("SCOPE_write") 44 | .pathMatchers(HttpMethod.PATCH,"/**") 45 | .hasAuthority("SCOPE_write") 46 | .pathMatchers(HttpMethod.PUT,"/**") 47 | .hasAuthority("SCOPE_write") 48 | .anyExchange() 49 | .authenticated() 50 | .and() 51 | .oauth2ResourceServer(oauth2 -> oauth2.jwt().jwtAuthenticationConverter(converter())); 52 | 53 | http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); 54 | 55 | return http.build(); 56 | } 57 | 58 | public Converter> converter(){ 59 | 60 | ReactiveJwtAuthenticationConverter converter = new ReactiveJwtAuthenticationConverter(); 61 | 62 | converter.setJwtGrantedAuthoritiesConverter(jwt -> { 63 | 64 | List roles =(List) jwt.getClaims().get("roles"); 65 | List scopes =(List) jwt.getClaims().get("scope"); 66 | 67 | List authorities = new ArrayList<>(); 68 | for (String role : roles) 69 | authorities.add(ROLE_PREFIX.concat(role)); 70 | 71 | for (String scope : scopes) 72 | authorities.add(SCOPE_PREFIX.concat(scope)); 73 | 74 | return Flux.fromIterable(authorities.stream().map(SimpleGrantedAuthority::new) 75 | .collect(Collectors.toList())); 76 | }); 77 | return converter; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/nasr/orderservice/config/ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.core.convert.converter.Converter; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.security.authentication.AbstractAuthenticationToken; 8 | import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; 9 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 10 | import org.springframework.security.config.web.server.ServerHttpSecurity; 11 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 12 | import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; 13 | import org.springframework.security.oauth2.jwt.Jwt; 14 | import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; 15 | import org.springframework.security.web.server.SecurityWebFilterChain; 16 | import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository; 17 | import reactor.core.publisher.Flux; 18 | import reactor.core.publisher.Mono; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.stream.Collectors; 23 | 24 | import static com.nasr.orderservice.constant.ConstantField.ROLE_PREFIX; 25 | import static com.nasr.orderservice.constant.ConstantField.SCOPE_PREFIX; 26 | 27 | @EnableWebFluxSecurity 28 | @EnableReactiveMethodSecurity 29 | public class ResourceServerConfig { 30 | 31 | @Bean 32 | public SecurityWebFilterChain webFilterChain(ServerHttpSecurity http) { 33 | 34 | http.csrf(ServerHttpSecurity.CsrfSpec::disable) 35 | .cors(ServerHttpSecurity.CorsSpec::disable) 36 | .authorizeExchange() 37 | .pathMatchers("/v3/api-docs/**", "/webjars/**", "/actuator/**") 38 | .permitAll() 39 | .pathMatchers(HttpMethod.GET, "/**") 40 | .hasAuthority("SCOPE_read") 41 | .pathMatchers(HttpMethod.POST, "/**") 42 | .hasAuthority("SCOPE_write") 43 | .pathMatchers(HttpMethod.DELETE, "/**") 44 | .hasAuthority("SCOPE_write") 45 | .pathMatchers(HttpMethod.PATCH, "/**") 46 | .hasAuthority("SCOPE_write") 47 | .pathMatchers(HttpMethod.PUT, "/**") 48 | .hasAuthority("SCOPE_write") 49 | .anyExchange() 50 | .authenticated() 51 | .and() 52 | .oauth2ResourceServer(oauth2 -> oauth2.jwt().jwtAuthenticationConverter(converter())); 53 | 54 | http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); 55 | 56 | return http.build(); 57 | } 58 | 59 | public Converter> converter() { 60 | 61 | ReactiveJwtAuthenticationConverter converter = new ReactiveJwtAuthenticationConverter(); 62 | 63 | converter.setJwtGrantedAuthoritiesConverter(jwt -> { 64 | 65 | List roles = (List) jwt.getClaims().get("roles"); 66 | List scopes = (List) jwt.getClaims().get("scope"); 67 | 68 | List authorities = new ArrayList<>(); 69 | for (String role : roles) 70 | authorities.add(ROLE_PREFIX.concat(role)); 71 | 72 | for (String scope : scopes) 73 | authorities.add(SCOPE_PREFIX.concat(scope)); 74 | 75 | return Flux.fromIterable(authorities.stream().map(SimpleGrantedAuthority::new) 76 | .collect(Collectors.toList())); 77 | }); 78 | return converter; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/nasr/orderservice/service/impl/OrderDetailServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.nasr.orderservice.service.impl; 2 | 3 | import com.nasr.orderservice.domain.OrderDetail; 4 | import com.nasr.orderservice.dto.response.OrderDetailResponse; 5 | import com.nasr.orderservice.exception.OrderNotFoundException; 6 | import com.nasr.orderservice.mapper.OrderDetailMapper; 7 | import com.nasr.orderservice.repository.OrderDetailRepository; 8 | import com.nasr.orderservice.service.OrderDetailService; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.DisplayName; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | import org.mockito.Mock; 14 | import org.mockito.junit.jupiter.MockitoExtension; 15 | import reactor.core.publisher.Flux; 16 | import reactor.core.publisher.Mono; 17 | import reactor.test.StepVerifier; 18 | 19 | import java.util.stream.Stream; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | import static org.mockito.ArgumentMatchers.any; 23 | import static org.mockito.BDDMockito.given; 24 | import static org.mockito.Mockito.*; 25 | 26 | @ExtendWith(MockitoExtension.class) 27 | class OrderDetailServiceImplTest { 28 | 29 | @Mock 30 | private OrderDetailRepository repository; 31 | 32 | @Mock 33 | private OrderDetailMapper mapper; 34 | 35 | 36 | private OrderDetailService underTest ; 37 | 38 | @BeforeEach 39 | void setUp() { 40 | underTest = new OrderDetailServiceImpl(repository,mapper); 41 | } 42 | 43 | @Test 44 | @DisplayName("this unit test for get order detail by order id ") 45 | void itShouldGetOrderDetailsByOrderId() { 46 | // given 47 | Long orderId = 1L; 48 | // when 49 | given(repository.findAllByOrderId(orderId)).willReturn(getMockOrderDetails()); 50 | 51 | given(mapper.convertEntityToDto(any())) 52 | .willReturn(new OrderDetailResponse(2L,1L,4L)) 53 | .willReturn(new OrderDetailResponse(3L,1L,2L)) 54 | .willReturn(new OrderDetailResponse(8L,1L,6L)); 55 | 56 | Flux orderDetails = underTest.getOrderDetailsByOrderId(orderId); 57 | 58 | //then 59 | StepVerifier.create(orderDetails.collectList()) 60 | .assertNext(orderDetailsResponse -> assertThat(orderDetailsResponse).hasSize(3)) 61 | .verifyComplete(); 62 | 63 | 64 | }@Test 65 | @DisplayName("this unit test must throw not found exception because when get orderDetail by order need to exists order") 66 | void itShouldThrowNotFoundExceptionOnGetOrderDetailsByOrderIdWhenOrderIdNotValid() { 67 | 68 | // given 69 | Long orderId = 1L; 70 | 71 | // when 72 | given(repository.findAllByOrderId(orderId)) 73 | .willReturn(Flux.error(new OrderNotFoundException("dont find any products with orderId : "+orderId))); 74 | 75 | Flux orderDetails = underTest.getOrderDetailsByOrderId(orderId); 76 | 77 | //then 78 | StepVerifier.create(orderDetails.collectList()) 79 | .expectError(OrderNotFoundException.class) 80 | .verify(); 81 | 82 | verify(mapper,never()).convertEntityToDto(any()); 83 | 84 | } 85 | 86 | private Flux getMockOrderDetails() { 87 | return Flux.fromStream( 88 | Stream.of( 89 | new OrderDetail(1L, 2L, 4L), 90 | new OrderDetail(1L, 3L, 2L), 91 | new OrderDetail(1L, 8L, 6L) 92 | ) 93 | ); 94 | } 95 | 96 | @Test 97 | @DisplayName("this unit test for delete all order detail by order id from db") 98 | void itShouldDeleteOrderDetailByOrderId() { 99 | 100 | // given 101 | Long orderId = 1L; 102 | 103 | // when 104 | when(repository.deleteAllByOrderId(orderId)).thenReturn(Mono.empty()); 105 | 106 | Mono mono = underTest.deleteOrderDetailByOrderId(orderId); 107 | 108 | //then 109 | StepVerifier.create(mono) 110 | .verifyComplete(); 111 | } 112 | } -------------------------------------------------------------------------------- /authorization-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.4 9 | 10 | 11 | com.nasr 12 | authorization-server 13 | 0.0.1-SNAPSHOT 14 | authorization-server 15 | authorization-server 16 | 17 | 17 18 | 2021.0.4 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | mysql 29 | mysql-connector-java 30 | runtime 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-data-jpa 36 | 37 | 38 | 39 | 40 | org.projectlombok 41 | lombok 42 | true 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-test 47 | test 48 | 49 | 50 | 51 | 52 | org.springframework.security 53 | spring-security-oauth2-authorization-server 54 | 0.2.0 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-security 60 | 61 | 62 | 63 | org.springframework.cloud 64 | spring-cloud-starter-config 65 | 66 | 67 | 68 | org.springframework.cloud 69 | spring-cloud-starter-netflix-eureka-client 70 | 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-starter-actuator 75 | 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-starter-oauth2-resource-server 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.springframework.cloud 88 | spring-cloud-dependencies 89 | ${spring-cloud.version} 90 | pom 91 | import 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | org.springframework.boot 102 | spring-boot-maven-plugin 103 | 104 | 105 | 106 | org.projectlombok 107 | lombok 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | --------------------------------------------------------------------------------