├── auth ├── auth-service │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── pro │ │ │ │ │ └── teamlead │ │ │ │ │ └── kubepay │ │ │ │ │ └── auth │ │ │ │ │ ├── repository │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── domain │ │ │ │ │ ├── exception │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── model │ │ │ │ │ │ └── JwtToken.java │ │ │ │ │ └── service │ │ │ │ │ │ └── AuthService.java │ │ │ │ │ ├── AuthApplication.java │ │ │ │ │ ├── configuration │ │ │ │ │ ├── ServicePrincipalConfiguration.java │ │ │ │ │ ├── SpringSecurityConfiguration.java │ │ │ │ │ └── JwtConfiguration.java │ │ │ │ │ ├── api │ │ │ │ │ └── AuthApiController.java │ │ │ │ │ ├── application │ │ │ │ │ └── AuthApplicationService.java │ │ │ │ │ └── component │ │ │ │ │ └── ServiceTokenProvider.java │ │ │ └── resources │ │ │ │ ├── jwt │ │ │ │ ├── public.pem │ │ │ │ └── private.pem │ │ │ │ ├── git.properties │ │ │ │ └── application.yml │ │ └── test │ │ │ └── resources │ │ │ ├── application-test.yml │ │ │ └── integration │ │ │ └── unprotected-api │ │ │ ├── login │ │ │ ├── success │ │ │ │ ├── mock.json │ │ │ │ ├── response.json │ │ │ │ └── request.json │ │ │ └── error │ │ │ │ ├── mock.json │ │ │ │ ├── request.json │ │ │ │ └── response.json │ │ │ ├── signup │ │ │ └── success │ │ │ │ ├── response.json │ │ │ │ ├── request.json │ │ │ │ └── mock.json │ │ │ └── service-token │ │ │ └── success │ │ │ └── response.json │ └── Dockerfile ├── auth-sdk │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── auth │ │ │ └── sdk │ │ │ ├── user │ │ │ ├── AuthPrincipal.java │ │ │ ├── UserPrincipal.java │ │ │ ├── ServicePrincipal.java │ │ │ ├── JwtPrincipal.java │ │ │ └── JwtPrincipalExtractor.java │ │ │ ├── api │ │ │ ├── ServiceApi.java │ │ │ └── PublicApi.java │ │ │ ├── util │ │ │ ├── JwtFields.java │ │ │ └── JwtTokenProvider.java │ │ │ ├── authority │ │ │ ├── UnprotectedApi.java │ │ │ ├── PublicApi.java │ │ │ ├── ServiceApi.java │ │ │ ├── CanCreateUser.java │ │ │ ├── CanCreateWallet.java │ │ │ ├── CanIntroinspectUser.java │ │ │ ├── CanViewUserPassword.java │ │ │ └── Authority.java │ │ │ ├── feign │ │ │ ├── UserPrincipalTokenExpander.java │ │ │ ├── JwtPrincipalFeignContract.java │ │ │ └── JwtPrincipalParameterProcessor.java │ │ │ ├── configuration │ │ │ └── AuthSdkAutoConfiguration.java │ │ │ ├── controller │ │ │ ├── PublicApiController.java │ │ │ ├── ServiceApiController.java │ │ │ └── UnprotectedApiController.java │ │ │ └── error │ │ │ └── ApiClientErrorDecoder.java │ └── pom.xml ├── auth-api │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── auth │ │ │ └── api │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── AuthToken.java │ │ │ │ └── AuthRequest.java │ │ │ └── exception │ │ │ │ ├── InvalidCredentialsException.java │ │ │ │ ├── UserAlreadyExistsException.java │ │ │ │ └── AuthApiException.java │ │ │ ├── AuthApiMethodDictionary.java │ │ │ └── AuthApi.java │ └── pom.xml ├── auth-client │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── auth │ │ │ └── client │ │ │ ├── AuthClient.java │ │ │ ├── component │ │ │ └── AuthClientErrorDecoder.java │ │ │ ├── starter │ │ │ ├── AuthClientAutoConfiguration.java │ │ │ └── AuthClientFeignConfiguration.java │ │ │ └── configuration │ │ │ ├── AuthClientConfiguration.java │ │ │ └── ServicePrincipalConfiguration.java │ └── pom.xml └── pom.xml ├── user ├── user-service │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── pro │ │ │ │ │ └── teamlead │ │ │ │ │ └── kubepay │ │ │ │ │ └── user │ │ │ │ │ ├── configuration │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── domain │ │ │ │ │ ├── exception │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── model │ │ │ │ │ │ └── User.java │ │ │ │ │ └── service │ │ │ │ │ │ └── UserService.java │ │ │ │ │ ├── repository │ │ │ │ │ ├── UserRepository.java │ │ │ │ │ └── impl │ │ │ │ │ │ └── InMemoryUserRepository.java │ │ │ │ │ ├── UserServiceApplication.java │ │ │ │ │ ├── api │ │ │ │ │ ├── UserPublicApiController.java │ │ │ │ │ └── UserServiceApiController.java │ │ │ │ │ └── application │ │ │ │ │ └── UserApplicationService.java │ │ │ └── resources │ │ │ │ ├── jwt │ │ │ │ └── public.pem │ │ │ │ └── application.yml │ │ └── test │ │ │ ├── resources │ │ │ ├── integration │ │ │ │ ├── service-api │ │ │ │ │ ├── create-user │ │ │ │ │ │ ├── success │ │ │ │ │ │ │ ├── response.json │ │ │ │ │ │ │ └── request.json │ │ │ │ │ │ └── error │ │ │ │ │ │ │ └── request.json │ │ │ │ │ ├── get-user-info │ │ │ │ │ │ ├── success │ │ │ │ │ │ │ ├── response.json │ │ │ │ │ │ │ └── user.json │ │ │ │ │ │ └── error │ │ │ │ │ │ │ └── user.json │ │ │ │ │ └── get-password-hash │ │ │ │ │ │ ├── error │ │ │ │ │ │ └── user.json │ │ │ │ │ │ └── success │ │ │ │ │ │ └── user.json │ │ │ │ └── public-api │ │ │ │ │ └── get-my-info │ │ │ │ │ └── success │ │ │ │ │ ├── response.json │ │ │ │ │ └── user.json │ │ │ └── application-test.yml │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── user │ │ │ └── UserPublicApiControllerTest.java │ └── Dockerfile ├── user-api │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── user │ │ │ └── api │ │ │ ├── UserApi.java │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── UserInfo.java │ │ │ │ └── CreateUserRequest.java │ │ │ └── exception │ │ │ │ ├── UserNotFoundException.java │ │ │ │ └── UserApiException.java │ │ │ ├── UserApiMethodDictionary.java │ │ │ ├── UserPublicApi.java │ │ │ └── UserServiceApi.java │ └── pom.xml ├── user-client │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── user │ │ │ └── client │ │ │ ├── UserClient.java │ │ │ ├── component │ │ │ ├── UserClientErrorDecoder.java │ │ │ └── UserClientRequestInterceptor.java │ │ │ ├── starter │ │ │ ├── UserClientAutoConfiguration.java │ │ │ └── UserClientFeignConfiguration.java │ │ │ └── configuration │ │ │ └── UserClientConfiguration.java │ └── pom.xml └── pom.xml ├── wallet ├── wallet-service │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── pro │ │ │ │ │ └── teamlead │ │ │ │ │ └── kubepay │ │ │ │ │ └── wallet │ │ │ │ │ ├── domain │ │ │ │ │ ├── exception │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── service │ │ │ │ │ │ ├── NotificationService.java │ │ │ │ │ │ └── BillingService.java │ │ │ │ │ └── model │ │ │ │ │ │ └── Wallet.java │ │ │ │ │ ├── repository │ │ │ │ │ ├── WalletRepository.java │ │ │ │ │ └── impl │ │ │ │ │ │ └── InMemoryWalletRepository.java │ │ │ │ │ ├── WalletServiceApplication.java │ │ │ │ │ ├── configuration │ │ │ │ │ └── RestTemplateConfiguration.java │ │ │ │ │ ├── api │ │ │ │ │ ├── WalletServiceApiController.java │ │ │ │ │ └── WalletPublicApiController.java │ │ │ │ │ └── application │ │ │ │ │ └── WalletApplicationService.java │ │ │ └── resources │ │ │ │ ├── jwt │ │ │ │ └── public.pem │ │ │ │ └── application.yml │ │ └── test │ │ │ ├── resources │ │ │ ├── integration │ │ │ │ ├── service-api │ │ │ │ │ └── create-wallet │ │ │ │ │ │ ├── error │ │ │ │ │ │ ├── response.json │ │ │ │ │ │ └── request.json │ │ │ │ │ │ └── success │ │ │ │ │ │ ├── response.json │ │ │ │ │ │ └── request.json │ │ │ │ └── public-api │ │ │ │ │ ├── transfer-funds │ │ │ │ │ └── success │ │ │ │ │ │ ├── amount.json │ │ │ │ │ │ ├── response.json │ │ │ │ │ │ ├── request.json │ │ │ │ │ │ ├── mock.json │ │ │ │ │ │ ├── wallet1.json │ │ │ │ │ │ └── wallet2.json │ │ │ │ │ └── top-up │ │ │ │ │ └── success │ │ │ │ │ ├── response.json │ │ │ │ │ └── wallet.json │ │ │ └── application-test.yml │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── wallet │ │ │ ├── SwaggerApiDocsTest.java │ │ │ └── WalletServiceApiControllerTest.java │ └── Dockerfile ├── wallet-client │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── wallet │ │ │ └── client │ │ │ ├── WalletClient.java │ │ │ ├── component │ │ │ ├── WalletClientErrorDecoder.java │ │ │ └── WalletClientRequestInterceptor.java │ │ │ ├── starter │ │ │ ├── WalletClientAutoConfiguration.java │ │ │ └── WalletClientFeignConfiguration.java │ │ │ └── configuration │ │ │ └── WalletClientConfiguration.java │ └── pom.xml ├── wallet-api │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── wallet │ │ │ └── api │ │ │ ├── WalletApi.java │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── WalletInfo.java │ │ │ │ ├── TransferFundsRequest.java │ │ │ │ └── CreateWalletRequest.java │ │ │ └── exception │ │ │ │ ├── WalletNotFoundException.java │ │ │ │ ├── InvalidRecipientException.java │ │ │ │ ├── InsufficientFundsException.java │ │ │ │ ├── BillingNotAvailableException.java │ │ │ │ ├── WalletAlreadyCreatedException.java │ │ │ │ └── WalletApiException.java │ │ │ ├── WalletApiMethodDictionary.java │ │ │ ├── WalletServiceApi.java │ │ │ └── WalletPublicApi.java │ └── pom.xml └── pom.xml ├── gateway ├── gateway-service │ ├── Dockerfile │ └── src │ │ └── main │ │ ├── resources │ │ ├── static │ │ │ ├── favicon.ico │ │ │ ├── swagger.png │ │ │ ├── tracing.png │ │ │ ├── screencast.gif │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ └── site.webmanifest │ │ └── application.yml │ │ └── java │ │ └── pro │ │ └── teamlead │ │ └── kubepay │ │ └── gateway │ │ ├── GatewayApplication.java │ │ ├── filters │ │ └── RateLimiterConfiguration.java │ │ └── component │ │ └── SimpleInMemoryRateLimiter.java └── pom.xml ├── common ├── swagger │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── common │ │ │ └── swagger │ │ │ └── configuration │ │ │ └── SwaggerConfiguration.java │ └── pom.xml ├── metrics │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── common │ │ │ └── metrics │ │ │ └── configuration │ │ │ └── ObservedAspectConfiguration.java │ └── pom.xml ├── testing │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ └── spring.factories │ │ │ └── jwt │ │ │ │ ├── public.pem │ │ │ │ └── private.pem │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── common │ │ │ └── testing │ │ │ └── integration │ │ │ ├── testcase │ │ │ ├── TestCase.java │ │ │ └── FileSourceProvider.java │ │ │ ├── configuration │ │ │ ├── Constants.java │ │ │ ├── JwtConfiguration.java │ │ │ └── WireMockTestConfiguration.java │ │ │ ├── annotation │ │ │ ├── WireMockEnabled.java │ │ │ └── IntegrationTest.java │ │ │ ├── initializer │ │ │ └── TestPropertiesInitializer.java │ │ │ └── test │ │ │ └── ApiControllerIntegrationTest.java │ └── pom.xml ├── service │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── common │ │ │ └── service │ │ │ ├── configuration │ │ │ ├── ApiConfiguration.java │ │ │ ├── SpringSecurityConfiguration.java │ │ │ └── ServiceSecurityBeanConfiguration.java │ │ │ └── advice │ │ │ └── RestExceptionControllerAdvice.java │ └── pom.xml ├── api │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── common │ │ │ └── api │ │ │ └── domain │ │ │ └── exception │ │ │ ├── ApiException.java │ │ │ └── validation │ │ │ ├── UserFieldValidator.java │ │ │ └── ValidUserField.java │ └── pom.xml ├── util │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── pro │ │ │ └── teamlead │ │ │ └── kubepay │ │ │ └── common │ │ │ └── util │ │ │ ├── RandomStringUtil.java │ │ │ └── MapUtil.java │ └── pom.xml ├── json │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── pro │ │ └── teamlead │ │ └── kubepay │ │ └── common │ │ └── json │ │ └── ObjectMapperBuilder.java ├── pom.xml └── client │ ├── src │ └── main │ │ └── java │ │ └── pro │ │ └── teamlead │ │ └── kubepay │ │ └── common │ │ └── client │ │ └── configuration │ │ └── CommonFeignClientConfiguration.java │ └── pom.xml ├── .env ├── k8s ├── configmap.yml ├── zipkin-service.yml ├── auth-service.yml ├── user-service.yml ├── wallet-service.yml └── gateway-service.yml ├── lombok.config ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── .github └── workflows │ └── build-and-analyze.yml ├── bin └── run-all.sh ├── docker-compose.yml └── coverage └── pom.xml /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/repository/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/domain/exception/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /user/user-service/src/main/java/pro/teamlead/kubepay/user/configuration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /user/user-service/src/main/java/pro/teamlead/kubepay/user/domain/exception/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/domain/exception/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | user: 2 | url: ${wire-mock.url} 3 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/service-api/create-wallet/error/response.json: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/integration/unprotected-api/login/success/mock.json: -------------------------------------------------------------------------------- 1 | {noop}password 2 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/public-api/transfer-funds/success/amount.json: -------------------------------------------------------------------------------- 1 | 100 2 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/integration/unprotected-api/login/error/mock.json: -------------------------------------------------------------------------------- 1 | {noop}INVALID_PASSWORD 2 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/integration/unprotected-api/login/success/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "noop" 3 | } 4 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/integration/unprotected-api/signup/success/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "noop" 3 | } 4 | -------------------------------------------------------------------------------- /auth/auth-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17-alpine 2 | COPY target/*.jar service.jar 3 | CMD java ${JAVA_OPTS} -jar service.jar 4 | -------------------------------------------------------------------------------- /user/user-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17-alpine 2 | COPY target/*.jar service.jar 3 | CMD java ${JAVA_OPTS} -jar service.jar 4 | -------------------------------------------------------------------------------- /user/user-service/src/test/resources/integration/service-api/create-user/success/response.json: -------------------------------------------------------------------------------- 1 | {"user":"user","enabled":true} 2 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/integration/unprotected-api/service-token/success/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "noop" 3 | } 4 | -------------------------------------------------------------------------------- /gateway/gateway-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17-alpine 2 | COPY target/*.jar service.jar 3 | CMD java ${JAVA_OPTS} -jar service.jar 4 | -------------------------------------------------------------------------------- /user/user-service/src/test/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | auth: 2 | url: ${wire-mock.url} 3 | wallet: 4 | url: ${wire-mock.url} 5 | -------------------------------------------------------------------------------- /wallet/wallet-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17-alpine 2 | COPY target/*.jar service.jar 3 | CMD java ${JAVA_OPTS} -jar service.jar 4 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/public-api/transfer-funds/success/response.json: -------------------------------------------------------------------------------- 1 | {"address":"noop","balance":0} 2 | -------------------------------------------------------------------------------- /user/user-service/src/test/resources/integration/public-api/get-my-info/success/response.json: -------------------------------------------------------------------------------- 1 | {"user":"user","name":"name","enabled":true} 2 | -------------------------------------------------------------------------------- /user/user-service/src/test/resources/integration/service-api/create-user/error/request.json: -------------------------------------------------------------------------------- 1 | {"user":"user","passwordHash":"{noop}test"} 2 | -------------------------------------------------------------------------------- /user/user-service/src/test/resources/integration/service-api/create-user/success/request.json: -------------------------------------------------------------------------------- 1 | {"user":"user","passwordHash":"{noop}test"} 2 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/integration/unprotected-api/login/error/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "password": "password" 4 | } 5 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/public-api/top-up/success/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "noop", 3 | "balance": 100 4 | } 5 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/integration/unprotected-api/login/success/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "password": "password" 4 | } 5 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/integration/unprotected-api/signup/success/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "password": "password" 4 | } 5 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/service-api/create-wallet/error/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "initialBalance": 0 4 | } 5 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/service-api/create-wallet/success/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "random", 3 | "balance": 0 4 | } 5 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/service-api/create-wallet/success/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "initialBalance": 0 4 | } 5 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/integration/unprotected-api/signup/success/mock.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "User Not Found", 3 | "type": "USER_NOT_FOUND" 4 | } 5 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/public-api/top-up/success/wallet.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "noop", 3 | "balance": 0, 4 | "user": "user" 5 | } 6 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/public-api/transfer-funds/success/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "to": "recipient_address", 3 | "amount": 100 4 | } 5 | -------------------------------------------------------------------------------- /auth/auth-service/src/test/resources/integration/unprotected-api/login/error/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Invalid Credentials", 3 | "type": "INVALID_CREDENTIALS" 4 | } 5 | -------------------------------------------------------------------------------- /user/user-service/src/test/resources/integration/service-api/get-user-info/success/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "name": "name", 4 | "enabled": true 5 | } 6 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | auth: 2 | url: ${wire-mock.url} 3 | billing: 4 | url: ${wire-mock.url} 5 | user: 6 | url: ${wire-mock.url} 7 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/public-api/transfer-funds/success/mock.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "name": "Random Name", 4 | "enabled": true 5 | } 6 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/public-api/transfer-funds/success/wallet1.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "noop", 3 | "balance": 100, 4 | "user": "user" 5 | } 6 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | pro.teamlead.kubepay.auth.sdk.configuration.AuthSdkAutoConfiguration 2 | -------------------------------------------------------------------------------- /common/swagger/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | pro.teamlead.kubepay.common.swagger.configuration.SwaggerConfiguration 2 | -------------------------------------------------------------------------------- /user/user-api/src/main/java/pro/teamlead/kubepay/user/api/UserApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.api; 2 | 3 | public interface UserApi extends UserServiceApi, UserPublicApi { 4 | } 5 | -------------------------------------------------------------------------------- /user/user-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | pro.teamlead.kubepay.user.client.starter.UserClientAutoConfiguration 2 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/domain/model/JwtToken.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.domain.model; 2 | 3 | public record JwtToken(String token) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /common/metrics/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | pro.teamlead.kubepay.common.metrics.configuration.ObservedAspectConfiguration 2 | -------------------------------------------------------------------------------- /wallet/wallet-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | pro.teamlead.kubepay.wallet.client.starter.WalletClientAutoConfiguration 2 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/resources/integration/public-api/transfer-funds/success/wallet2.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "recipient_address", 3 | "balance": 0, 4 | "user": "recipient" 5 | } 6 | -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamlead/spring-microservices-kubernetes-demo/HEAD/gateway/gateway-service/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/static/swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamlead/spring-microservices-kubernetes-demo/HEAD/gateway/gateway-service/src/main/resources/static/swagger.png -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/static/tracing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamlead/spring-microservices-kubernetes-demo/HEAD/gateway/gateway-service/src/main/resources/static/tracing.png -------------------------------------------------------------------------------- /user/user-service/src/test/resources/integration/public-api/get-my-info/success/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "name": "name", 4 | "enabled": true, 5 | "passwordHash": "{noop}test" 6 | } 7 | -------------------------------------------------------------------------------- /user/user-service/src/test/resources/integration/service-api/get-password-hash/error/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "name": "name", 4 | "enabled": true, 5 | "passwordHash": "{noop}test" 6 | } 7 | -------------------------------------------------------------------------------- /user/user-service/src/test/resources/integration/service-api/get-user-info/error/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "name": "name", 4 | "enabled": true, 5 | "passwordHash": "{noop}test" 6 | } 7 | -------------------------------------------------------------------------------- /user/user-service/src/test/resources/integration/service-api/get-user-info/success/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "name": "name", 4 | "enabled": true, 5 | "passwordHash": "{noop}test" 6 | } 7 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/user/AuthPrincipal.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.user; 2 | 3 | public interface AuthPrincipal { 4 | 5 | String getToken(); 6 | } 7 | -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/static/screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamlead/spring-microservices-kubernetes-demo/HEAD/gateway/gateway-service/src/main/resources/static/screencast.gif -------------------------------------------------------------------------------- /user/user-service/src/test/resources/integration/service-api/get-password-hash/success/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "user", 3 | "name": "name", 4 | "enabled": true, 5 | "passwordHash": "{noop}test" 6 | } 7 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/WalletApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api; 2 | 3 | public interface WalletApi extends WalletServiceApi, WalletPublicApi { 4 | } 5 | -------------------------------------------------------------------------------- /common/testing/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.context.ApplicationContextInitializer=\ 2 | pro.teamlead.kubepay.common.testing.integration.initializer.TestPropertiesInitializer 3 | -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamlead/spring-microservices-kubernetes-demo/HEAD/gateway/gateway-service/src/main/resources/static/favicon-16x16.png -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamlead/spring-microservices-kubernetes-demo/HEAD/gateway/gateway-service/src/main/resources/static/favicon-32x32.png -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamlead/spring-microservices-kubernetes-demo/HEAD/gateway/gateway-service/src/main/resources/static/apple-touch-icon.png -------------------------------------------------------------------------------- /user/user-api/src/main/java/pro/teamlead/kubepay/user/api/domain/model/UserInfo.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.api.domain.model; 2 | 3 | public record UserInfo(String user, String name, boolean enabled) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamlead/spring-microservices-kubernetes-demo/HEAD/gateway/gateway-service/src/main/resources/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamlead/spring-microservices-kubernetes-demo/HEAD/gateway/gateway-service/src/main/resources/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /auth/auth-api/src/main/java/pro/teamlead/kubepay/auth/api/domain/model/AuthToken.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.api.domain.model; 2 | import jakarta.validation.constraints.NotBlank; 3 | 4 | public record AuthToken(@NotBlank String token) { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | GATEWAY_URL=http://gateway:8100 2 | AUTH_URL=http://auth:8101 3 | USER_URL=http://user:8102 4 | WALLET_URL=http://wallet:8103 5 | BILLING_URL=http://gateway:8100/billing/test 6 | STANDALONE=false 7 | TRACING=true 8 | ZIPKIN_ENDPOINT=http://zipkin:9411/api/v2/spans 9 | -------------------------------------------------------------------------------- /auth/auth-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | pro.teamlead.kubepay.auth.client.starter.AuthClientAutoConfiguration 2 | pro.teamlead.kubepay.auth.client.configuration.ServicePrincipalConfiguration 3 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/domain/model/WalletInfo.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api.domain.model; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record WalletInfo(String address, BigDecimal balance) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/static/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /user/user-service/src/main/java/pro/teamlead/kubepay/user/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.repository; 2 | 3 | import pro.teamlead.kubepay.user.domain.model.User; 4 | 5 | public interface UserRepository { 6 | 7 | User findByUser(String user); 8 | 9 | User save(User user); 10 | } 11 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/api/ServiceApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.api; 2 | 3 | import io.swagger.v3.oas.annotations.Hidden; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target(ElementType.TYPE) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Hidden 10 | public @interface ServiceApi { 11 | } 12 | -------------------------------------------------------------------------------- /common/service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | pro.teamlead.kubepay.common.service.configuration.ServiceSecurityBeanConfiguration 2 | pro.teamlead.kubepay.common.service.configuration.ApiConfiguration 3 | pro.teamlead.kubepay.common.service.configuration.SpringSecurityConfiguration 4 | 5 | -------------------------------------------------------------------------------- /k8s/configmap.yml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: shared-env-config 5 | data: 6 | AUTH_URL: "http://auth" 7 | USER_URL: "http://user" 8 | WALLET_URL: "http://wallet" 9 | BILLING_URL: "http://gateway/billing/test" 10 | STANDALONE: "false" 11 | TRACING: "true" 12 | ZIPKIN_ENDPOINT: "http://zipkin:9411/api/v2/spans" 13 | -------------------------------------------------------------------------------- /common/api/src/main/java/pro/teamlead/kubepay/common/api/domain/exception/ApiException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.api.domain.exception; 2 | 3 | public abstract class ApiException extends RuntimeException { 4 | 5 | protected ApiException(String message) { 6 | super(message); 7 | } 8 | 9 | public abstract String getType(); 10 | } 11 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | lombok.anyConstructor.addConstructorProperties = true 2 | lombok.fieldDefaults.defaultPrivate = true 3 | lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier 4 | lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value 5 | lombok.copyableAnnotations += org.springframework.context.annotation.Lazy 6 | lombok.accessors.chain=true 7 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/repository/WalletRepository.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.repository; 2 | 3 | import pro.teamlead.kubepay.wallet.domain.model.Wallet; 4 | 5 | public interface WalletRepository { 6 | 7 | Wallet findByAddress(String address); 8 | 9 | Wallet findByUser(String user); 10 | 11 | Wallet save(Wallet wallet); 12 | } 13 | -------------------------------------------------------------------------------- /auth/auth-api/src/main/java/pro/teamlead/kubepay/auth/api/domain/model/AuthRequest.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.api.domain.model; 2 | 3 | import pro.teamlead.kubepay.common.api.domain.exception.validation.ValidUserField; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | public record AuthRequest(@ValidUserField String user, 7 | @NotBlank String password) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/api/PublicApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.api; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface PublicApi { 11 | } 12 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/user/UserPrincipal.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.user; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Getter 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class UserPrincipal implements AuthPrincipal { 11 | 12 | private String user; 13 | 14 | private String token; 15 | } 16 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/user/ServicePrincipal.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.user; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Getter 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class ServicePrincipal implements AuthPrincipal { 11 | 12 | private String service; 13 | 14 | private String token; 15 | } 16 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/AuthApplication.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class AuthApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(AuthApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/util/JwtFields.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.util; 2 | 3 | public final class JwtFields { 4 | 5 | private JwtFields() { 6 | } 7 | 8 | public static final String SCOPE = "scope"; 9 | 10 | public static final String USER = "sub"; 11 | 12 | public static final String ISSUED_AT = "iat"; 13 | 14 | public static final String EXPIRE_AT = "exp"; 15 | } 16 | -------------------------------------------------------------------------------- /user/user-api/src/main/java/pro/teamlead/kubepay/user/api/domain/model/CreateUserRequest.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.api.domain.model; 2 | 3 | import pro.teamlead.kubepay.common.api.domain.exception.validation.ValidUserField; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | 7 | public record CreateUserRequest(@ValidUserField String user, 8 | @NotBlank String passwordHash) { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/java/pro/teamlead/kubepay/gateway/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.gateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class GatewayApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(GatewayApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /user/user-service/src/main/java/pro/teamlead/kubepay/user/UserServiceApplication.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class UserServiceApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(UserServiceApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/WalletServiceApplication.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class WalletServiceApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(WalletServiceApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /user/user-api/src/main/java/pro/teamlead/kubepay/user/api/domain/exception/UserNotFoundException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.api.domain.exception; 2 | 3 | public class UserNotFoundException extends UserApiException { 4 | 5 | public UserNotFoundException() { 6 | super("User Not Found"); 7 | } 8 | 9 | public static final String TYPE = "USER_NOT_FOUND"; 10 | 11 | @Override 12 | public String getType() { 13 | return TYPE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/authority/UnprotectedApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.authority; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ ElementType.METHOD, ElementType.TYPE }) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | //@PreAuthorize("permitAll()") 11 | public @interface UnprotectedApi { 12 | } 13 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/user/JwtPrincipal.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.user; 2 | 3 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target({ElementType.PARAMETER, ElementType.TYPE}) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Documented 10 | @AuthenticationPrincipal(expression = "@JwtPrincipalExtractor.getPrincipal(#this)") 11 | public @interface JwtPrincipal { 12 | } 13 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/domain/model/TransferFundsRequest.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api.domain.model; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotEmpty; 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | import java.math.BigDecimal; 8 | 9 | public record TransferFundsRequest(@NotEmpty String to, 10 | @Min(0) @NotNull BigDecimal amount) { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/domain/exception/WalletNotFoundException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api.domain.exception; 2 | 3 | public class WalletNotFoundException extends WalletApiException { 4 | 5 | public WalletNotFoundException() { 6 | super("Wallet not found"); 7 | } 8 | 9 | public static final String TYPE = "WALLET_NOT_FOUND"; 10 | 11 | @Override 12 | public String getType() { 13 | return TYPE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/domain/model/CreateWalletRequest.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api.domain.model; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | import java.math.BigDecimal; 8 | 9 | public record CreateWalletRequest(@NotBlank String user, 10 | @NotNull @Min(0) BigDecimal initialBalance) { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /auth/auth-api/src/main/java/pro/teamlead/kubepay/auth/api/AuthApiMethodDictionary.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.api; 2 | 3 | public final class AuthApiMethodDictionary { 4 | 5 | private AuthApiMethodDictionary() { 6 | } 7 | 8 | public static final String AUTH_SIGNUP = "/signup"; 9 | 10 | public static final String AUTH_LOGIN = "/login"; 11 | 12 | public static final String AUTH_LOGOUT = "/logout"; 13 | 14 | public static final String SERVICE_TOKEN = "/service/{key}"; 15 | } 16 | -------------------------------------------------------------------------------- /auth/auth-api/src/main/java/pro/teamlead/kubepay/auth/api/domain/exception/InvalidCredentialsException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.api.domain.exception; 2 | 3 | public class InvalidCredentialsException extends AuthApiException { 4 | 5 | public InvalidCredentialsException() { 6 | super("Invalid Credentials"); 7 | } 8 | 9 | public static final String TYPE = "INVALID_CREDENTIALS"; 10 | 11 | @Override 12 | public String getType() { 13 | return TYPE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /auth/auth-api/src/main/java/pro/teamlead/kubepay/auth/api/domain/exception/UserAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.api.domain.exception; 2 | 3 | public class UserAlreadyExistsException extends AuthApiException { 4 | 5 | public UserAlreadyExistsException() { 6 | super("User already exists"); 7 | } 8 | 9 | public static final String TYPE = "USER_ALREADY_EXISTS"; 10 | 11 | @Override 12 | public String getType() { 13 | return TYPE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/domain/exception/InvalidRecipientException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api.domain.exception; 2 | 3 | public class InvalidRecipientException extends WalletApiException { 4 | 5 | public InvalidRecipientException() { 6 | super("Invalid recipient"); 7 | } 8 | 9 | public static final String TYPE = "WALLET_INVALID_RECIPIENT"; 10 | 11 | @Override 12 | public String getType() { 13 | return TYPE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/domain/exception/InsufficientFundsException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api.domain.exception; 2 | 3 | public class InsufficientFundsException extends WalletApiException { 4 | 5 | public InsufficientFundsException() { 6 | super("Insufficient funds"); 7 | } 8 | 9 | public static final String TYPE = "WALLET_INSUFFICIENT_FUNDS"; 10 | 11 | @Override 12 | public String getType() { 13 | return TYPE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/domain/exception/BillingNotAvailableException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api.domain.exception; 2 | 3 | public class BillingNotAvailableException extends WalletApiException { 4 | 5 | public BillingNotAvailableException() { 6 | super("Billing not available"); 7 | } 8 | 9 | public static final String TYPE = "BILLING_NOT_AVAILABLE"; 10 | 11 | @Override 12 | public String getType() { 13 | return TYPE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/domain/service/NotificationService.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.domain.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Slf4j 8 | @Service 9 | @RequiredArgsConstructor 10 | public class NotificationService { 11 | 12 | public void notify(String user, String message) { 13 | log.info("Message to: {}, text: {}", user, message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /auth/auth-client/src/main/java/pro/teamlead/kubepay/auth/client/AuthClient.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.client; 2 | 3 | import pro.teamlead.kubepay.auth.api.AuthApi; 4 | import org.springframework.cloud.openfeign.FeignClient; 5 | import pro.teamlead.kubepay.auth.client.configuration.AuthClientConfiguration; 6 | 7 | @FeignClient( 8 | value = "auth-client", 9 | url = "${auth.url}", 10 | configuration = {AuthClientConfiguration.class} 11 | ) 12 | public interface AuthClient extends AuthApi { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /user/user-api/src/main/java/pro/teamlead/kubepay/user/api/UserApiMethodDictionary.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.api; 2 | 3 | public final class UserApiMethodDictionary { 4 | 5 | private UserApiMethodDictionary() { 6 | 7 | } 8 | 9 | public static final String CREATE_USER = "/create"; 10 | 11 | public static final String GET_PASSWORD_HASH = "/getPasswordHash/{user}"; 12 | 13 | public static final String GET_MY_INFO = "/me"; 14 | 15 | public static final String GET_USER_INFO = "/info/{user}"; 16 | } 17 | -------------------------------------------------------------------------------- /user/user-client/src/main/java/pro/teamlead/kubepay/user/client/UserClient.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.client; 2 | 3 | import pro.teamlead.kubepay.user.api.UserApi; 4 | import pro.teamlead.kubepay.user.client.configuration.UserClientConfiguration; 5 | import org.springframework.cloud.openfeign.FeignClient; 6 | 7 | @FeignClient( 8 | value = "user-client", 9 | url = "${user.url}", 10 | configuration = {UserClientConfiguration.class} 11 | ) 12 | public interface UserClient extends UserApi { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/domain/exception/WalletAlreadyCreatedException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api.domain.exception; 2 | 3 | public class WalletAlreadyCreatedException extends WalletApiException { 4 | 5 | public WalletAlreadyCreatedException() { 6 | super("Wallet already created"); 7 | } 8 | 9 | public static final String TYPE = "WALLET_ALREADY_CREATED"; 10 | 11 | @Override 12 | public String getType() { 13 | return TYPE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/WalletApiMethodDictionary.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api; 2 | 3 | public final class WalletApiMethodDictionary { 4 | 5 | private WalletApiMethodDictionary() { 6 | 7 | } 8 | 9 | public static final String WALLET_CREATE = "/create"; 10 | 11 | public static final String WALLET_INFO = "/balance"; 12 | 13 | public static final String WALLET_TOPUP = "/topup"; 14 | 15 | public static final String WALLET_TRANSFER_FUNDS = "/transfer"; 16 | } 17 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/feign/UserPrincipalTokenExpander.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.feign; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.user.AuthPrincipal; 4 | import feign.Param; 5 | 6 | public class UserPrincipalTokenExpander implements Param.Expander { 7 | 8 | @Override 9 | public String expand(Object value) { 10 | if (value instanceof AuthPrincipal principal) { 11 | return "Bearer " + principal.getToken(); 12 | } 13 | return null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /auth/auth-client/src/main/java/pro/teamlead/kubepay/auth/client/component/AuthClientErrorDecoder.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.client.component; 2 | 3 | import pro.teamlead.kubepay.auth.api.domain.exception.AuthApiException; 4 | import pro.teamlead.kubepay.auth.sdk.error.ApiClientErrorDecoder; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | public class AuthClientErrorDecoder extends ApiClientErrorDecoder { 9 | 10 | public AuthClientErrorDecoder() { 11 | super(AuthApiException.class); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /common/service/src/main/java/pro/teamlead/kubepay/common/service/configuration/ApiConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.service.configuration; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @ConditionalOnWebApplication 9 | @ComponentScan(basePackages = "pro.teamlead.kubepay.common.service.advice") 10 | class ApiConfiguration { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /user/user-client/src/main/java/pro/teamlead/kubepay/user/client/component/UserClientErrorDecoder.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.client.component; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.error.ApiClientErrorDecoder; 4 | import pro.teamlead.kubepay.user.api.domain.exception.UserApiException; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | public class UserClientErrorDecoder extends ApiClientErrorDecoder { 9 | 10 | public UserClientErrorDecoder() { 11 | super(UserApiException.class); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/authority/PublicApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.authority; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ ElementType.METHOD, ElementType.TYPE }) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @PreAuthorize("hasRole('USER')") 13 | public @interface PublicApi { 14 | } 15 | -------------------------------------------------------------------------------- /common/testing/src/main/java/pro/teamlead/kubepay/common/testing/integration/testcase/TestCase.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.testing.integration.testcase; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | 6 | import java.lang.annotation.*; 7 | 8 | @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Documented 11 | @Test 12 | @ExtendWith(FileSourceProvider.class) 13 | public @interface TestCase { 14 | 15 | String[] value() default {}; 16 | } 17 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/authority/ServiceApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.authority; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ ElementType.METHOD, ElementType.TYPE }) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @PreAuthorize("hasRole('SERVICE')") 13 | public @interface ServiceApi { 14 | } 15 | -------------------------------------------------------------------------------- /wallet/wallet-client/src/main/java/pro/teamlead/kubepay/wallet/client/WalletClient.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.client; 2 | 3 | import pro.teamlead.kubepay.wallet.api.WalletApi; 4 | import org.springframework.cloud.openfeign.FeignClient; 5 | import pro.teamlead.kubepay.wallet.client.configuration.WalletClientConfiguration; 6 | 7 | @FeignClient( 8 | value = "wallet-client", 9 | url = "${wallet.url}", 10 | configuration = {WalletClientConfiguration.class} 11 | ) 12 | public interface WalletClient extends WalletApi { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/configuration/AuthSdkAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.configuration; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipalExtractor; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class AuthSdkAutoConfiguration { 9 | 10 | @Bean("JwtPrincipalExtractor") 11 | public JwtPrincipalExtractor extractor() { 12 | return new JwtPrincipalExtractor(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /wallet/wallet-client/src/main/java/pro/teamlead/kubepay/wallet/client/component/WalletClientErrorDecoder.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.client.component; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.error.ApiClientErrorDecoder; 4 | import pro.teamlead.kubepay.wallet.api.domain.exception.WalletApiException; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | public class WalletClientErrorDecoder extends ApiClientErrorDecoder { 9 | 10 | public WalletClientErrorDecoder() { 11 | super(WalletApiException.class); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /auth/auth-client/src/main/java/pro/teamlead/kubepay/auth/client/starter/AuthClientAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.client.starter; 2 | 3 | import pro.teamlead.kubepay.auth.client.AuthClient; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | 8 | @Configuration 9 | @ConditionalOnMissingBean({AuthClient.class}) 10 | @Import({AuthClientFeignConfiguration.class}) 11 | public class AuthClientAutoConfiguration { 12 | } 13 | -------------------------------------------------------------------------------- /user/user-client/src/main/java/pro/teamlead/kubepay/user/client/starter/UserClientAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.client.starter; 2 | 3 | import pro.teamlead.kubepay.user.client.UserClient; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | 8 | @Configuration 9 | @ConditionalOnMissingBean({UserClient.class}) 10 | @Import({UserClientFeignConfiguration.class}) 11 | public class UserClientAutoConfiguration { 12 | } 13 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/authority/CanCreateUser.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.authority; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ ElementType.METHOD, ElementType.TYPE }) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @PreAuthorize("hasAuthority('user:create') and hasRole('SERVICE')") 13 | public @interface CanCreateUser { 14 | } 15 | -------------------------------------------------------------------------------- /user/user-api/src/main/java/pro/teamlead/kubepay/user/api/UserPublicApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.api; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.api.PublicApi; 4 | import pro.teamlead.kubepay.user.api.domain.model.UserInfo; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipal; 7 | import pro.teamlead.kubepay.auth.sdk.user.UserPrincipal; 8 | 9 | @PublicApi 10 | public interface UserPublicApi { 11 | 12 | @GetMapping(UserApiMethodDictionary.GET_MY_INFO) 13 | UserInfo getMyInfo(@JwtPrincipal UserPrincipal user); 14 | } 15 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/authority/CanCreateWallet.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.authority; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ ElementType.METHOD, ElementType.TYPE }) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @PreAuthorize("hasAuthority('wallet:create') and hasRole('SERVICE')") 13 | public @interface CanCreateWallet { 14 | } 15 | 16 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/authority/CanIntroinspectUser.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.authority; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ ElementType.METHOD, ElementType.TYPE }) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @PreAuthorize("hasAuthority('user:info') and hasRole('SERVICE')") 13 | public @interface CanIntroinspectUser { 14 | } 15 | -------------------------------------------------------------------------------- /wallet/wallet-client/src/main/java/pro/teamlead/kubepay/wallet/client/starter/WalletClientAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.client.starter; 2 | 3 | import pro.teamlead.kubepay.wallet.client.WalletClient; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | 8 | @Configuration 9 | @ConditionalOnMissingBean({WalletClient.class}) 10 | @Import({WalletClientFeignConfiguration.class}) 11 | public class WalletClientAutoConfiguration { 12 | } 13 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/authority/CanViewUserPassword.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.authority; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ ElementType.METHOD, ElementType.TYPE }) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @PreAuthorize("hasAuthority('user:password') and hasRole('SERVICE')") 13 | public @interface CanViewUserPassword { 14 | } 15 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/configuration/RestTemplateConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.configuration; 2 | 3 | import org.springframework.boot.web.client.RestTemplateBuilder; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.client.RestTemplate; 7 | 8 | @Configuration(proxyBeanMethods = false) 9 | public class RestTemplateConfiguration { 10 | 11 | @Bean 12 | public RestTemplate restTemplate(RestTemplateBuilder builder) { 13 | return builder.build(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/util/src/main/java/pro/teamlead/kubepay/common/util/RandomStringUtil.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.util; 2 | 3 | import lombok.experimental.UtilityClass; 4 | 5 | import java.util.UUID; 6 | 7 | @UtilityClass 8 | public class RandomStringUtil { 9 | 10 | private volatile String mock = null; 11 | 12 | public static String generate() { 13 | return mock != null ? mock : UUID.randomUUID().toString(); 14 | } 15 | 16 | public static void setMock(String mock) { 17 | RandomStringUtil.mock = mock; 18 | } 19 | 20 | public static void removeMock() { 21 | RandomStringUtil.mock = null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/controller/PublicApiController.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.controller; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.authority.PublicApi; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @RestController 12 | //@PreAuthorize("hasAuthority('user')") 13 | @Target({ElementType.TYPE}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @PublicApi 16 | public @interface PublicApiController { 17 | } 18 | -------------------------------------------------------------------------------- /user/user-client/src/main/java/pro/teamlead/kubepay/user/client/component/UserClientRequestInterceptor.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.client.component; 2 | 3 | import feign.RequestInterceptor; 4 | import feign.RequestTemplate; 5 | 6 | public class UserClientRequestInterceptor implements RequestInterceptor { 7 | 8 | @Override 9 | public void apply(RequestTemplate template) { 10 | // The method is intentionally left empty for now because no interception is currently needed. 11 | // However, theoretically, we could use this method to automatically inject a ServicePrincipal 12 | // if needed in the future. 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/controller/ServiceApiController.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.controller; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.authority.ServiceApi; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @RestController 12 | //@PreAuthorize("hasAuthority('service')") 13 | @Target({ElementType.TYPE}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @ServiceApi 16 | public @interface ServiceApiController { 17 | } 18 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/controller/UnprotectedApiController.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.controller; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.authority.UnprotectedApi; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @RestController 12 | //@PreAuthorize("permitAll()") 13 | @Target({ElementType.TYPE}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @UnprotectedApi 16 | public @interface UnprotectedApiController { 17 | } 18 | -------------------------------------------------------------------------------- /wallet/wallet-client/src/main/java/pro/teamlead/kubepay/wallet/client/component/WalletClientRequestInterceptor.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.client.component; 2 | 3 | import feign.RequestInterceptor; 4 | import feign.RequestTemplate; 5 | 6 | public class WalletClientRequestInterceptor implements RequestInterceptor { 7 | 8 | @Override 9 | public void apply(RequestTemplate template) { 10 | // The method is intentionally left empty for now because no interception is currently needed. 11 | // However, theoretically, we could use this method to automatically inject a ServicePrincipal 12 | // if needed in the future. 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | !**/src/main/**/bin/ 17 | !**/src/test/**/bin/ 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | out/ 25 | !**/src/main/**/out/ 26 | !**/src/test/**/out/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | 38 | ### Custom ### 39 | /jar/ 40 | **/target/** 41 | /libraries/ 42 | -------------------------------------------------------------------------------- /common/util/src/main/java/pro/teamlead/kubepay/common/util/MapUtil.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.util; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Map; 7 | import java.util.NoSuchElementException; 8 | 9 | @UtilityClass 10 | public class MapUtil { 11 | 12 | public static K getKeyByValue(@NotNull Map map, V value) { 13 | 14 | for (Map.Entry entry : map.entrySet()) { 15 | if (value.equals(entry.getValue())) { 16 | return entry.getKey(); 17 | } 18 | } 19 | 20 | throw new NoSuchElementException(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /auth/auth-client/src/main/java/pro/teamlead/kubepay/auth/client/starter/AuthClientFeignConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.client.starter; 2 | 3 | import pro.teamlead.kubepay.auth.client.AuthClient; 4 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 5 | import org.springframework.cloud.openfeign.EnableFeignClients; 6 | import org.springframework.cloud.openfeign.FeignAutoConfiguration; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @EnableFeignClients( 10 | basePackageClasses = {AuthClient.class} 11 | ) 12 | @ImportAutoConfiguration({FeignAutoConfiguration.class}) 13 | @Configuration 14 | public class AuthClientFeignConfiguration { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /common/util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | util 7 | ${common.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | common 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /user/user-client/src/main/java/pro/teamlead/kubepay/user/client/starter/UserClientFeignConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.client.starter; 2 | 3 | import pro.teamlead.kubepay.user.client.UserClient; 4 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 5 | import org.springframework.cloud.openfeign.EnableFeignClients; 6 | import org.springframework.cloud.openfeign.FeignAutoConfiguration; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @EnableFeignClients( 10 | basePackageClasses = {UserClient.class} 11 | ) 12 | @ImportAutoConfiguration({FeignAutoConfiguration.class}) 13 | @Configuration 14 | public class UserClientFeignConfiguration { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /common/testing/src/main/java/pro/teamlead/kubepay/common/testing/integration/configuration/Constants.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.testing.integration.configuration; 2 | 3 | public final class Constants { 4 | 5 | private Constants() { 6 | 7 | } 8 | 9 | public static final String SPRING_TEST_PROFILE_NAME = "test"; 10 | 11 | public static final String ENV_PROPERTY_MAPPING_ROOT = "com.dats.test"; 12 | 13 | public static final String ENV_PROPERTY_MAPPING_WIREMOCK = "com.dats.test.wire-mock"; 14 | 15 | public static final String ENV_PROPERTY_IS_WIREMOCK_ENABLED = "com.dats.test.wire-mock.enabled"; 16 | 17 | public static final String ENV_PROPERTY_WIREMOCK_URL = "wire-mock.url"; 18 | } 19 | -------------------------------------------------------------------------------- /wallet/wallet-client/src/main/java/pro/teamlead/kubepay/wallet/client/starter/WalletClientFeignConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.client.starter; 2 | 3 | import pro.teamlead.kubepay.wallet.client.WalletClient; 4 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 5 | import org.springframework.cloud.openfeign.EnableFeignClients; 6 | import org.springframework.cloud.openfeign.FeignAutoConfiguration; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @EnableFeignClients( 10 | basePackageClasses = {WalletClient.class} 11 | ) 12 | @ImportAutoConfiguration({FeignAutoConfiguration.class}) 13 | @Configuration 14 | public class WalletClientFeignConfiguration { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | gateway 7 | 0.0.0 8 | pom 9 | 10 | 11 | pro.teamlead.kubepay 12 | root 13 | 0.0.0 14 | ../pom.xml 15 | 16 | 17 | 18 | gateway-service 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /k8s/zipkin-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: zipkin-service 5 | labels: 6 | app: zipkin 7 | spec: 8 | selector: 9 | app: zipkin 10 | ports: 11 | - protocol: TCP 12 | port: 9411 13 | targetPort: 9411 14 | type: NodePort 15 | 16 | --- 17 | 18 | apiVersion: apps/v1 19 | kind: Deployment 20 | metadata: 21 | name: zipkin-deployment 22 | labels: 23 | app: zipkin 24 | spec: 25 | replicas: 1 26 | selector: 27 | matchLabels: 28 | app: zipkin 29 | template: 30 | metadata: 31 | labels: 32 | app: zipkin 33 | spec: 34 | containers: 35 | - name: zipkin-container 36 | image: openzipkin/zipkin 37 | ports: 38 | - containerPort: 9411 39 | -------------------------------------------------------------------------------- /common/metrics/src/main/java/pro/teamlead/kubepay/common/metrics/configuration/ObservedAspectConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.metrics.configuration; 2 | 3 | import io.micrometer.observation.ObservationRegistry; 4 | import io.micrometer.observation.aop.ObservedAspect; 5 | 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 9 | 10 | @Configuration(proxyBeanMethods = false) 11 | class ObservedAspectConfiguration { 12 | 13 | @Bean 14 | @ConditionalOnMissingBean 15 | ObservedAspect observedAspect(ObservationRegistry observationRegistry) { 16 | return new ObservedAspect(observationRegistry); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /user/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | user 7 | 0.0.0 8 | pom 9 | 10 | 11 | pro.teamlead.kubepay 12 | root 13 | 0.0.0 14 | ../pom.xml 15 | 16 | 17 | 18 | user-service 19 | user-api 20 | user-client 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /common/api/src/main/java/pro/teamlead/kubepay/common/api/domain/exception/validation/UserFieldValidator.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.api.domain.exception.validation; 2 | 3 | import jakarta.validation.ConstraintValidator; 4 | import jakarta.validation.ConstraintValidatorContext; 5 | 6 | import java.util.regex.Pattern; 7 | 8 | public class UserFieldValidator implements ConstraintValidator { 9 | 10 | private static final Pattern VALID_PATTERN = Pattern.compile("^[a-zA-Z0-9-_]+$"); 11 | 12 | @Override 13 | public boolean isValid(String value, ConstraintValidatorContext context) { 14 | if (value == null) { 15 | return true; // Let @NotNull or similar annotations handle null checks 16 | } 17 | return VALID_PATTERN.matcher(value).matches(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /wallet/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | wallet 7 | 0.0.0 8 | pom 9 | 10 | 11 | pro.teamlead.kubepay 12 | root 13 | 0.0.0 14 | ../pom.xml 15 | 16 | 17 | 18 | wallet-api 19 | wallet-client 20 | wallet-service 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /common/api/src/main/java/pro/teamlead/kubepay/common/api/domain/exception/validation/ValidUserField.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.api.domain.exception.validation; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Constraint(validatedBy = UserFieldValidator.class) 14 | public @interface ValidUserField { 15 | 16 | String message() default "Should only contain letters (a-Z), numbers, hyphens, and underscores."; 17 | 18 | Class[] groups() default {}; 19 | 20 | Class[] payload() default {}; 21 | } 22 | -------------------------------------------------------------------------------- /auth/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | auth 7 | 0.0.0 8 | pom 9 | 10 | 11 | pro.teamlead.kubepay 12 | root 13 | 0.0.0 14 | ../pom.xml 15 | 16 | 17 | 18 | auth-service 19 | auth-sdk 20 | auth-api 21 | auth-client 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /common/testing/src/main/resources/jwt/public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAiuZ4N2VZ8bo95gLg/tyV 3 | p6hEaR7NnXqGsPdg7iWVEnHLMEMEpxKKSRqies2xgqJYK+vqdXF5qmIc9arMsKQQ 4 | wHW8U3uMUtfAE5XMjgX0eUv4MrZOexJViUxgHpWo214J3tq/+hXuuoFnz514q18d 5 | 413FW3l674+S7ISVjvrYQeI10IHfhXPG8YjXTtZl96ZeNN7Kfdn+twn/RyuBNaOE 6 | shvRhklk46BPFJYUQvh6W/KpA5c9csNj0QwJDqquwugM38GfSUaOYki9nBz+cYJW 7 | TwsA7ZjF53my606Ml1iUspkpypVSWOd9MAUjGOysLOQL//kpkIAhpKAHkKA0qTwa 8 | HtkrCyZLghTxHq6i89oMZFOdl2AAxz7AbB2xGPexitYc1gchGTnsKVvkMqe/IdNh 9 | I8T6YJAwVZ8kp2CmyMblg7+4XQORedSnY/Hdi19ljaBkkiUDOlsMcsY+2Da07O5G 10 | ojpVJLsjnlE4KjHNi9vdSOENS2Qlnuty2X/NziWDAzrnhsINMaOEuwwWco+8HYrv 11 | lqichDEK2colbDpxWNBqBpTHF4p+jtBVzIomXFTm7r5PRaPrVcP8yMF5TWs9jwUq 12 | EqjWnErcnmNb9F3AeocFi8DTd6x6YxXA9G8YMdx1JEKiggIPu587viYxiFEgSmC+ 13 | DSHH49SuJzUozyddXZG0A5kCAwEAAQ== 14 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /auth/auth-client/src/main/java/pro/teamlead/kubepay/auth/client/configuration/AuthClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.client.configuration; 2 | 3 | import org.zalando.logbook.Logbook; 4 | import org.zalando.logbook.openfeign.FeignLogbookLogger; 5 | import pro.teamlead.kubepay.auth.client.component.AuthClientErrorDecoder; 6 | import feign.Logger; 7 | import feign.codec.ErrorDecoder; 8 | import org.springframework.context.annotation.Bean; 9 | 10 | public class AuthClientConfiguration { 11 | 12 | @Bean 13 | public Logger logger(Logbook logbook) { 14 | return new FeignLogbookLogger(logbook); 15 | } 16 | 17 | @Bean 18 | public Logger.Level loglevel() { 19 | return Logger.Level.FULL; 20 | } 21 | 22 | @Bean 23 | public ErrorDecoder errorDecoder() { 24 | return new AuthClientErrorDecoder(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/resources/jwt/public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAiuZ4N2VZ8bo95gLg/tyV 3 | p6hEaR7NnXqGsPdg7iWVEnHLMEMEpxKKSRqies2xgqJYK+vqdXF5qmIc9arMsKQQ 4 | wHW8U3uMUtfAE5XMjgX0eUv4MrZOexJViUxgHpWo214J3tq/+hXuuoFnz514q18d 5 | 413FW3l674+S7ISVjvrYQeI10IHfhXPG8YjXTtZl96ZeNN7Kfdn+twn/RyuBNaOE 6 | shvRhklk46BPFJYUQvh6W/KpA5c9csNj0QwJDqquwugM38GfSUaOYki9nBz+cYJW 7 | TwsA7ZjF53my606Ml1iUspkpypVSWOd9MAUjGOysLOQL//kpkIAhpKAHkKA0qTwa 8 | HtkrCyZLghTxHq6i89oMZFOdl2AAxz7AbB2xGPexitYc1gchGTnsKVvkMqe/IdNh 9 | I8T6YJAwVZ8kp2CmyMblg7+4XQORedSnY/Hdi19ljaBkkiUDOlsMcsY+2Da07O5G 10 | ojpVJLsjnlE4KjHNi9vdSOENS2Qlnuty2X/NziWDAzrnhsINMaOEuwwWco+8HYrv 11 | lqichDEK2colbDpxWNBqBpTHF4p+jtBVzIomXFTm7r5PRaPrVcP8yMF5TWs9jwUq 12 | EqjWnErcnmNb9F3AeocFi8DTd6x6YxXA9G8YMdx1JEKiggIPu587viYxiFEgSmC+ 13 | DSHH49SuJzUozyddXZG0A5kCAwEAAQ== 14 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /user/user-service/src/main/resources/jwt/public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAiuZ4N2VZ8bo95gLg/tyV 3 | p6hEaR7NnXqGsPdg7iWVEnHLMEMEpxKKSRqies2xgqJYK+vqdXF5qmIc9arMsKQQ 4 | wHW8U3uMUtfAE5XMjgX0eUv4MrZOexJViUxgHpWo214J3tq/+hXuuoFnz514q18d 5 | 413FW3l674+S7ISVjvrYQeI10IHfhXPG8YjXTtZl96ZeNN7Kfdn+twn/RyuBNaOE 6 | shvRhklk46BPFJYUQvh6W/KpA5c9csNj0QwJDqquwugM38GfSUaOYki9nBz+cYJW 7 | TwsA7ZjF53my606Ml1iUspkpypVSWOd9MAUjGOysLOQL//kpkIAhpKAHkKA0qTwa 8 | HtkrCyZLghTxHq6i89oMZFOdl2AAxz7AbB2xGPexitYc1gchGTnsKVvkMqe/IdNh 9 | I8T6YJAwVZ8kp2CmyMblg7+4XQORedSnY/Hdi19ljaBkkiUDOlsMcsY+2Da07O5G 10 | ojpVJLsjnlE4KjHNi9vdSOENS2Qlnuty2X/NziWDAzrnhsINMaOEuwwWco+8HYrv 11 | lqichDEK2colbDpxWNBqBpTHF4p+jtBVzIomXFTm7r5PRaPrVcP8yMF5TWs9jwUq 12 | EqjWnErcnmNb9F3AeocFi8DTd6x6YxXA9G8YMdx1JEKiggIPu587viYxiFEgSmC+ 13 | DSHH49SuJzUozyddXZG0A5kCAwEAAQ== 14 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /common/json/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | json 7 | ${common.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | common 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | com.fasterxml.jackson.datatype 19 | jackson-datatype-jsr310 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/resources/jwt/public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAiuZ4N2VZ8bo95gLg/tyV 3 | p6hEaR7NnXqGsPdg7iWVEnHLMEMEpxKKSRqies2xgqJYK+vqdXF5qmIc9arMsKQQ 4 | wHW8U3uMUtfAE5XMjgX0eUv4MrZOexJViUxgHpWo214J3tq/+hXuuoFnz514q18d 5 | 413FW3l674+S7ISVjvrYQeI10IHfhXPG8YjXTtZl96ZeNN7Kfdn+twn/RyuBNaOE 6 | shvRhklk46BPFJYUQvh6W/KpA5c9csNj0QwJDqquwugM38GfSUaOYki9nBz+cYJW 7 | TwsA7ZjF53my606Ml1iUspkpypVSWOd9MAUjGOysLOQL//kpkIAhpKAHkKA0qTwa 8 | HtkrCyZLghTxHq6i89oMZFOdl2AAxz7AbB2xGPexitYc1gchGTnsKVvkMqe/IdNh 9 | I8T6YJAwVZ8kp2CmyMblg7+4XQORedSnY/Hdi19ljaBkkiUDOlsMcsY+2Da07O5G 10 | ojpVJLsjnlE4KjHNi9vdSOENS2Qlnuty2X/NziWDAzrnhsINMaOEuwwWco+8HYrv 11 | lqichDEK2colbDpxWNBqBpTHF4p+jtBVzIomXFTm7r5PRaPrVcP8yMF5TWs9jwUq 12 | EqjWnErcnmNb9F3AeocFi8DTd6x6YxXA9G8YMdx1JEKiggIPu587viYxiFEgSmC+ 13 | DSHH49SuJzUozyddXZG0A5kCAwEAAQ== 14 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /common/api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | api 7 | ${common.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | common 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | jakarta.validation 19 | jakarta.validation-api 20 | 3.0.2 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /common/swagger/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | swagger 7 | ${common.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | common 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | org.springdoc 19 | springdoc-openapi-starter-webmvc-ui 20 | 2.2.0 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /user/user-client/src/main/java/pro/teamlead/kubepay/user/client/configuration/UserClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.client.configuration; 2 | 3 | import pro.teamlead.kubepay.common.client.configuration.CommonFeignClientConfiguration; 4 | import pro.teamlead.kubepay.user.client.component.UserClientErrorDecoder; 5 | import pro.teamlead.kubepay.user.client.component.UserClientRequestInterceptor; 6 | import feign.RequestInterceptor; 7 | import feign.codec.ErrorDecoder; 8 | import org.springframework.context.annotation.Bean; 9 | 10 | public class UserClientConfiguration extends CommonFeignClientConfiguration { 11 | 12 | @Bean 13 | @Override 14 | public ErrorDecoder errorDecoder() { 15 | return new UserClientErrorDecoder(); 16 | } 17 | 18 | @Bean 19 | @Override 20 | public RequestInterceptor requestInterceptor() { 21 | return new UserClientRequestInterceptor(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /wallet/wallet-client/src/main/java/pro/teamlead/kubepay/wallet/client/configuration/WalletClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.client.configuration; 2 | 3 | import pro.teamlead.kubepay.common.client.configuration.CommonFeignClientConfiguration; 4 | import pro.teamlead.kubepay.wallet.client.component.WalletClientErrorDecoder; 5 | import pro.teamlead.kubepay.wallet.client.component.WalletClientRequestInterceptor; 6 | import feign.codec.ErrorDecoder; 7 | import feign.RequestInterceptor; 8 | import org.springframework.context.annotation.Bean; 9 | 10 | public class WalletClientConfiguration extends CommonFeignClientConfiguration { 11 | 12 | @Bean 13 | @Override 14 | public ErrorDecoder errorDecoder() { 15 | return new WalletClientErrorDecoder(); 16 | } 17 | 18 | @Bean 19 | @Override 20 | public RequestInterceptor requestInterceptor() { 21 | return new WalletClientRequestInterceptor(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/resources/git.properties: -------------------------------------------------------------------------------- 1 | # git.properties 2 | git.tags=${git.tags} 3 | git.branch=${git.branch} 4 | git.dirty=${git.dirty} 5 | git.remote.origin.url=${git.remote.origin.url} 6 | git.commit.id=${git.commit.id} 7 | git.commit.id.abbrev=${git.commit.id.abbrev} 8 | git.commit.id.describe=${git.commit.id.describe} 9 | git.commit.id.describe-short=${git.commit.id.describe-short} 10 | git.commit.user.name=${git.commit.user.name} 11 | git.commit.user.email=${git.commit.user.email} 12 | git.commit.message.full=${git.commit.message.full} 13 | git.commit.message.short=${git.commit.message.short} 14 | git.commit.time=${git.commit.time} 15 | git.closest.tag.name=${git.closest.tag.name} 16 | git.closest.tag.commit.count=${git.closest.tag.commit.count} 17 | git.build.user.name=${git.build.user.name} 18 | git.build.user.email=${git.build.user.email} 19 | git.build.time=${git.build.time} 20 | git.build.host=${git.build.host} 21 | git.build.version=${git.build.version} 22 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 18 | -------------------------------------------------------------------------------- /auth/auth-api/src/main/java/pro/teamlead/kubepay/auth/api/AuthApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.api; 2 | 3 | import pro.teamlead.kubepay.auth.api.domain.model.AuthRequest; 4 | import pro.teamlead.kubepay.auth.api.domain.model.AuthToken; 5 | import org.springframework.validation.annotation.Validated; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | 11 | public interface AuthApi { 12 | 13 | @PostMapping(AuthApiMethodDictionary.AUTH_LOGIN) 14 | AuthToken login(@Validated @RequestBody AuthRequest request); 15 | 16 | @PostMapping(AuthApiMethodDictionary.AUTH_SIGNUP) 17 | AuthToken signup(@Validated @RequestBody AuthRequest request); 18 | 19 | @GetMapping(AuthApiMethodDictionary.SERVICE_TOKEN) 20 | AuthToken serviceToken(@PathVariable("key") String key); 21 | } 22 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | common 7 | 0.0.0 8 | pom 9 | 10 | 11 | pro.teamlead.kubepay 12 | root 13 | 0.0.0 14 | ../pom.xml 15 | 16 | 17 | 18 | api 19 | util 20 | swagger 21 | service 22 | json 23 | testing 24 | metrics 25 | client 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /user/user-service/src/main/java/pro/teamlead/kubepay/user/api/UserPublicApiController.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.api; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipal; 4 | import pro.teamlead.kubepay.auth.sdk.user.UserPrincipal; 5 | import pro.teamlead.kubepay.auth.sdk.controller.PublicApiController; 6 | import pro.teamlead.kubepay.user.api.domain.model.UserInfo; 7 | import pro.teamlead.kubepay.user.application.UserApplicationService; 8 | import io.micrometer.observation.annotation.Observed; 9 | import lombok.RequiredArgsConstructor; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | 13 | @Slf4j 14 | @RequiredArgsConstructor 15 | @PublicApiController 16 | @Observed 17 | public class UserPublicApiController implements UserPublicApi { 18 | 19 | private final UserApplicationService userApplicationService; 20 | 21 | @Override 22 | public UserInfo getMyInfo(@JwtPrincipal UserPrincipal user) { 23 | return userApplicationService.getUserInfo(user); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/java/pro/teamlead/kubepay/wallet/SwaggerApiDocsTest.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.test.web.servlet.result.MockMvcResultHandlers; 6 | import pro.teamlead.kubepay.common.testing.integration.test.ApiControllerIntegrationTest; 7 | 8 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 9 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 10 | 11 | class SwaggerApiDocsTest extends ApiControllerIntegrationTest { 12 | 13 | @Value("${springdoc.api-docs.path}") 14 | private String restApiDocPath; 15 | 16 | @Test 17 | void whenSwaggerDocsRequested_thenReturnSuccess() throws Exception { 18 | 19 | mockMvc.perform(get(restApiDocPath)) 20 | .andExpect(status().isOk()) 21 | .andDo(MockMvcResultHandlers.print()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /user/user-api/src/main/java/pro/teamlead/kubepay/user/api/domain/exception/UserApiException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.api.domain.exception; 2 | 3 | import com.fasterxml.jackson.annotation.JsonSubTypes; 4 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 5 | import pro.teamlead.kubepay.common.api.domain.exception.ApiException; 6 | 7 | import java.io.Serializable; 8 | 9 | 10 | @JsonTypeInfo( 11 | use = JsonTypeInfo.Id.NAME, 12 | property = "type", 13 | include = JsonTypeInfo.As.EXISTING_PROPERTY 14 | ) 15 | @JsonSubTypes({ 16 | @JsonSubTypes.Type( 17 | name = UserNotFoundException.TYPE, 18 | value = UserNotFoundException.class 19 | ), 20 | }) 21 | public abstract class UserApiException extends ApiException implements Serializable { 22 | 23 | protected UserApiException(String message) { 24 | super(message); 25 | } 26 | 27 | @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) 28 | public abstract String getType(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /user/user-service/src/main/java/pro/teamlead/kubepay/user/domain/model/User.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.domain.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Getter; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | @Getter 9 | public class User { 10 | 11 | @SuppressWarnings("squid:S1700") 12 | private final String user; 13 | private final String name; 14 | private volatile Boolean enabled; 15 | private final String passwordHash; 16 | 17 | @JsonCreator 18 | public User(@NotNull @JsonProperty("user") String user, 19 | @NotNull @JsonProperty("name") String name, 20 | @NotNull @JsonProperty("enabled") Boolean enabled, 21 | @NotNull @JsonProperty("passwordHash") String passwordHash) { 22 | this.user = user; 23 | this.name = name; 24 | this.enabled = enabled; 25 | this.passwordHash = passwordHash; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/WalletServiceApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.api.ServiceApi; 4 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipal; 5 | import pro.teamlead.kubepay.auth.sdk.user.ServicePrincipal; 6 | import pro.teamlead.kubepay.wallet.api.domain.exception.WalletAlreadyCreatedException; 7 | import pro.teamlead.kubepay.wallet.api.domain.model.CreateWalletRequest; 8 | import pro.teamlead.kubepay.wallet.api.domain.model.WalletInfo; 9 | import org.springframework.validation.annotation.Validated; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | 13 | @ServiceApi 14 | public interface WalletServiceApi { 15 | 16 | @PostMapping(WalletApiMethodDictionary.WALLET_CREATE) 17 | WalletInfo createWallet(@JwtPrincipal ServicePrincipal user, 18 | @Validated @RequestBody CreateWalletRequest request) throws WalletAlreadyCreatedException; 19 | } 20 | -------------------------------------------------------------------------------- /common/testing/src/main/java/pro/teamlead/kubepay/common/testing/integration/annotation/WireMockEnabled.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.testing.integration.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import pro.teamlead.kubepay.common.testing.integration.configuration.WireMockTestConfiguration; 10 | import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; 11 | import org.springframework.context.annotation.Import; 12 | 13 | import static pro.teamlead.kubepay.common.testing.integration.configuration.Constants.ENV_PROPERTY_MAPPING_WIREMOCK; 14 | 15 | @Documented 16 | @Target({ElementType.TYPE, ElementType.METHOD }) 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @PropertyMapping(ENV_PROPERTY_MAPPING_WIREMOCK) 19 | @Import(WireMockTestConfiguration.class) 20 | public @interface WireMockEnabled { 21 | boolean enabled() default true; 22 | } 23 | -------------------------------------------------------------------------------- /auth/auth-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | auth-client 7 | ${auth.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | auth 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | pro.teamlead.kubepay 19 | auth-api 20 | ${auth.libs.version} 21 | 22 | 23 | pro.teamlead.kubepay 24 | client 25 | ${common.libs.version} 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /user/user-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | user-client 7 | ${user.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | user 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | pro.teamlead.kubepay 19 | user-api 20 | ${user.libs.version} 21 | 22 | 23 | pro.teamlead.kubepay 24 | client 25 | ${common.libs.version} 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/java/pro/teamlead/kubepay/gateway/filters/RateLimiterConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.gateway.filters; 2 | 3 | import pro.teamlead.kubepay.gateway.component.SimpleInMemoryRateLimiter; 4 | import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; 5 | import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.http.HttpHeaders; 9 | import reactor.core.publisher.Mono; 10 | 11 | import java.util.Optional; 12 | 13 | @Configuration 14 | public class RateLimiterConfiguration { 15 | 16 | @Bean("customRateLimiter") 17 | RateLimiter customRateLimiter() { 18 | return new SimpleInMemoryRateLimiter(); 19 | } 20 | 21 | @Bean("customKeyResolver") 22 | KeyResolver customKeyResolver() { 23 | return exchange -> Mono.just( 24 | Optional.ofNullable(exchange.getRequest().getHeaders().getFirst(HttpHeaders.USER_AGENT)) 25 | .orElse("default") 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /wallet/wallet-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | wallet-client 7 | ${wallet.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | wallet 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | pro.teamlead.kubepay 19 | wallet-api 20 | ${wallet.libs.version} 21 | 22 | 23 | pro.teamlead.kubepay 24 | client 25 | ${common.libs.version} 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /auth/auth-api/src/main/java/pro/teamlead/kubepay/auth/api/domain/exception/AuthApiException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.api.domain.exception; 2 | 3 | import com.fasterxml.jackson.annotation.JsonSubTypes; 4 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 5 | import pro.teamlead.kubepay.common.api.domain.exception.ApiException; 6 | 7 | import java.io.Serializable; 8 | 9 | @JsonTypeInfo( 10 | use = JsonTypeInfo.Id.NAME, 11 | property = "type", 12 | include = JsonTypeInfo.As.EXISTING_PROPERTY 13 | ) 14 | @JsonSubTypes({ 15 | @JsonSubTypes.Type( 16 | name = InvalidCredentialsException.TYPE, 17 | value = InvalidCredentialsException.class 18 | ), 19 | @JsonSubTypes.Type( 20 | name = UserAlreadyExistsException.TYPE, 21 | value = UserAlreadyExistsException.class 22 | ), 23 | }) 24 | public abstract class AuthApiException extends ApiException implements Serializable { 25 | 26 | protected AuthApiException(String message) { 27 | super(message); 28 | } 29 | 30 | @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) 31 | public abstract String getType(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /user/user-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: user 4 | description: User Service API 5 | security: 6 | oauth2: 7 | resourceserver: 8 | jwt: 9 | public-key-location: classpath:jwt/public.pem 10 | 11 | management: 12 | endpoints: 13 | web: 14 | exposure: 15 | include: 16 | - health 17 | base-path: /actuator 18 | server: 19 | port: ${ACTUATOR_PORT:8202} 20 | tracing: 21 | sampling: 22 | probability: 1 23 | enabled: ${TRACING:false} 24 | zipkin: 25 | tracing: 26 | endpoint: ${ZIPKIN_ENDPOINT:http://localhost:9411/api/v2/spans} 27 | 28 | springdoc: 29 | api-docs: 30 | enabled: true 31 | path: /api-docs 32 | swagger-ui: 33 | path: /swagger-ui 34 | enabled: true 35 | 36 | server: 37 | port: ${SERVER_PORT:8102} 38 | standalone: ${STANDALONE:true} 39 | 40 | service: 41 | key: user_service_secret 42 | 43 | wallet: 44 | url: ${WALLET_URL:http://localhost:8103} 45 | 46 | auth: 47 | url: ${AUTH_URL:http://localhost:8101} 48 | 49 | logging.level: 50 | org.zalando.logbook: trace 51 | 52 | logbook: 53 | format: 54 | style: http 55 | -------------------------------------------------------------------------------- /.github/workflows/build-and-analyze.yml: -------------------------------------------------------------------------------- 1 | name: Java CI with Maven and SonarCloud 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | types: [opened, synchronize, reopened] 9 | 10 | jobs: 11 | build-and-analyze: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 # Necessary for a comprehensive SonarCloud analysis 18 | 19 | - name: Set up JDK 17 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: '17' 23 | distribution: 'zulu' 24 | 25 | - name: Cache Maven and SonarCloud packages 26 | uses: actions/cache@v3 27 | with: 28 | path: | 29 | ~/.m2 30 | ~/.sonar/cache 31 | key: ${{ runner.os }}-m2-sonar-${{ hashFiles('**/pom.xml') }} 32 | 33 | - name: Build and analyze with Maven 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 37 | run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=teamlead_spring-microservices-kubernetes-demo 38 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/configuration/ServicePrincipalConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.configuration; 2 | 3 | import pro.teamlead.kubepay.auth.component.ServiceTokenProvider; 4 | import pro.teamlead.kubepay.auth.sdk.user.ServicePrincipal; 5 | import lombok.AllArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Scope; 11 | import org.springframework.context.annotation.ScopedProxyMode; 12 | 13 | @Slf4j 14 | @Configuration 15 | @AllArgsConstructor 16 | public class ServicePrincipalConfiguration { 17 | 18 | private final ServiceTokenProvider serviceTokenProvider; 19 | 20 | @Bean 21 | @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) 22 | public ServicePrincipal servicePrincipal(@Value("${spring.application.name}") final String service) { 23 | 24 | var token = serviceTokenProvider.createSelfServiceToken(); 25 | 26 | return new ServicePrincipal(service, token.token()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/user/JwtPrincipalExtractor.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.user; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.authority.Authority; 4 | import org.springframework.security.access.AccessDeniedException; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.context.SecurityContextHolder; 7 | import org.springframework.security.oauth2.jwt.Jwt; 8 | 9 | public class JwtPrincipalExtractor { 10 | 11 | public AuthPrincipal getPrincipal(final Jwt jwt) { 12 | 13 | var authorities = SecurityContextHolder 14 | .getContext() 15 | .getAuthentication().getAuthorities(); 16 | 17 | for (GrantedAuthority authority : authorities) { 18 | if (Authority.ROLE_SERVICE.equals(authority)) { 19 | return new ServicePrincipal(jwt.getSubject(), jwt.getTokenValue()); 20 | } 21 | if (Authority.ROLE_USER.equals(authority)) { 22 | return new UserPrincipal(jwt.getSubject(), jwt.getTokenValue()); 23 | } 24 | } 25 | 26 | throw new AccessDeniedException("Unknown JWT Scope"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bin/run-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the directory of the current script 4 | SCRIPT_DIR="$(pwd)" 5 | 6 | modules=("$SCRIPT_DIR/gateway/gateway-service" "$SCRIPT_DIR/auth/auth-service" "$SCRIPT_DIR/user/user-service" "$SCRIPT_DIR/wallet/wallet-service") 7 | pids=() 8 | 9 | # Function to stop services when script is terminated 10 | stop_services() { 11 | for pid in "${pids[@]}" 12 | do 13 | if kill -0 $pid 2>/dev/null; then 14 | echo "Stopping service with PID $pid..." 15 | kill $pid 16 | fi 17 | done 18 | echo "Stopped" 19 | exit 0 20 | } 21 | 22 | # Trap various signals and call stop_services() when they occur 23 | trap stop_services SIGINT SIGTERM SIGQUIT 24 | 25 | for module in "${modules[@]}" 26 | do 27 | echo "Starting $module..." 28 | if cd "$module"; then 29 | "$SCRIPT_DIR/mvnw" spring-boot:run & 30 | sleep 1 # Give the process a moment to start 31 | pids+=($!) 32 | else 33 | echo "Failed to navigate to $module directory. Skipping..." 34 | fi 35 | done 36 | 37 | echo "All services started! Press Ctrl+C to stop." 38 | 39 | # Wait forever, until a signal is caught 40 | while true; do 41 | sleep 1 42 | done 43 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/WalletPublicApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.api.PublicApi; 4 | import pro.teamlead.kubepay.wallet.api.domain.model.TransferFundsRequest; 5 | import pro.teamlead.kubepay.wallet.api.domain.model.WalletInfo; 6 | import org.springframework.validation.annotation.Validated; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipal; 9 | import pro.teamlead.kubepay.auth.sdk.user.UserPrincipal; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | 13 | import static pro.teamlead.kubepay.wallet.api.WalletApiMethodDictionary.*; 14 | 15 | @PublicApi 16 | public interface WalletPublicApi { 17 | 18 | @GetMapping(WALLET_INFO) 19 | WalletInfo getInfo(@JwtPrincipal UserPrincipal user); 20 | 21 | @GetMapping(WALLET_TOPUP) 22 | WalletInfo topUp(@JwtPrincipal UserPrincipal user); 23 | 24 | @PostMapping(WALLET_TRANSFER_FUNDS) 25 | WalletInfo transferFunds(@JwtPrincipal UserPrincipal user, 26 | @Validated @RequestBody TransferFundsRequest request); 27 | } 28 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: wallet 4 | description: Wallet Service API 5 | security: 6 | oauth2: 7 | resourceserver: 8 | jwt: 9 | public-key-location: classpath:jwt/public.pem 10 | 11 | management: 12 | endpoints: 13 | web: 14 | exposure: 15 | include: 16 | - health 17 | base-path: /actuator 18 | server: 19 | port: ${ACTUATOR_PORT:8203} 20 | tracing: 21 | sampling: 22 | probability: 1 23 | enabled: ${TRACING:false} 24 | zipkin: 25 | tracing: 26 | endpoint: ${ZIPKIN_ENDPOINT:http://localhost:9411/api/v2/spans} 27 | 28 | springdoc: 29 | api-docs: 30 | enabled: true 31 | path: /api-docs 32 | swagger-ui: 33 | path: /swagger-ui 34 | enabled: true 35 | 36 | server: 37 | port: ${SERVER_PORT:8103} 38 | standalone: ${STANDALONE:true} 39 | 40 | service: 41 | key: wallet_service_secret 42 | 43 | auth: 44 | url: ${AUTH_URL:http://localhost:8101} 45 | 46 | user: 47 | url: ${USER_URL:http://localhost:8102} 48 | 49 | billing: 50 | url: ${BILLING_URL:http://localhost:8100/billing/test} 51 | 52 | logging.level: 53 | org.zalando.logbook: trace 54 | 55 | logbook: 56 | format: 57 | style: http 58 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: auth 4 | description: Authorization Service API 5 | 6 | security: 7 | customServicePrincipal: true 8 | customFilterChain: true 9 | 10 | management: 11 | endpoints: 12 | web: 13 | exposure: 14 | include: 15 | - health 16 | base-path: /actuator 17 | server: 18 | port: ${ACTUATOR_PORT:8201} 19 | tracing: 20 | sampling: 21 | probability: 1 22 | enabled: ${TRACING:false} 23 | zipkin: 24 | tracing: 25 | endpoint: ${ZIPKIN_ENDPOINT:http://localhost:9411/api/v2/spans} 26 | 27 | springdoc: 28 | api-docs: 29 | enabled: true 30 | path: /api-docs 31 | swagger-ui: 32 | path: /swagger-ui 33 | enabled: true 34 | 35 | server: 36 | port: ${SERVER_PORT:8101} 37 | standalone: ${STANDALONE:true} 38 | 39 | services: '{"user": "user_service_secret", "wallet": "wallet_service_secret"}' 40 | scopes: '{"user": "ROLE_SERVICE,wallet:create", "wallet": "ROLE_SERVICE,user:info", "auth": "ROLE_SERVICE,user:password,user:info,user:create"}' 41 | 42 | jwt: 43 | ttl: 3600 44 | 45 | user: 46 | url: ${USER_URL:http://localhost:8102} 47 | 48 | logging.level: 49 | org.zalando.logbook: trace 50 | 51 | logbook: 52 | format: 53 | style: http 54 | -------------------------------------------------------------------------------- /auth/auth-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | auth-api 7 | ${auth.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | auth 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | pro.teamlead.kubepay 19 | api 20 | ${common.libs.version} 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-validation 25 | 26 | 27 | pro.teamlead.kubepay 28 | auth-sdk 29 | ${auth.libs.version} 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /user/user-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | user-api 7 | ${user.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | user 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | pro.teamlead.kubepay 19 | api 20 | ${common.libs.version} 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-validation 25 | 26 | 27 | pro.teamlead.kubepay 28 | auth-sdk 29 | ${auth.libs.version} 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /wallet/wallet-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | wallet-api 7 | ${wallet.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | wallet 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | pro.teamlead.kubepay 19 | api 20 | ${common.libs.version} 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-validation 25 | 26 | 27 | pro.teamlead.kubepay 28 | auth-sdk 29 | ${auth.libs.version} 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/authority/Authority.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.authority; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.security.core.GrantedAuthority; 7 | 8 | import java.util.Objects; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class Authority implements GrantedAuthority { 14 | 15 | public static final Authority ROLE_SERVICE = new Authority("ROLE_SERVICE"); 16 | public static final Authority ROLE_USER = new Authority("ROLE_USER"); 17 | public static final Authority AUTHORITY_INTROSPECT_USER = new Authority("user:info"); 18 | public static final Authority AUTHORITY_USER_CREATE = new Authority("user:create"); 19 | public static final Authority AUTHORITY_VIEW_USER_PASSWORD = new Authority("user:password"); 20 | public static final Authority AUTHORITY_WALLET_CREATE = new Authority("wallet:create"); 21 | 22 | @SuppressWarnings("squid:S1700") 23 | private String authority; 24 | 25 | @SuppressWarnings("squid:S1201") 26 | public boolean equals(GrantedAuthority o) { 27 | if (o == null) return false; 28 | if (this == o) return true; 29 | return Objects.equals(authority, o.getAuthority()); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/feign/JwtPrincipalFeignContract.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.feign; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipal; 4 | import feign.MethodMetadata; 5 | import org.springframework.cloud.openfeign.support.SpringMvcContract; 6 | import org.springframework.http.HttpHeaders; 7 | 8 | import java.lang.annotation.Annotation; 9 | import java.util.List; 10 | 11 | public class JwtPrincipalFeignContract extends SpringMvcContract { 12 | 13 | @Override 14 | protected boolean processAnnotationsOnParameter(MethodMetadata data, 15 | Annotation[] annotations, 16 | int paramIndex) { 17 | 18 | for (Annotation annotation: annotations) { 19 | 20 | if (annotation instanceof JwtPrincipal) { 21 | String name = HttpHeaders.AUTHORIZATION; 22 | nameParam(data, name, paramIndex); 23 | data.template().header(name, List.of(String.format("{%s}", name))); 24 | data.indexToExpander().put(paramIndex, new UserPrincipalTokenExpander()); 25 | return true; 26 | } 27 | } 28 | 29 | return super.processAnnotationsOnParameter(data, annotations, paramIndex); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /user/user-service/src/main/java/pro/teamlead/kubepay/user/repository/impl/InMemoryUserRepository.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.repository.impl; 2 | 3 | import pro.teamlead.kubepay.user.domain.model.User; 4 | import pro.teamlead.kubepay.user.repository.UserRepository; 5 | import io.micrometer.observation.annotation.Observed; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import javax.sql.DataSource; 11 | import java.util.Map; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * This repository implementation is intended solely for testing purposes. 16 | * It is NOT production-ready and should not be used in a live environment. 17 | * Ensure that a production-ready UserRepository implementation is used in actual deployments. 18 | */ 19 | @Repository 20 | @Observed 21 | @ConditionalOnMissingBean(DataSource.class) 22 | public class InMemoryUserRepository implements UserRepository { 23 | 24 | private final Map users = new ConcurrentHashMap<>(); 25 | 26 | public User findByUser(@NotNull String user) { 27 | return users.get(user); 28 | } 29 | 30 | public User save(@NotNull User user) { 31 | users.put(user.getUser(), user); 32 | return user; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/api/AuthApiController.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.api; 2 | 3 | import pro.teamlead.kubepay.auth.api.domain.model.AuthRequest; 4 | import pro.teamlead.kubepay.auth.api.domain.model.AuthToken; 5 | import pro.teamlead.kubepay.auth.application.AuthApplicationService; 6 | import pro.teamlead.kubepay.auth.sdk.controller.UnprotectedApiController; 7 | import io.micrometer.observation.annotation.Observed; 8 | import lombok.RequiredArgsConstructor; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.validation.annotation.Validated; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | 13 | 14 | @Slf4j 15 | @RequiredArgsConstructor 16 | @UnprotectedApiController 17 | @Observed 18 | public class AuthApiController implements AuthApi { 19 | 20 | private final AuthApplicationService authApplicationService; 21 | 22 | @Override 23 | public AuthToken login(@Validated @RequestBody AuthRequest request) { 24 | return authApplicationService.handleLogin(request); 25 | } 26 | 27 | @Override 28 | public AuthToken signup(@Validated @RequestBody AuthRequest request) { 29 | return authApplicationService.handleSignup(request); 30 | } 31 | 32 | @Override 33 | public AuthToken serviceToken(String key) { 34 | return authApplicationService.handleService(key); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/api/WalletServiceApiController.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.authority.CanCreateWallet; 4 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipal; 5 | import pro.teamlead.kubepay.auth.sdk.user.ServicePrincipal; 6 | import pro.teamlead.kubepay.auth.sdk.controller.ServiceApiController; 7 | import pro.teamlead.kubepay.wallet.api.domain.model.CreateWalletRequest; 8 | import pro.teamlead.kubepay.wallet.api.domain.model.WalletInfo; 9 | import pro.teamlead.kubepay.wallet.application.WalletApplicationService; 10 | import io.micrometer.observation.annotation.Observed; 11 | import lombok.RequiredArgsConstructor; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.validation.annotation.Validated; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | 16 | 17 | @Slf4j 18 | @RequiredArgsConstructor 19 | @ServiceApiController 20 | @Observed 21 | public class WalletServiceApiController implements WalletServiceApi { 22 | 23 | private final WalletApplicationService walletApplicationService; 24 | 25 | @Override 26 | @CanCreateWallet 27 | public WalletInfo createWallet(@JwtPrincipal ServicePrincipal principal, 28 | @Validated @RequestBody CreateWalletRequest request) { 29 | return walletApplicationService.createWallet(request); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/domain/model/Wallet.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.domain.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import pro.teamlead.kubepay.wallet.api.domain.exception.InsufficientFundsException; 6 | import lombok.Getter; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.math.BigDecimal; 10 | 11 | @Getter 12 | public class Wallet { 13 | 14 | private final String address; 15 | private volatile BigDecimal balance; 16 | private final String user; 17 | 18 | @JsonCreator 19 | public Wallet(@NotNull @JsonProperty("address") String address, 20 | @NotNull @JsonProperty("balance") BigDecimal balance, 21 | @NotNull @JsonProperty("user") String user) { 22 | this.address = address; 23 | this.balance = balance; 24 | this.user = user; 25 | } 26 | 27 | public synchronized void topUp(@NotNull BigDecimal amount) { 28 | this.balance = this.balance.add(amount); 29 | } 30 | 31 | public synchronized void transferFunds(@NotNull Wallet toWallet, 32 | @NotNull BigDecimal amount) { 33 | if (this.balance.compareTo(amount) < 0) { 34 | throw new InsufficientFundsException(); 35 | } 36 | this.balance = this.balance.subtract(amount); 37 | toWallet.topUp(amount); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/feign/JwtPrincipalParameterProcessor.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.feign; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipal; 4 | import org.springframework.http.HttpHeaders; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.lang.reflect.Method; 8 | import java.util.Collection; 9 | 10 | import feign.MethodMetadata; 11 | import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; 12 | 13 | public class JwtPrincipalParameterProcessor implements AnnotatedParameterProcessor { 14 | 15 | private static final Class ANNOTATION = JwtPrincipal.class; 16 | 17 | public Class getAnnotationType() { 18 | return ANNOTATION; 19 | } 20 | 21 | public boolean processArgument(AnnotatedParameterProcessor.AnnotatedParameterContext context, 22 | Annotation annotation, Method method) { 23 | int parameterIndex = context.getParameterIndex(); 24 | MethodMetadata data = context.getMethodMetadata(); 25 | String name = HttpHeaders.AUTHORIZATION; 26 | context.setParameterName(name); 27 | Collection header = context.setTemplateParameter(name, (Collection) data.template().headers().get(name)); 28 | data.template().header(name, header); 29 | data.indexToExpander().put(parameterIndex, new UserPrincipalTokenExpander()); 30 | return true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /user/user-api/src/main/java/pro/teamlead/kubepay/user/api/UserServiceApi.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.api; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.api.ServiceApi; 4 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipal; 5 | import pro.teamlead.kubepay.auth.sdk.user.ServicePrincipal; 6 | import pro.teamlead.kubepay.user.api.domain.exception.UserNotFoundException; 7 | import pro.teamlead.kubepay.user.api.domain.model.CreateUserRequest; 8 | import pro.teamlead.kubepay.user.api.domain.model.UserInfo; 9 | import org.springframework.validation.annotation.Validated; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | 15 | @ServiceApi 16 | public interface UserServiceApi { 17 | 18 | @PostMapping(UserApiMethodDictionary.CREATE_USER) 19 | UserInfo createUser(@JwtPrincipal ServicePrincipal service, 20 | @Validated @RequestBody CreateUserRequest request); 21 | 22 | @GetMapping(UserApiMethodDictionary.GET_USER_INFO) 23 | UserInfo getUserInfo(@JwtPrincipal ServicePrincipal service, 24 | @PathVariable("user") String user) throws UserNotFoundException; 25 | 26 | @GetMapping(UserApiMethodDictionary.GET_PASSWORD_HASH) 27 | String getPasswordHash(@JwtPrincipal ServicePrincipal service, 28 | @PathVariable("user") String user) throws UserNotFoundException; 29 | } 30 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/configuration/SpringSecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.configuration; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.http.SessionCreationPolicy; 8 | import org.springframework.security.web.SecurityFilterChain; 9 | 10 | 11 | @Configuration 12 | public class SpringSecurityConfiguration { 13 | 14 | @Value("${springdoc.api-docs.path}") 15 | private String restApiDocPath; 16 | 17 | @Value("${springdoc.swagger-ui.path}") 18 | private String swaggerPath; 19 | 20 | @Value("${management.endpoints.web.base-path}") 21 | private String actuatorPath; 22 | 23 | @Bean 24 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 25 | // @formatter:off 26 | http 27 | .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()) 28 | .formLogin().disable() 29 | .logout().disable() 30 | .cors().disable() 31 | .csrf().disable() 32 | .httpBasic().disable() 33 | .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); 34 | // @formatter:on 35 | return http.build(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /common/json/src/main/java/pro/teamlead/kubepay/common/json/ObjectMapperBuilder.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.json; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.databind.DeserializationFeature; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.SerializationFeature; 8 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 9 | import lombok.experimental.UtilityClass; 10 | 11 | 12 | @UtilityClass 13 | public class ObjectMapperBuilder { 14 | 15 | /** 16 | * Instance 17 | */ 18 | private static final ObjectMapper INSTANCE = createInstance(); 19 | 20 | /** 21 | * 22 | * @return ObjectMapper 23 | */ 24 | public static ObjectMapper build() { 25 | return INSTANCE; 26 | } 27 | 28 | /** 29 | * 30 | * @return ObjectMapper 31 | */ 32 | private static ObjectMapper createInstance() { 33 | 34 | ObjectMapper mapper = new ObjectMapper(); 35 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 36 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 37 | mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true); 38 | mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 39 | mapper.registerModule(new JavaTimeModule()); 40 | mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 41 | return mapper; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/application/AuthApplicationService.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.application; 2 | 3 | import pro.teamlead.kubepay.auth.api.domain.model.AuthRequest; 4 | import pro.teamlead.kubepay.auth.api.domain.model.AuthToken; 5 | import pro.teamlead.kubepay.auth.domain.model.JwtToken; 6 | import pro.teamlead.kubepay.auth.domain.service.AuthService; 7 | import pro.teamlead.kubepay.auth.component.ServiceTokenProvider; 8 | import io.micrometer.observation.annotation.Observed; 9 | import lombok.AllArgsConstructor; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.springframework.stereotype.Service; 13 | 14 | @Slf4j 15 | @Service 16 | @AllArgsConstructor 17 | @Observed 18 | public class AuthApplicationService { 19 | 20 | private final AuthService authService; 21 | 22 | private final ServiceTokenProvider serviceTokenProvider; 23 | 24 | public AuthToken handleLogin(AuthRequest request) { 25 | JwtToken token = authService.authUser(request.user(), 26 | request.password()); 27 | return new AuthToken(token.token()); 28 | } 29 | 30 | public AuthToken handleService(@NotNull String key) { 31 | var token = serviceTokenProvider.getServiceToken(key); 32 | return new AuthToken(token.token()); 33 | } 34 | 35 | public AuthToken handleSignup(AuthRequest request) { 36 | JwtToken token = authService.createUser(request.user(), 37 | request.password()); 38 | return new AuthToken(token.token()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/domain/service/BillingService.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.domain.service; 2 | 3 | import pro.teamlead.kubepay.wallet.api.domain.exception.BillingNotAvailableException; 4 | import pro.teamlead.kubepay.wallet.domain.model.Wallet; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.http.HttpEntity; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | 15 | @Slf4j 16 | @Service 17 | public class BillingService { 18 | 19 | private final RestTemplate restTemplate; 20 | 21 | private final String billingUrl; 22 | 23 | public BillingService(RestTemplate template, 24 | @Value("${billing.url}") String billingUrl) { 25 | this.restTemplate = template; 26 | this.billingUrl = billingUrl; 27 | } 28 | 29 | public void execute(@NotNull Wallet wallet) { 30 | try { 31 | HttpHeaders headers = new HttpHeaders(); 32 | headers.set(HttpHeaders.USER_AGENT, wallet.getAddress()); 33 | HttpEntity httpEntity = new HttpEntity<>(null, headers); 34 | restTemplate.exchange(billingUrl, HttpMethod.GET, httpEntity, String.class); 35 | } catch (Exception e) { 36 | throw new BillingNotAvailableException(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/testing/src/main/java/pro/teamlead/kubepay/common/testing/integration/configuration/JwtConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.testing.integration.configuration; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.util.JwtTokenProvider; 4 | import com.nimbusds.jose.jwk.JWK; 5 | import com.nimbusds.jose.jwk.JWKSet; 6 | import com.nimbusds.jose.jwk.RSAKey; 7 | import com.nimbusds.jose.jwk.source.ImmutableJWKSet; 8 | import com.nimbusds.jose.jwk.source.JWKSource; 9 | import com.nimbusds.jose.proc.SecurityContext; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 12 | import org.springframework.boot.test.context.TestConfiguration; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; 15 | 16 | import java.security.interfaces.RSAPrivateKey; 17 | import java.security.interfaces.RSAPublicKey; 18 | 19 | @TestConfiguration 20 | public class JwtConfiguration { 21 | 22 | @Bean 23 | @ConditionalOnMissingBean 24 | public JwtTokenProvider jwtTokenProvider(@Value("classpath:jwt/public.pem") RSAPublicKey publicKey, 25 | @Value("classpath:jwt/private.pem") RSAPrivateKey privateKey) { 26 | JWK jwk = new RSAKey 27 | .Builder(publicKey) 28 | .privateKey(privateKey) 29 | .build(); 30 | JWKSource jwks = new ImmutableJWKSet<>(new JWKSet(jwk)); 31 | 32 | return new JwtTokenProvider(new NimbusJwtEncoder(jwks)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /common/testing/src/main/java/pro/teamlead/kubepay/common/testing/integration/annotation/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.testing.integration.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import pro.teamlead.kubepay.common.testing.integration.configuration.Constants; 10 | import pro.teamlead.kubepay.common.testing.integration.configuration.JwtConfiguration; 11 | import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; 12 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.context.annotation.Import; 15 | import org.springframework.core.annotation.AliasFor; 16 | import org.springframework.test.context.ActiveProfiles; 17 | 18 | import static pro.teamlead.kubepay.common.testing.integration.configuration.Constants.ENV_PROPERTY_MAPPING_ROOT; 19 | 20 | @WireMockEnabled 21 | @ActiveProfiles(Constants.SPRING_TEST_PROFILE_NAME) 22 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 23 | @AutoConfigureMockMvc 24 | @Documented 25 | @Target(ElementType.TYPE) 26 | @Retention(RetentionPolicy.RUNTIME) 27 | @PropertyMapping(ENV_PROPERTY_MAPPING_ROOT) 28 | @Import(JwtConfiguration.class) 29 | public @interface IntegrationTest { 30 | 31 | @AliasFor(annotation = WireMockEnabled.class, attribute = "enabled") 32 | boolean wireMockEnable() default false; 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/repository/impl/InMemoryWalletRepository.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.repository.impl; 2 | 3 | import pro.teamlead.kubepay.wallet.domain.model.Wallet; 4 | import pro.teamlead.kubepay.wallet.repository.WalletRepository; 5 | import io.micrometer.observation.annotation.Observed; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import javax.sql.DataSource; 11 | import java.util.Map; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * This repository implementation is intended solely for testing purposes. 16 | * It is NOT production-ready and should not be used in a live environment. 17 | * Ensure that a production-ready WalletRepository implementation is used in actual deployments. 18 | */ 19 | @Repository 20 | @Observed 21 | @ConditionalOnMissingBean(DataSource.class) 22 | public class InMemoryWalletRepository implements WalletRepository { 23 | 24 | private final Map wallets = new ConcurrentHashMap<>(); 25 | 26 | public Wallet findByAddress(@NotNull String address) { 27 | for (Wallet wallet : wallets.values()) { 28 | if (wallet.getAddress().equals(address)) { 29 | return wallet; 30 | } 31 | } 32 | return null; 33 | } 34 | 35 | public Wallet findByUser(@NotNull String user) { 36 | return wallets.get(user); 37 | } 38 | 39 | public Wallet save(@NotNull Wallet wallet) { 40 | wallets.put(wallet.getUser(), wallet); 41 | return wallet; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /wallet/wallet-api/src/main/java/pro/teamlead/kubepay/wallet/api/domain/exception/WalletApiException.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api.domain.exception; 2 | 3 | import com.fasterxml.jackson.annotation.JsonSubTypes; 4 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 5 | import pro.teamlead.kubepay.common.api.domain.exception.ApiException; 6 | 7 | import java.io.Serializable; 8 | 9 | @JsonTypeInfo( 10 | use = JsonTypeInfo.Id.NAME, 11 | property = "type", 12 | include = JsonTypeInfo.As.EXISTING_PROPERTY 13 | ) 14 | @JsonSubTypes({ 15 | @JsonSubTypes.Type( 16 | name = WalletAlreadyCreatedException.TYPE, 17 | value = WalletAlreadyCreatedException.class 18 | ), 19 | @JsonSubTypes.Type( 20 | name = BillingNotAvailableException.TYPE, 21 | value = BillingNotAvailableException.class 22 | ), 23 | @JsonSubTypes.Type( 24 | name = WalletNotFoundException.TYPE, 25 | value = WalletNotFoundException.class 26 | ), 27 | @JsonSubTypes.Type( 28 | name = InvalidRecipientException.TYPE, 29 | value = InvalidRecipientException.class 30 | ), 31 | @JsonSubTypes.Type( 32 | name = InsufficientFundsException.TYPE, 33 | value = InsufficientFundsException.class 34 | ), 35 | }) 36 | public abstract class WalletApiException extends ApiException implements Serializable { 37 | 38 | protected WalletApiException(String message) { 39 | super(message); 40 | } 41 | 42 | @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) 43 | public abstract String getType(); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /common/testing/src/main/java/pro/teamlead/kubepay/common/testing/integration/configuration/WireMockTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.testing.integration.configuration; 2 | 3 | import com.github.tomakehurst.wiremock.WireMockServer; 4 | import com.github.tomakehurst.wiremock.common.Slf4jNotifier; 5 | import com.github.tomakehurst.wiremock.core.WireMockConfiguration; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.test.context.TestConfiguration; 9 | import org.springframework.context.annotation.Bean; 10 | 11 | import static pro.teamlead.kubepay.common.testing.integration.configuration.Constants.ENV_PROPERTY_IS_WIREMOCK_ENABLED; 12 | 13 | @Slf4j 14 | @TestConfiguration 15 | @ConditionalOnProperty(ENV_PROPERTY_IS_WIREMOCK_ENABLED) 16 | public class WireMockTestConfiguration { 17 | 18 | @SuppressWarnings("squid:S3077") 19 | private static volatile WireMockServer mockServer; 20 | 21 | public static synchronized WireMockServer getMockServer() { 22 | if (mockServer != null) { 23 | if (!mockServer.isRunning()) { 24 | mockServer = null; 25 | } else { 26 | return mockServer; 27 | } 28 | } 29 | mockServer = new WireMockServer( 30 | WireMockConfiguration.options() 31 | .dynamicPort() 32 | .notifier(new Slf4jNotifier(true)) 33 | ); 34 | mockServer.start(); 35 | 36 | return mockServer; 37 | } 38 | 39 | @Bean 40 | public WireMockServer wireMockServer() { 41 | return getMockServer(); 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /auth/auth-sdk/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | auth-sdk 7 | ${auth.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | auth 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | org.springframework.security 19 | spring-security-oauth2-jose 20 | 21 | 22 | com.nimbusds 23 | nimbus-jose-jwt 24 | 9.31 25 | 26 | 27 | org.springframework.cloud 28 | spring-cloud-starter-openfeign 29 | provided 30 | 31 | 32 | pro.teamlead.kubepay 33 | json 34 | ${common.libs.version} 35 | 36 | 37 | io.swagger.core.v3 38 | swagger-annotations 39 | ${swagger.version} 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /auth/auth-client/src/main/java/pro/teamlead/kubepay/auth/client/configuration/ServicePrincipalConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.client.configuration; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | import pro.teamlead.kubepay.auth.client.AuthClient; 5 | import pro.teamlead.kubepay.auth.sdk.user.ServicePrincipal; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Scope; 11 | import org.springframework.context.annotation.ScopedProxyMode; 12 | 13 | 14 | @Configuration 15 | @RequiredArgsConstructor 16 | @ConditionalOnProperty(prefix = "security", name = "customServicePrincipal", havingValue = "false", matchIfMissing = true) 17 | public class ServicePrincipalConfiguration { 18 | 19 | private final AuthClient authClient; 20 | 21 | /** 22 | * WARNING: This implementation is CONCEPTUAL and NOT production-ready. 23 | * You should implement proper caching mechanisms for the token to avoid 24 | * unnecessary calls and potential rate limits. Always ensure security 25 | * and efficiency when handling tokens in a real-world scenario. 26 | */ 27 | @Bean 28 | @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) 29 | public ServicePrincipal servicePrincipal(@Value("${spring.application.name}") final String service, 30 | @Value("${service.key}") final String key) { 31 | var token = authClient.serviceToken(key); 32 | return new ServicePrincipal(service, token.token()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/java/pro/teamlead/kubepay/gateway/component/SimpleInMemoryRateLimiter.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.gateway.component; 2 | 3 | import com.google.common.cache.Cache; 4 | import com.google.common.cache.CacheBuilder; 5 | import com.google.common.util.concurrent.RateLimiter; 6 | import reactor.core.publisher.Mono; 7 | 8 | import java.util.*; 9 | import java.util.concurrent.ExecutionException; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * WARNING: This code is a basic example and may not be production-ready. 14 | * Please review, test, and adapt as necessary before using in a production environment. 15 | */ 16 | public class SimpleInMemoryRateLimiter implements org.springframework.cloud.gateway.filter.ratelimit.RateLimiter { 17 | 18 | private final Cache rateLimiterCache = CacheBuilder.newBuilder() 19 | .expireAfterAccess(1, TimeUnit.MINUTES) // Evict entries 1 minute after last access 20 | .build(); 21 | 22 | public Mono isAllowed(String routeId, String id) { 23 | try { 24 | RateLimiter limiter = rateLimiterCache.get(id, () -> RateLimiter.create(1.0/60)); 25 | return Mono.just(new Response(limiter.tryAcquire(), Collections.emptyMap())); 26 | } catch (ExecutionException e) { 27 | return Mono.just(new Response(false, Collections.emptyMap())); 28 | } 29 | } 30 | 31 | @Override 32 | public Map getConfig() { 33 | return Collections.emptyMap(); 34 | } 35 | 36 | @Override 37 | public Class getConfigClass() { 38 | return null; 39 | } 40 | 41 | @Override 42 | public Void newConfig() { 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/api/WalletPublicApiController.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.api; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipal; 4 | import pro.teamlead.kubepay.auth.sdk.user.UserPrincipal; 5 | import pro.teamlead.kubepay.auth.sdk.controller.PublicApiController; 6 | import pro.teamlead.kubepay.wallet.api.domain.model.TransferFundsRequest; 7 | import pro.teamlead.kubepay.wallet.api.domain.model.WalletInfo; 8 | import pro.teamlead.kubepay.wallet.application.WalletApplicationService; 9 | import io.micrometer.observation.annotation.Observed; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.validation.annotation.Validated; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | 15 | import java.math.BigDecimal; 16 | 17 | @Slf4j 18 | @RequiredArgsConstructor 19 | @PublicApiController 20 | @Observed 21 | public class WalletPublicApiController implements WalletPublicApi { 22 | 23 | private final WalletApplicationService walletApplicationService; 24 | 25 | @Override 26 | public WalletInfo getInfo(@JwtPrincipal UserPrincipal user) { 27 | return walletApplicationService.getInfo(user); 28 | } 29 | 30 | @Override 31 | public WalletInfo topUp(@JwtPrincipal UserPrincipal user) { 32 | walletApplicationService.topUp(user, BigDecimal.TEN.pow(2)); 33 | return getInfo(user); 34 | } 35 | 36 | @Override 37 | public WalletInfo transferFunds(@JwtPrincipal UserPrincipal user, 38 | @Validated @RequestBody TransferFundsRequest request) { 39 | walletApplicationService.transferFunds(user, request); 40 | return getInfo(user); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/client/src/main/java/pro/teamlead/kubepay/common/client/configuration/CommonFeignClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.client.configuration; 2 | 3 | import feign.Contract; 4 | import feign.Logger; 5 | import feign.RequestInterceptor; 6 | import feign.codec.ErrorDecoder; 7 | import org.springframework.context.annotation.Bean; 8 | import org.zalando.logbook.Logbook; 9 | import org.zalando.logbook.openfeign.FeignLogbookLogger; 10 | import pro.teamlead.kubepay.auth.sdk.feign.JwtPrincipalFeignContract; 11 | 12 | public abstract class CommonFeignClientConfiguration { 13 | 14 | @Bean 15 | @SuppressWarnings("squid:S125") 16 | public Contract feignContract() { 17 | 18 | // The commented-out code below is an alternative way to create a Feign Contract 19 | // using individual argument resolvers, including the JwtPrincipalParameterProcessor. 20 | // It's an alternative approach to using the JwtPrincipalFeignContract. 21 | // 22 | // List argumentResolvers = new ArrayList(); 23 | // argumentResolvers.add(new RequestHeaderParameterProcessor()); 24 | // argumentResolvers.add(new JwtPrincipalParameterProcessor()); 25 | // ... 26 | // return new SpringMvcContract(argumentResolvers); 27 | 28 | return new JwtPrincipalFeignContract(); 29 | } 30 | 31 | @Bean 32 | public Logger logger(Logbook logbook) { 33 | return new FeignLogbookLogger(logbook); 34 | } 35 | 36 | @Bean 37 | public Logger.Level loglevel() { 38 | return Logger.Level.FULL; 39 | } 40 | 41 | @Bean 42 | public abstract ErrorDecoder errorDecoder(); 43 | 44 | @Bean 45 | public abstract RequestInterceptor requestInterceptor(); 46 | } 47 | -------------------------------------------------------------------------------- /common/service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | service 7 | ${common.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | common 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | org.springframework 19 | spring-webmvc 20 | provided 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-autoconfigure 25 | provided 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-security 30 | provided 31 | 32 | 33 | org.springframework.security 34 | spring-security-oauth2-resource-server 35 | provided 36 | 37 | 38 | pro.teamlead.kubepay 39 | api 40 | ${common.libs.version} 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /common/metrics/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | metrics 7 | ${common.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | common 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | org.springframework 19 | spring-context 20 | provided 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-autoconfigure 25 | provided 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-aop 30 | 31 | 32 | io.micrometer 33 | micrometer-tracing-bridge-otel 34 | 35 | 36 | io.opentelemetry 37 | opentelemetry-exporter-zipkin 38 | 39 | 40 | io.github.openfeign 41 | feign-micrometer 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/component/ServiceTokenProvider.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.component; 2 | 3 | import pro.teamlead.kubepay.auth.api.domain.exception.InvalidCredentialsException; 4 | import pro.teamlead.kubepay.auth.domain.model.JwtToken; 5 | import pro.teamlead.kubepay.auth.sdk.util.JwtTokenProvider; 6 | import pro.teamlead.kubepay.auth.sdk.authority.Authority; 7 | import pro.teamlead.kubepay.common.util.MapUtil; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.Map; 13 | import java.util.NoSuchElementException; 14 | 15 | 16 | @Component 17 | @RequiredArgsConstructor 18 | @SuppressWarnings("squid:S6857") 19 | public class ServiceTokenProvider { 20 | 21 | private final JwtTokenProvider jwtTokenProvider; 22 | 23 | @Value("#{${services}}") 24 | private Map secrets; 25 | 26 | @Value("#{${scopes}}") 27 | private Map scopes; 28 | 29 | @Value("${spring.application.name}") 30 | private String applicationName; 31 | 32 | public JwtToken createSelfServiceToken() { 33 | var token = jwtTokenProvider.createToken(applicationName, 120, 34 | scopes.get("auth") 35 | ); 36 | return new JwtToken(token); 37 | } 38 | 39 | public JwtToken getServiceToken(String key) { 40 | 41 | try { 42 | 43 | var service = MapUtil.getKeyByValue(secrets, key); 44 | var token = jwtTokenProvider.createToken(service, 120, 45 | scopes.get(service)); 46 | return new JwtToken(token); 47 | 48 | } catch (NoSuchElementException e) { 49 | throw new InvalidCredentialsException(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/util/JwtTokenProvider.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.util; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.authority.Authority; 4 | import lombok.AllArgsConstructor; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.security.oauth2.jwt.JwtEncoder; 7 | import org.springframework.security.oauth2.jwt.JwtEncoderParameters; 8 | import org.springframework.security.oauth2.jwt.JwtClaimsSet; 9 | 10 | import java.time.Instant; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | 15 | @Component 16 | @AllArgsConstructor 17 | public class JwtTokenProvider { 18 | 19 | private final JwtEncoder jwtEncoder; 20 | 21 | public String createToken(String user, Authority... scopes) { 22 | return createToken(user, -1, scopes); 23 | } 24 | 25 | public String createToken(String user, int lifeTimeSec, Authority... scopes) { 26 | 27 | var now = Instant.now(); 28 | 29 | var builder = JwtClaimsSet.builder(); 30 | var authorities = asList(scopes); 31 | 32 | builder.claim(JwtFields.SCOPE, authorities); 33 | builder.claim(JwtFields.USER, user); 34 | builder.claim(JwtFields.ISSUED_AT, now); 35 | 36 | if (lifeTimeSec > 0) { 37 | builder.claim(JwtFields.EXPIRE_AT, now.plusSeconds(lifeTimeSec)); 38 | } 39 | 40 | JwtClaimsSet claims = builder.build(); 41 | 42 | return jwtEncoder 43 | .encode(JwtEncoderParameters.from(claims)) 44 | .getTokenValue(); 45 | } 46 | 47 | private List asList(Authority... scopes) { 48 | if (scopes == null) { 49 | return List.of(); 50 | } 51 | return Arrays.stream(scopes) 52 | .map(Authority::getAuthority).toList(); 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /common/client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | client 7 | ${common.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | common 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 18 | org.springframework.cloud 19 | spring-cloud-starter-openfeign 20 | 21 | 22 | io.github.openfeign 23 | feign-jackson 24 | 25 | 26 | org.zalando 27 | logbook-openfeign 28 | ${logbook.version} 29 | 30 | 31 | io.github.openfeign 32 | feign-micrometer 33 | 34 | 35 | pro.teamlead.kubepay 36 | json 37 | ${common.libs.version} 38 | 39 | 40 | pro.teamlead.kubepay 41 | auth-sdk 42 | ${auth.libs.version} 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /user/user-service/src/test/java/pro/teamlead/kubepay/user/UserPublicApiControllerTest.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user; 2 | 3 | import pro.teamlead.kubepay.common.testing.integration.test.ApiControllerIntegrationTest; 4 | import pro.teamlead.kubepay.common.testing.integration.testcase.TestCase; 5 | import pro.teamlead.kubepay.user.domain.model.User; 6 | import pro.teamlead.kubepay.user.repository.UserRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpHeaders; 9 | import org.springframework.test.web.servlet.result.MockMvcResultHandlers; 10 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 11 | 12 | 13 | import static pro.teamlead.kubepay.user.api.UserApiMethodDictionary.GET_MY_INFO; 14 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 16 | 17 | public class UserPublicApiControllerTest extends ApiControllerIntegrationTest { 18 | 19 | @Autowired 20 | private UserRepository userRepository; 21 | 22 | @TestCase({ 23 | "integration/public-api/get-my-info/success/user.json", 24 | "integration/public-api/get-my-info/success/response.json" 25 | }) 26 | public void whenTopUpRequested_thenWalletBalanceIncreases(User user, 27 | String response) throws Exception { 28 | 29 | userRepository.save(user); 30 | 31 | mockMvc.perform(get(GET_MY_INFO) 32 | .header(HttpHeaders.AUTHORIZATION, userToken())) 33 | .andExpect(status().isOk()) 34 | .andDo(MockMvcResultHandlers.print()) 35 | .andExpect(MockMvcResultMatchers.content().json(response)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /common/testing/src/main/java/pro/teamlead/kubepay/common/testing/integration/initializer/TestPropertiesInitializer.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.testing.integration.initializer; 2 | 3 | import com.github.tomakehurst.wiremock.WireMockServer; 4 | import java.util.Map; 5 | 6 | import pro.teamlead.kubepay.common.testing.integration.configuration.WireMockTestConfiguration; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.context.ApplicationContextInitializer; 9 | import org.springframework.context.ConfigurableApplicationContext; 10 | import org.springframework.core.env.ConfigurableEnvironment; 11 | import org.springframework.core.env.MapPropertySource; 12 | import org.springframework.core.env.MutablePropertySources; 13 | 14 | import static pro.teamlead.kubepay.common.testing.integration.configuration.Constants.ENV_PROPERTY_IS_WIREMOCK_ENABLED; 15 | import static pro.teamlead.kubepay.common.testing.integration.configuration.Constants.ENV_PROPERTY_WIREMOCK_URL; 16 | 17 | @Slf4j 18 | public class TestPropertiesInitializer implements ApplicationContextInitializer { 19 | 20 | @Override 21 | public void initialize(ConfigurableApplicationContext applicationContext) { 22 | final ConfigurableEnvironment environment = applicationContext.getEnvironment(); 23 | final MutablePropertySources propertySources = environment.getPropertySources(); 24 | 25 | if (environment.getProperty(ENV_PROPERTY_IS_WIREMOCK_ENABLED, boolean.class, false)) { 26 | WireMockServer mockServer = WireMockTestConfiguration.getMockServer(); 27 | Map map = Map.of( 28 | ENV_PROPERTY_WIREMOCK_URL, 29 | mockServer.baseUrl() 30 | ); 31 | 32 | propertySources.addFirst(new MapPropertySource("testWireMockPropertySource", map)); 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /k8s/auth-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: auth 5 | labels: 6 | app: auth 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: auth 12 | template: 13 | metadata: 14 | labels: 15 | app: auth 16 | spec: 17 | containers: 18 | - name: auth 19 | image: kubepay.auth:latest 20 | imagePullPolicy: IfNotPresent 21 | env: 22 | - name: AUTH_URL 23 | valueFrom: 24 | configMapKeyRef: 25 | key: AUTH_URL 26 | name: shared-env-config 27 | - name: USER_URL 28 | valueFrom: 29 | configMapKeyRef: 30 | key: USER_URL 31 | name: shared-env-config 32 | - name: WALLET_URL 33 | valueFrom: 34 | configMapKeyRef: 35 | key: WALLET_URL 36 | name: shared-env-config 37 | - name: BILLING_URL 38 | valueFrom: 39 | configMapKeyRef: 40 | key: BILLING_URL 41 | name: shared-env-config 42 | - name: STANDALONE 43 | valueFrom: 44 | configMapKeyRef: 45 | key: STANDALONE 46 | name: shared-env-config 47 | - name: TRACING 48 | valueFrom: 49 | configMapKeyRef: 50 | key: TRACING 51 | name: shared-env-config 52 | - name: ZIPKIN_ENDPOINT 53 | valueFrom: 54 | configMapKeyRef: 55 | key: ZIPKIN_ENDPOINT 56 | name: shared-env-config 57 | --- 58 | apiVersion: v1 59 | kind: Service 60 | metadata: 61 | name: auth 62 | spec: 63 | type: NodePort 64 | selector: 65 | app: auth 66 | ports: 67 | - name: http 68 | protocol: TCP 69 | port: 80 70 | targetPort: 8101 71 | -------------------------------------------------------------------------------- /k8s/user-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: user 5 | labels: 6 | app: user 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: user 12 | template: 13 | metadata: 14 | labels: 15 | app: user 16 | spec: 17 | containers: 18 | - name: user 19 | image: kubepay.user:latest 20 | imagePullPolicy: IfNotPresent 21 | env: 22 | - name: AUTH_URL 23 | valueFrom: 24 | configMapKeyRef: 25 | key: AUTH_URL 26 | name: shared-env-config 27 | - name: USER_URL 28 | valueFrom: 29 | configMapKeyRef: 30 | key: USER_URL 31 | name: shared-env-config 32 | - name: WALLET_URL 33 | valueFrom: 34 | configMapKeyRef: 35 | key: WALLET_URL 36 | name: shared-env-config 37 | - name: BILLING_URL 38 | valueFrom: 39 | configMapKeyRef: 40 | key: BILLING_URL 41 | name: shared-env-config 42 | - name: STANDALONE 43 | valueFrom: 44 | configMapKeyRef: 45 | key: STANDALONE 46 | name: shared-env-config 47 | - name: TRACING 48 | valueFrom: 49 | configMapKeyRef: 50 | key: TRACING 51 | name: shared-env-config 52 | - name: ZIPKIN_ENDPOINT 53 | valueFrom: 54 | configMapKeyRef: 55 | key: ZIPKIN_ENDPOINT 56 | name: shared-env-config 57 | --- 58 | apiVersion: v1 59 | kind: Service 60 | metadata: 61 | name: user 62 | spec: 63 | type: NodePort 64 | selector: 65 | app: user 66 | ports: 67 | - name: http 68 | protocol: TCP 69 | port: 80 70 | targetPort: 8102 71 | -------------------------------------------------------------------------------- /user/user-service/src/main/java/pro/teamlead/kubepay/user/api/UserServiceApiController.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.api; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.authority.CanCreateUser; 4 | import pro.teamlead.kubepay.auth.sdk.authority.CanIntroinspectUser; 5 | import pro.teamlead.kubepay.auth.sdk.authority.CanViewUserPassword; 6 | import pro.teamlead.kubepay.auth.sdk.user.JwtPrincipal; 7 | import pro.teamlead.kubepay.auth.sdk.user.ServicePrincipal; 8 | import pro.teamlead.kubepay.auth.sdk.controller.ServiceApiController; 9 | import pro.teamlead.kubepay.user.api.domain.model.CreateUserRequest; 10 | import pro.teamlead.kubepay.user.api.domain.model.UserInfo; 11 | import pro.teamlead.kubepay.user.application.UserApplicationService; 12 | import io.micrometer.observation.annotation.Observed; 13 | import lombok.RequiredArgsConstructor; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.validation.annotation.Validated; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | 18 | @Slf4j 19 | @RequiredArgsConstructor 20 | @ServiceApiController 21 | @Observed 22 | public class UserServiceApiController implements UserServiceApi { 23 | 24 | private final UserApplicationService userApplicationService; 25 | 26 | @Override 27 | @CanCreateUser 28 | public UserInfo createUser(@JwtPrincipal ServicePrincipal service, 29 | @Validated @RequestBody CreateUserRequest request) { 30 | return userApplicationService.createUser(request); 31 | } 32 | 33 | @Override 34 | @CanIntroinspectUser 35 | public UserInfo getUserInfo(ServicePrincipal service, 36 | String user) { 37 | return userApplicationService.getUserInfo(user); 38 | } 39 | 40 | @Override 41 | @CanViewUserPassword 42 | public String getPasswordHash(ServicePrincipal service, 43 | String user) { 44 | return userApplicationService.getPasswordHash(user); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /k8s/wallet-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: wallet 5 | labels: 6 | app: wallet 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: wallet 12 | template: 13 | metadata: 14 | labels: 15 | app: wallet 16 | spec: 17 | containers: 18 | - name: wallet 19 | image: kubepay.wallet:latest 20 | imagePullPolicy: IfNotPresent 21 | env: 22 | - name: AUTH_URL 23 | valueFrom: 24 | configMapKeyRef: 25 | key: AUTH_URL 26 | name: shared-env-config 27 | - name: USER_URL 28 | valueFrom: 29 | configMapKeyRef: 30 | key: USER_URL 31 | name: shared-env-config 32 | - name: WALLET_URL 33 | valueFrom: 34 | configMapKeyRef: 35 | key: WALLET_URL 36 | name: shared-env-config 37 | - name: BILLING_URL 38 | valueFrom: 39 | configMapKeyRef: 40 | key: BILLING_URL 41 | name: shared-env-config 42 | - name: STANDALONE 43 | valueFrom: 44 | configMapKeyRef: 45 | key: STANDALONE 46 | name: shared-env-config 47 | - name: TRACING 48 | valueFrom: 49 | configMapKeyRef: 50 | key: TRACING 51 | name: shared-env-config 52 | - name: ZIPKIN_ENDPOINT 53 | valueFrom: 54 | configMapKeyRef: 55 | key: ZIPKIN_ENDPOINT 56 | name: shared-env-config 57 | --- 58 | apiVersion: v1 59 | kind: Service 60 | metadata: 61 | name: wallet 62 | spec: 63 | type: NodePort 64 | selector: 65 | app: wallet 66 | ports: 67 | - name: http 68 | protocol: TCP 69 | port: 80 70 | targetPort: 8103 71 | -------------------------------------------------------------------------------- /k8s/gateway-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: gateway 5 | labels: 6 | app: gateway 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: gateway 12 | template: 13 | metadata: 14 | labels: 15 | app: gateway 16 | spec: 17 | containers: 18 | - name: gateway 19 | image: kubepay.gateway:latest 20 | imagePullPolicy: IfNotPresent 21 | env: 22 | - name: AUTH_URL 23 | valueFrom: 24 | configMapKeyRef: 25 | key: AUTH_URL 26 | name: shared-env-config 27 | - name: USER_URL 28 | valueFrom: 29 | configMapKeyRef: 30 | key: USER_URL 31 | name: shared-env-config 32 | - name: WALLET_URL 33 | valueFrom: 34 | configMapKeyRef: 35 | key: WALLET_URL 36 | name: shared-env-config 37 | - name: BILLING_URL 38 | valueFrom: 39 | configMapKeyRef: 40 | key: BILLING_URL 41 | name: shared-env-config 42 | - name: STANDALONE 43 | valueFrom: 44 | configMapKeyRef: 45 | key: STANDALONE 46 | name: shared-env-config 47 | - name: TRACING 48 | valueFrom: 49 | configMapKeyRef: 50 | key: TRACING 51 | name: shared-env-config 52 | - name: ZIPKIN_ENDPOINT 53 | valueFrom: 54 | configMapKeyRef: 55 | key: ZIPKIN_ENDPOINT 56 | name: shared-env-config 57 | --- 58 | apiVersion: v1 59 | kind: Service 60 | metadata: 61 | name: gateway 62 | spec: 63 | type: NodePort 64 | selector: 65 | app: gateway 66 | ports: 67 | - name: http 68 | protocol: TCP 69 | port: 80 70 | targetPort: 8100 71 | -------------------------------------------------------------------------------- /common/testing/src/main/java/pro/teamlead/kubepay/common/testing/integration/test/ApiControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.testing.integration.test; 2 | 3 | import com.github.tomakehurst.wiremock.WireMockServer; 4 | import pro.teamlead.kubepay.auth.sdk.authority.Authority; 5 | import pro.teamlead.kubepay.auth.sdk.util.JwtTokenProvider; 6 | import pro.teamlead.kubepay.common.testing.integration.annotation.IntegrationTest; 7 | import pro.teamlead.kubepay.common.testing.integration.annotation.WireMockEnabled; 8 | import pro.teamlead.kubepay.common.util.RandomStringUtil; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.mock.mockito.SpyBean; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | 14 | import static java.lang.String.format; 15 | 16 | @IntegrationTest 17 | @WireMockEnabled 18 | public class ApiControllerIntegrationTest { 19 | 20 | @SuppressWarnings("squid:S6813") 21 | @Autowired 22 | protected MockMvc mockMvc; 23 | 24 | @SpyBean 25 | protected JwtTokenProvider provider; 26 | 27 | @SuppressWarnings("squid:S6813") 28 | @Autowired 29 | protected WireMockServer wireMockServer; 30 | 31 | @BeforeEach 32 | protected void init() { 33 | RandomStringUtil.removeMock(); 34 | wireMockServer.resetAll(); 35 | } 36 | 37 | protected String customToken(Authority ... authorities) { 38 | return asHeaderValue(provider.createToken(userName(), authorities)); 39 | } 40 | 41 | protected String serviceToken() { 42 | return asHeaderValue(provider.createToken(userName(), Authority.ROLE_SERVICE)); 43 | } 44 | 45 | protected String userToken() { 46 | return asHeaderValue(provider.createToken(userName(), Authority.ROLE_USER)); 47 | } 48 | 49 | protected String userName() { 50 | return "user"; 51 | } 52 | 53 | protected String asHeaderValue(String token) { 54 | return format("Bearer %s", token); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/configuration/JwtConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.configuration; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.NotNull; 6 | import lombok.Data; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import com.nimbusds.jose.jwk.JWK; 11 | import com.nimbusds.jose.jwk.JWKSet; 12 | import com.nimbusds.jose.jwk.RSAKey; 13 | import com.nimbusds.jose.jwk.source.ImmutableJWKSet; 14 | import com.nimbusds.jose.jwk.source.JWKSource; 15 | import com.nimbusds.jose.proc.SecurityContext; 16 | import org.springframework.security.oauth2.jwt.JwtDecoder; 17 | import org.springframework.security.oauth2.jwt.JwtEncoder; 18 | import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; 19 | import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; 20 | 21 | import java.security.interfaces.RSAPrivateKey; 22 | import java.security.interfaces.RSAPublicKey; 23 | 24 | @Data 25 | @Configuration 26 | public class JwtConfiguration { 27 | 28 | @Value("${jwt.ttl:3600}") 29 | @NotNull 30 | @Min(60) 31 | @Max(86400) 32 | private Integer jwtTTL; 33 | 34 | @Bean 35 | public JwtDecoder jwtDecoder(@Value("classpath:jwt/public.pem") RSAPublicKey publicKey) { 36 | return NimbusJwtDecoder 37 | .withPublicKey(publicKey) 38 | .build(); 39 | } 40 | 41 | @Bean 42 | public JwtEncoder jwtEncoder(@Value("classpath:jwt/public.pem") RSAPublicKey publicKey, 43 | @Value("classpath:jwt/private.pem") RSAPrivateKey privateKey) { 44 | JWK jwk = new RSAKey 45 | .Builder(publicKey) 46 | .privateKey(privateKey) 47 | .build(); 48 | JWKSource jwks = new ImmutableJWKSet<>(new JWKSet(jwk)); 49 | return new NimbusJwtEncoder(jwks); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /common/testing/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | testing 7 | ${common.libs.version} 8 | 9 | 10 | pro.teamlead.kubepay 11 | common 12 | 0.0.0 13 | ../pom.xml 14 | 15 | 16 | 17 | 3.3.1 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-test 24 | 25 | 26 | org.junit.vintage 27 | junit-vintage-engine 28 | 29 | 30 | 31 | 32 | org.wiremock 33 | wiremock-standalone 34 | ${wiremock.version} 35 | 36 | 37 | pro.teamlead.kubepay 38 | json 39 | ${common.libs.version} 40 | 41 | 42 | pro.teamlead.kubepay 43 | auth-sdk 44 | ${auth.libs.version} 45 | 46 | 47 | pro.teamlead.kubepay 48 | util 49 | ${common.libs.version} 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | zipkin: 5 | image: openzipkin/zipkin 6 | ports: 7 | - "9411:9411" 8 | gateway: 9 | image: kubepay.gateway:latest 10 | ports: 11 | - "8100:8100" 12 | - "8500:8500" 13 | environment: 14 | - AUTH_URL=${AUTH_URL} 15 | - USER_URL=${USER_URL} 16 | - WALLET_URL=${WALLET_URL} 17 | - BILLING_URL=${BILLING_URL} 18 | - STANDALONE=${STANDALONE} 19 | - TRACING=${TRACING} 20 | - ZIPKIN_ENDPOINT=${ZIPKIN_ENDPOINT} 21 | - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8500 22 | auth: 23 | image: kubepay.auth:latest 24 | ports: 25 | - "8101:8101" 26 | - "8501:8501" 27 | environment: 28 | - AUTH_URL=${AUTH_URL} 29 | - USER_URL=${USER_URL} 30 | - WALLET_URL=${WALLET_URL} 31 | - BILLING_URL=${BILLING_URL} 32 | - STANDALONE=${STANDALONE} 33 | - TRACING=${TRACING} 34 | - ZIPKIN_ENDPOINT=${ZIPKIN_ENDPOINT} 35 | - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8501 36 | user: 37 | image: kubepay.user:latest 38 | ports: 39 | - "8102:8102" 40 | - "8502:8502" 41 | environment: 42 | - AUTH_URL=${AUTH_URL} 43 | - USER_URL=${USER_URL} 44 | - WALLET_URL=${WALLET_URL} 45 | - BILLING_URL=${BILLING_URL} 46 | - STANDALONE=${STANDALONE} 47 | - TRACING=${TRACING} 48 | - ZIPKIN_ENDPOINT=${ZIPKIN_ENDPOINT} 49 | - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8502 50 | wallet: 51 | image: kubepay.wallet:latest 52 | ports: 53 | - "8103:8103" 54 | - "8503:8503" 55 | environment: 56 | - AUTH_URL=${AUTH_URL} 57 | - USER_URL=${USER_URL} 58 | - WALLET_URL=${WALLET_URL} 59 | - BILLING_URL=${BILLING_URL} 60 | - STANDALONE=${STANDALONE} 61 | - TRACING=${TRACING} 62 | - ZIPKIN_ENDPOINT=${ZIPKIN_ENDPOINT} 63 | - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8503 64 | -------------------------------------------------------------------------------- /user/user-service/src/main/java/pro/teamlead/kubepay/user/application/UserApplicationService.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.application; 2 | 3 | import com.github.javafaker.Faker; 4 | import pro.teamlead.kubepay.auth.sdk.user.UserPrincipal; 5 | import pro.teamlead.kubepay.user.api.domain.model.CreateUserRequest; 6 | import pro.teamlead.kubepay.user.api.domain.model.UserInfo; 7 | import pro.teamlead.kubepay.user.domain.model.User; 8 | import pro.teamlead.kubepay.user.domain.service.UserService; 9 | import io.micrometer.observation.annotation.Observed; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.springframework.stereotype.Service; 14 | import pro.teamlead.kubepay.user.api.domain.exception.UserNotFoundException; 15 | 16 | 17 | @Slf4j 18 | @Service 19 | @RequiredArgsConstructor 20 | @Observed 21 | public class UserApplicationService { 22 | 23 | private final UserService userService; 24 | 25 | public UserInfo getUserInfo(@NotNull UserPrincipal principal) { 26 | return getUserInfo(principal.getUser()); 27 | } 28 | 29 | public UserInfo getUserInfo(@NotNull String id) { 30 | var user = userService.getUser(id) 31 | .orElseThrow(UserNotFoundException::new); 32 | 33 | return new UserInfo(user.getUser(), user.getName(), user.getEnabled()); 34 | } 35 | 36 | public UserInfo createUser(@NotNull CreateUserRequest request) { 37 | var faker = new Faker(); 38 | var fakeName = faker.name().fullName(); 39 | 40 | User user = userService.createUser(request.user(), 41 | request.passwordHash(), 42 | fakeName); 43 | 44 | return new UserInfo(user.getUser(), user.getName(), user.getEnabled()); 45 | } 46 | 47 | public String getPasswordHash(@NotNull String id) { 48 | 49 | var user = userService.getUser(id) 50 | .orElseThrow(UserNotFoundException::new); 51 | 52 | return user.getPasswordHash(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /auth/auth-sdk/src/main/java/pro/teamlead/kubepay/auth/sdk/error/ApiClientErrorDecoder.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.sdk.error; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import pro.teamlead.kubepay.common.json.ObjectMapperBuilder; 5 | import feign.FeignException; 6 | import feign.Response; 7 | import feign.codec.ErrorDecoder; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.security.access.AccessDeniedException; 12 | 13 | import java.util.HashSet; 14 | import java.util.Set; 15 | 16 | 17 | @Slf4j 18 | public class ApiClientErrorDecoder implements ErrorDecoder { 19 | 20 | private static final Set APPLICATION_ERROR_STATUSES = new HashSet<>(); 21 | 22 | private static final ObjectMapper OBJECT_MAPPER = ObjectMapperBuilder.build(); 23 | 24 | static { 25 | APPLICATION_ERROR_STATUSES.add(HttpStatus.UNPROCESSABLE_ENTITY.value()); 26 | APPLICATION_ERROR_STATUSES.add(HttpStatus.BAD_REQUEST.value()); 27 | APPLICATION_ERROR_STATUSES.add(HttpStatus.FORBIDDEN.value()); 28 | } 29 | 30 | private final Class type; 31 | 32 | public ApiClientErrorDecoder(Class type) { 33 | this.type = type; 34 | } 35 | 36 | public Exception decode(final @NotNull String methodKey, final @NotNull Response response) { 37 | 38 | if (!APPLICATION_ERROR_STATUSES.contains(response.status())) { 39 | return FeignException.errorStatus(methodKey, response); 40 | } 41 | 42 | if (HttpStatus.FORBIDDEN.value() == response.status()) { 43 | return new AccessDeniedException(HttpStatus.FORBIDDEN.getReasonPhrase()); 44 | } 45 | 46 | try { 47 | if (response.body() != null) { 48 | return OBJECT_MAPPER.readValue(response.body().asInputStream(), type); 49 | } 50 | } catch (Exception e) { 51 | log.error(e.getMessage(), e); 52 | } 53 | 54 | return FeignException.errorStatus(methodKey, response); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /gateway/gateway-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: gateway 4 | cloud: 5 | gateway: 6 | globalcors: 7 | cors-configurations: 8 | '[/**]': 9 | allowedOrigins: "https://localhost" 10 | allowedMethods: 11 | - GET 12 | - POST 13 | - OPTIONS 14 | httpclient: 15 | wiretap: true 16 | httpserver: 17 | wiretap: true 18 | routes: 19 | - id: auth 20 | uri: ${AUTH_URL:http://localhost:8101} 21 | predicates: 22 | - Path=/auth/** 23 | filters: 24 | - StripPrefix=1 25 | - id: user 26 | uri: ${USER_URL:http://localhost:8102} 27 | predicates: 28 | - Path=/user/** 29 | filters: 30 | - StripPrefix=1 31 | - id: wallet 32 | uri: ${WALLET_URL:http://localhost:8103} 33 | predicates: 34 | - Path=/wallet/** 35 | filters: 36 | - StripPrefix=1 37 | - id: billing 38 | uri: https://httpbin.org 39 | predicates: 40 | - Path=/billing/** 41 | filters: 42 | - RewritePath=/billing/(?.*),/get?path=$\{path} 43 | - name: RequestRateLimiter 44 | args: 45 | rate-limiter: "#{@customRateLimiter}" 46 | key-resolver: "#{@customKeyResolver}" 47 | 48 | management: 49 | endpoints: 50 | web: 51 | exposure: 52 | include: 53 | - health 54 | base-path: /actuator 55 | server: 56 | port: ${ACTUATOR_PORT:8200} 57 | tracing: 58 | sampling: 59 | probability: 1 60 | enabled: ${TRACING:false} 61 | zipkin: 62 | tracing: 63 | endpoint: ${ZIPKIN_ENDPOINT:http://localhost:9411/api/v2/spans} 64 | 65 | springdoc: 66 | swagger-ui: 67 | path: /swagger-ui 68 | urls: 69 | - url: /auth/api-docs 70 | name: auth 71 | - url: /wallet/api-docs 72 | name: wallet 73 | - url: /user/api-docs 74 | name: user 75 | 76 | server: 77 | port : ${SERVER_PORT:8100} 78 | standalone: ${STANDALONE:true} 79 | -------------------------------------------------------------------------------- /common/testing/src/main/java/pro/teamlead/kubepay/common/testing/integration/testcase/FileSourceProvider.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.testing.integration.testcase; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import pro.teamlead.kubepay.common.json.ObjectMapperBuilder; 5 | import org.junit.jupiter.api.extension.*; 6 | import org.springframework.util.ResourceUtils; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | 12 | 13 | public class FileSourceProvider implements ParameterResolver, BeforeEachCallback { 14 | 15 | @SuppressWarnings("squid:S3077") 16 | private volatile String[] paths; 17 | 18 | private final ObjectMapper objectMapper = ObjectMapperBuilder.build(); 19 | 20 | @Override 21 | public void beforeEach(ExtensionContext context) { 22 | context.getTestMethod().ifPresent(method -> { 23 | TestCase testParams = method.getAnnotation(TestCase.class); 24 | if (testParams != null) { 25 | paths = testParams.value(); 26 | } 27 | }); 28 | } 29 | 30 | @Override 31 | public boolean supportsParameter(ParameterContext parameterContext, 32 | ExtensionContext extensionContext) { 33 | return paths != null && parameterContext.getIndex() < paths.length; 34 | } 35 | 36 | @Override 37 | public Object resolveParameter(ParameterContext parameterContext, 38 | ExtensionContext extensionContext) { 39 | 40 | try { 41 | String jsonResource = getJsonResource(paths[parameterContext.getIndex()]); 42 | Class type = parameterContext.getParameter().getType(); 43 | if (type == String.class) { 44 | return jsonResource; 45 | } else { 46 | return objectMapper.readValue(jsonResource, type); 47 | } 48 | } catch (IOException e) { 49 | throw new ParameterResolutionException("Failed to read file", e); 50 | } 51 | } 52 | 53 | private String getJsonResource(String file) throws IOException { 54 | String classpathName = String.format("classpath:%s", file); 55 | Path path = ResourceUtils.getFile(classpathName).toPath(); 56 | String result = new String(Files.readAllBytes(path)); 57 | return result.trim(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /coverage/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | coverage 7 | 0.0.0 8 | pom 9 | 10 | coverage 11 | Aggregated Coverage Report 12 | 13 | 14 | pro.teamlead.kubepay 15 | root 16 | 0.0.0 17 | ../pom.xml 18 | 19 | 20 | 21 | 22 | 23 | pro.teamlead.kubepay 24 | user-service 25 | ${user.service.version} 26 | 27 | 28 | 29 | pro.teamlead.kubepay 30 | wallet-service 31 | ${wallset.service.version} 32 | 33 | 34 | 35 | pro.teamlead.kubepay 36 | auth-service 37 | ${auth.service.version} 38 | 39 | 40 | 41 | pro.teamlead.kubepay 42 | gateway-service 43 | ${gateway.service.version} 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.jacoco 52 | jacoco-maven-plugin 53 | 0.8.10 54 | 55 | 56 | report-aggregate 57 | verify 58 | 59 | report-aggregate 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /user/user-service/src/main/java/pro/teamlead/kubepay/user/domain/service/UserService.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.user.domain.service; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.user.ServicePrincipal; 4 | import pro.teamlead.kubepay.user.domain.model.User; 5 | import pro.teamlead.kubepay.user.repository.UserRepository; 6 | import pro.teamlead.kubepay.wallet.api.domain.exception.WalletAlreadyCreatedException; 7 | import pro.teamlead.kubepay.wallet.api.domain.model.CreateWalletRequest; 8 | import pro.teamlead.kubepay.wallet.client.WalletClient; 9 | import io.micrometer.observation.annotation.Observed; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.security.access.AccessDeniedException; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.web.server.ResponseStatusException; 17 | 18 | import java.math.BigDecimal; 19 | import java.util.Optional; 20 | 21 | @Slf4j 22 | @Service 23 | @RequiredArgsConstructor 24 | @Observed 25 | public class UserService { 26 | 27 | private final UserRepository userRepository; 28 | 29 | private final WalletClient walletClient; 30 | 31 | private final ServicePrincipal servicePrincipal; 32 | 33 | public Optional getUser(@NotNull String user) { 34 | User wallet = userRepository.findByUser(user); 35 | return Optional.ofNullable(wallet); 36 | } 37 | 38 | public User createUser(@NotNull String userId, 39 | @NotNull String passwordHash, 40 | @NotNull String name) { 41 | User user = new User(userId, name, true, passwordHash); 42 | 43 | userRepository.save(user); 44 | 45 | CreateWalletRequest request = new CreateWalletRequest(userId, BigDecimal.ZERO); 46 | 47 | try { 48 | walletClient.createWallet(servicePrincipal, request); 49 | } catch (WalletAlreadyCreatedException e) { 50 | log.warn("User {} wallet already created", userId); 51 | } catch (AccessDeniedException e) { 52 | log.error(e.getMessage()); 53 | } catch (Exception e) { 54 | throw new ResponseStatusException( 55 | HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e); 56 | } 57 | 58 | return user; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /common/service/src/main/java/pro/teamlead/kubepay/common/service/configuration/SpringSecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.service.configuration; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.core.Ordered; 8 | import org.springframework.core.annotation.Order; 9 | import org.springframework.http.HttpMethod; 10 | import org.springframework.security.config.Customizer; 11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.security.web.SecurityFilterChain; 14 | 15 | import static java.lang.String.format; 16 | 17 | @Configuration 18 | @ConditionalOnProperty(prefix = "security", name = "customFilterChain", havingValue = "false", matchIfMissing = true) 19 | public class SpringSecurityConfiguration { 20 | 21 | @Value("${springdoc.api-docs.path}") 22 | private String restApiDocPath; 23 | 24 | @Value("${springdoc.swagger-ui.path}") 25 | private String swaggerPath; 26 | 27 | @Value("${management.endpoints.web.base-path}") 28 | private String actuatorPath; 29 | 30 | private static final String PATH_MATCHER = "%s/**"; 31 | 32 | @Bean 33 | @Order(Ordered.HIGHEST_PRECEDENCE) 34 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 35 | // @formatter:off 36 | http 37 | .authorizeHttpRequests(authorize -> authorize 38 | .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() 39 | .requestMatchers(format(PATH_MATCHER, restApiDocPath)).permitAll() 40 | .requestMatchers(format(PATH_MATCHER, swaggerPath)).permitAll() 41 | .requestMatchers(format(PATH_MATCHER, actuatorPath)).permitAll() 42 | .anyRequest().authenticated() 43 | ) 44 | .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults())) 45 | .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); 46 | // @formatter:on 47 | return http.build(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /common/service/src/main/java/pro/teamlead/kubepay/common/service/configuration/ServiceSecurityBeanConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.service.configuration; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 8 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 9 | import org.springframework.security.crypto.password.DelegatingPasswordEncoder; 10 | import org.springframework.security.crypto.password.NoOpPasswordEncoder; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; 13 | import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | 19 | @Configuration 20 | @ConditionalOnWebApplication 21 | @EnableMethodSecurity 22 | public class ServiceSecurityBeanConfiguration { 23 | 24 | @Bean 25 | @ConditionalOnMissingBean 26 | @SuppressWarnings("squid:S5344") 27 | public PasswordEncoder passwordEncoder() { 28 | String idForEncode = "bcrypt"; 29 | Map encoders = new HashMap<>(); 30 | encoders.put(idForEncode, new BCryptPasswordEncoder()); 31 | 32 | // Passwords should not be stored in plaintext or with a fast hashing algorithm 33 | // This one should only be used for tests to mock out password encoding 34 | encoders.put("noop", NoOpPasswordEncoder.getInstance()); 35 | return new DelegatingPasswordEncoder(idForEncode, encoders); 36 | 37 | } 38 | 39 | @Bean 40 | public JwtAuthenticationConverter jwtAuthenticationConverter() { 41 | JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); 42 | grantedAuthoritiesConverter.setAuthorityPrefix(""); 43 | JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); 44 | jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); 45 | return jwtAuthenticationConverter; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/main/java/pro/teamlead/kubepay/wallet/application/WalletApplicationService.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet.application; 2 | 3 | import pro.teamlead.kubepay.wallet.api.domain.exception.WalletNotFoundException; 4 | 5 | import pro.teamlead.kubepay.auth.sdk.user.UserPrincipal; 6 | import pro.teamlead.kubepay.wallet.api.domain.model.CreateWalletRequest; 7 | import pro.teamlead.kubepay.wallet.api.domain.model.TransferFundsRequest; 8 | import pro.teamlead.kubepay.wallet.api.domain.model.WalletInfo; 9 | import pro.teamlead.kubepay.wallet.domain.model.Wallet; 10 | import pro.teamlead.kubepay.wallet.domain.service.WalletService; 11 | import io.micrometer.observation.annotation.Observed; 12 | import lombok.RequiredArgsConstructor; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.math.BigDecimal; 17 | import java.util.Optional; 18 | 19 | @Slf4j 20 | @Service 21 | @RequiredArgsConstructor 22 | @Observed 23 | public class WalletApplicationService { 24 | 25 | private final WalletService walletService; 26 | 27 | public WalletInfo getInfo(UserPrincipal user) { 28 | Optional walletOptional = walletService.findByUser(user.getUser()); 29 | Wallet wallet = walletOptional.orElseGet(() -> 30 | walletService.createWallet(user.getUser(), BigDecimal.ZERO)); 31 | return new WalletInfo(wallet.getAddress(), wallet.getBalance()); 32 | } 33 | 34 | public WalletInfo createWallet(CreateWalletRequest request) { 35 | Wallet wallet = walletService.createWallet(request.user(), 36 | request.initialBalance()); 37 | 38 | return new WalletInfo(wallet.getAddress(), wallet.getBalance()); 39 | } 40 | 41 | public void topUp(UserPrincipal user, BigDecimal amount) { 42 | Wallet wallet = walletService.findByUser(user.getUser()) 43 | .orElseThrow(WalletNotFoundException::new); 44 | 45 | walletService.topUp(wallet, amount); 46 | } 47 | 48 | public void transferFunds(UserPrincipal user, 49 | TransferFundsRequest request) { 50 | 51 | Wallet fromWallet = walletService.findByUser(user.getUser()) 52 | .orElseThrow(WalletNotFoundException::new); 53 | 54 | Wallet toWallet = walletService.findByAddress(request.to()) 55 | .orElseThrow(WalletNotFoundException::new); 56 | 57 | walletService.transferFunds(fromWallet, toWallet, request.amount()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /common/service/src/main/java/pro/teamlead/kubepay/common/service/advice/RestExceptionControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.service.advice; 2 | 3 | import pro.teamlead.kubepay.common.api.domain.exception.ApiException; 4 | import lombok.Getter; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.HttpStatusCode; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.validation.FieldError; 11 | import org.springframework.web.bind.MethodArgumentNotValidException; 12 | import org.springframework.web.bind.annotation.ExceptionHandler; 13 | import org.springframework.web.bind.annotation.ResponseStatus; 14 | import org.springframework.web.bind.annotation.RestControllerAdvice; 15 | import org.springframework.web.context.request.WebRequest; 16 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 17 | 18 | @RestControllerAdvice 19 | public class RestExceptionControllerAdvice extends ResponseEntityExceptionHandler { 20 | 21 | public static final String DEFAULT_ERROR_TYPE = "ERROR"; 22 | 23 | @ExceptionHandler(ApiException.class) 24 | @ResponseStatus(HttpStatus.BAD_REQUEST) 25 | public ApiError handle(final @NotNull ApiException e) { 26 | return new ApiError(e); 27 | } 28 | 29 | @Override 30 | protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, 31 | HttpHeaders headers, 32 | HttpStatusCode status, 33 | WebRequest request) { 34 | 35 | FieldError fieldError = ex.getBindingResult().getFieldError(); 36 | String fieldName = fieldError != null ? fieldError.getField() : "unknown"; 37 | String errorMessage = fieldError != null ? fieldError.getDefaultMessage() : "unknown error"; 38 | 39 | ApiError responseEntity = new ApiError("Field '" + fieldName + "' " + errorMessage); 40 | return new ResponseEntity<>(responseEntity, headers, status); 41 | } 42 | 43 | @Getter 44 | public static class ApiError { 45 | 46 | private final String message; 47 | private final String type; 48 | 49 | public ApiError(ApiException e) { 50 | this.message = e.getMessage(); 51 | this.type = e.getType(); 52 | } 53 | 54 | public ApiError(String message) { 55 | this.message = message; 56 | this.type = DEFAULT_ERROR_TYPE; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /wallet/wallet-service/src/test/java/pro/teamlead/kubepay/wallet/WalletServiceApiControllerTest.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.wallet; 2 | 3 | import pro.teamlead.kubepay.auth.sdk.authority.Authority; 4 | import pro.teamlead.kubepay.common.testing.integration.test.ApiControllerIntegrationTest; 5 | import pro.teamlead.kubepay.common.testing.integration.testcase.TestCase; 6 | import pro.teamlead.kubepay.common.util.RandomStringUtil; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.test.web.servlet.result.MockMvcResultHandlers; 10 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 11 | 12 | import static pro.teamlead.kubepay.wallet.api.WalletApiMethodDictionary.WALLET_CREATE; 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 15 | 16 | public class WalletServiceApiControllerTest extends ApiControllerIntegrationTest { 17 | 18 | @TestCase({ 19 | "integration/service-api/create-wallet/success/request.json", 20 | "integration/service-api/create-wallet/success/response.json", 21 | }) 22 | public void whenRequestingWalletCreation_thenReturnSuccess(String request, String response) throws Exception { 23 | 24 | RandomStringUtil.setMock("random"); 25 | 26 | var token = customToken(Authority.ROLE_SERVICE, Authority.AUTHORITY_WALLET_CREATE); 27 | 28 | mockMvc.perform(post(WALLET_CREATE) 29 | .header(HttpHeaders.AUTHORIZATION, token) 30 | .content(request) 31 | .contentType(MediaType.APPLICATION_JSON)) 32 | .andExpect(status().isOk()) 33 | .andDo(MockMvcResultHandlers.print()) 34 | .andExpect(MockMvcResultMatchers.content().json(response)); 35 | } 36 | 37 | @TestCase({ 38 | "integration/service-api/create-wallet/error/request.json", 39 | }) 40 | public void whenRequestingWalletCreation_withoutProperAuthority_thenThrowForbidden(String request) throws Exception { 41 | 42 | mockMvc.perform(post(WALLET_CREATE) 43 | .header(HttpHeaders.AUTHORIZATION, customToken(Authority.ROLE_SERVICE)) 44 | .content(request) 45 | .contentType(MediaType.APPLICATION_JSON)) 46 | .andExpect(status().isForbidden()); 47 | 48 | mockMvc.perform(post(WALLET_CREATE) 49 | .header(HttpHeaders.AUTHORIZATION, customToken(Authority.ROLE_USER)) 50 | .content(request) 51 | .contentType(MediaType.APPLICATION_JSON)) 52 | .andExpect(status().isForbidden()); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /common/testing/src/main/resources/jwt/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCK5ng3ZVnxuj3m 3 | AuD+3JWnqERpHs2deoaw92DuJZUSccswQwSnEopJGqJ6zbGColgr6+p1cXmqYhz1 4 | qsywpBDAdbxTe4xS18ATlcyOBfR5S/gytk57ElWJTGAelajbXgne2r/6Fe66gWfP 5 | nXirXx3jXcVbeXrvj5LshJWO+thB4jXQgd+Fc8bxiNdO1mX3pl403sp92f63Cf9H 6 | K4E1o4SyG9GGSWTjoE8UlhRC+Hpb8qkDlz1yw2PRDAkOqq7C6AzfwZ9JRo5iSL2c 7 | HP5xglZPCwDtmMXnebLrToyXWJSymSnKlVJY530wBSMY7Kws5Av/+SmQgCGkoAeQ 8 | oDSpPBoe2SsLJkuCFPEerqLz2gxkU52XYADHPsBsHbEY97GK1hzWByEZOewpW+Qy 9 | p78h02EjxPpgkDBVnySnYKbIxuWDv7hdA5F51Kdj8d2LX2WNoGSSJQM6Wwxyxj7Y 10 | NrTs7kaiOlUkuyOeUTgqMc2L291I4Q1LZCWe63LZf83OJYMDOueGwg0xo4S7DBZy 11 | j7wdiu+WqJyEMQrZyiVsOnFY0GoGlMcXin6O0FXMiiZcVObuvk9Fo+tVw/zIwXlN 12 | az2PBSoSqNacStyeY1v0XcB6hwWLwNN3rHpjFcD0bxgx3HUkQqKCAg+7nzu+JjGI 13 | USBKYL4NIcfj1K4nNSjPJ11dkbQDmQIDAQABAoICAEY7LBPOckEQS1XupLcY766b 14 | HSQNyCdVGZ+HJYKkjBCMaxGXAVY8wKEgD4GKWbPUxEJJAf6qBruI6l2pI+A4xKOb 15 | pEKN2LDWmC8O8iPpjOh76h4yIdKGvasza/Jq97sbfGArOSCJUHELF2NZmvcDnAgn 16 | lK+g5/ETJlXi4AAwg4Ikj9NCp7NBjBj6eOtK6rnibAXNlJM301vXeTUSIT3u1Rx9 17 | hl6g45tYpkOhctM98hhlxOGk6hQKCYFc1YON99ljHYdRzmltfHbjksPJwTnBjkRf 18 | KvP36DEXTsk9qUapKl53pTLid4WuXpUWM93ZZWE5WXvvDv4aW2hy5gBRkO71DbtE 19 | RVdLKyD7uruMwPtikHzg1fOVKWEPTpIlVL3iHEDubRiBp7nhraWpcO0Pvq0FifpW 20 | NFuCYG9XUsgPZL16LdtL/uBfCXb5NVfDuJz1DjM77v9Xyp9Ev+agCtxieZkDKlbX 21 | nKu4hLAG08tjYpvyFv69dPEyBTiYZ97AB9vUukmfEUU6X6LeH+LJThhxOFRgwEsk 22 | aQ2IP+nX9OJH0EEb6ArVuVqm731VSeNS0fvdNWEuw6amNsT8k3nRuvBYKNNVKXHJ 23 | HrM0/RLJMZCd3W59mnDWeDQVuyC1aXnpTL/h858rE/7gCY+kLizN1dM3RXe+VMjH 24 | R5VTbsDDucvShk8A8CTlAoIBAQDcnYPFnOsWyVBSNFaX+GSqAoGzqJKzuY9RlvXT 25 | L7gfeUUGEBFfniuSlNGjxyYuQ2N3sonaxZ+BTb0EpsL2asb1tzlaZ661FYChz8qG 26 | Xwp81N6gDXIF8AK+VW3w7NmlV1BaW4N/MY09UD9SCjx3I5Vpdrh7BYtvV1ogpL4y 27 | 4uAUs7kUpmD8tZWhtci9zKbA8M7cocI4RA6tP8Og8q6z2hTqRkXa8Q/XQk/DF/OX 28 | GSPw94peVDgRLsumkEiGcLRxoT2I5Dz9f1lrjg8A+2nk13lZh0rZMiVHv9+mrocA 29 | Ds24y9cwARvdVdielFfuGl5nyW6Tn4EfBVEh7Rw/9urdVm47AoIBAQChLbpsgag4 30 | xWV/wbdpllr7ssSP8iZOsYfjFCALuvneZuIT2G3zK0E+qeUR8Dxx4XRfzQ87AbVi 31 | bu1e992WmukWmAAGEkjftVsei7haLd8YM/tgO7qSCnMjEiX98zAGjfp6BoFzMfnA 32 | H81WjN+pbcK6r8bzLkFgjf77ezNvJunqfCx04gT+ZSUssLuAVSpoB9ncfsGPW81c 33 | rFlWmHPsdFPME6eiM7NMrA27wlyaaVWe3gs/TmTwvIan9t7QYWFq1pkMAt8VnWx5 34 | YaXNJQNSe/T+QuR5+0DeGFMhIJkkFK+vce1UQmchqDVlWrH5QXZMlnfB4JCLfjx9 35 | CZBWaCyrxBQ7AoIBABNRq9djPWb6bBE2yhp2wwHsREViTq50YmuHp7E9rYb8DKJS 36 | R+Myq6gjZhRMfThx6ET0GoRQ5/3fu9yraclAYnnj+J7FE5SB9Ii29Io9ymJSFci4 37 | RLLmaKcYPg9p/kkvUzcGaoZ55V9oJpNdmBvqvsvEfLUxMBuo75iKNKmqGmtVrbB0 38 | PWwDzr2heJm+UR/2Nnc1pxdcRli/i7cNaWUQhOJbEwTwNS3pfaic8VTa+72/P5ux 39 | DlPa4DSOz/sN5Y9JymSxb/HW9BKqFwBEA+rpP5Qqd9B7Rq54y/IBuIq1wSgcmZR3 40 | lGQWf2irnuOX5Yp7JjbBMgJwacFpzR6A1eJaDj8CggEAG3sWfAWnTvapFZ1Lzl7q 41 | aFvUk85vpZ8zUg5OYHGixL658TRHmtxXJX4GJobVZ31PQD5QSHlEGeJb6oNMbq8+ 42 | 6rFD38UiU7IyNQURi56imRfT1laYuum5M9OWUrQyDgwLFt8hFykAMje55PXj9JlY 43 | SEhMHY5Xa0UKooog/OSfoXFGiy82NsG8EuCXN4xNzKr6BFRAYFcArrpcCR25Nimn 44 | V/ZJ8Kfr/Dihps++xj0Lcijdtg+BumIKe3zmBJV17KdmNNwQeTj8E74IbO/QnzGv 45 | VeLF3d5u8u8mzVEnizVTtxAvrNwdhWmOoRdTnPICX5CQHqhRPNdrLM6Co5jjdO3b 46 | nQKCAQAtrqN3+DvOlWlq9Onyv/Pal9YZhy8R0sH6eYFfdt4CGwxdCgFzuybFEfqx 47 | bsn/kxbVYG4yDZ4SNOSdX4aihuvPkawv3i8EnD3cdvRUipb/+3HNHtCC9H0lg9C9 48 | cBmsRnuu5jxC+n7i3b3C/SzHgxXIVJye9xud8ZBWJLk9ANnxiw2t64b+aNuUI7u0 49 | qyydb+hcY1BBE9NogwwOVjiRKsnoEVO9LbYXbWBKHhbRw/gTnFRzn8fcjMG120f0 50 | RzBrTG7YCVFmmDohOqUZIzawDf/lf7o0tyiGmRdq9djgS8l0/0+kkMdVTGWS2IN+ 51 | XVEH0VishgwbYXOJCSBlZD7h0yS2 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/resources/jwt/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCK5ng3ZVnxuj3m 3 | AuD+3JWnqERpHs2deoaw92DuJZUSccswQwSnEopJGqJ6zbGColgr6+p1cXmqYhz1 4 | qsywpBDAdbxTe4xS18ATlcyOBfR5S/gytk57ElWJTGAelajbXgne2r/6Fe66gWfP 5 | nXirXx3jXcVbeXrvj5LshJWO+thB4jXQgd+Fc8bxiNdO1mX3pl403sp92f63Cf9H 6 | K4E1o4SyG9GGSWTjoE8UlhRC+Hpb8qkDlz1yw2PRDAkOqq7C6AzfwZ9JRo5iSL2c 7 | HP5xglZPCwDtmMXnebLrToyXWJSymSnKlVJY530wBSMY7Kws5Av/+SmQgCGkoAeQ 8 | oDSpPBoe2SsLJkuCFPEerqLz2gxkU52XYADHPsBsHbEY97GK1hzWByEZOewpW+Qy 9 | p78h02EjxPpgkDBVnySnYKbIxuWDv7hdA5F51Kdj8d2LX2WNoGSSJQM6Wwxyxj7Y 10 | NrTs7kaiOlUkuyOeUTgqMc2L291I4Q1LZCWe63LZf83OJYMDOueGwg0xo4S7DBZy 11 | j7wdiu+WqJyEMQrZyiVsOnFY0GoGlMcXin6O0FXMiiZcVObuvk9Fo+tVw/zIwXlN 12 | az2PBSoSqNacStyeY1v0XcB6hwWLwNN3rHpjFcD0bxgx3HUkQqKCAg+7nzu+JjGI 13 | USBKYL4NIcfj1K4nNSjPJ11dkbQDmQIDAQABAoICAEY7LBPOckEQS1XupLcY766b 14 | HSQNyCdVGZ+HJYKkjBCMaxGXAVY8wKEgD4GKWbPUxEJJAf6qBruI6l2pI+A4xKOb 15 | pEKN2LDWmC8O8iPpjOh76h4yIdKGvasza/Jq97sbfGArOSCJUHELF2NZmvcDnAgn 16 | lK+g5/ETJlXi4AAwg4Ikj9NCp7NBjBj6eOtK6rnibAXNlJM301vXeTUSIT3u1Rx9 17 | hl6g45tYpkOhctM98hhlxOGk6hQKCYFc1YON99ljHYdRzmltfHbjksPJwTnBjkRf 18 | KvP36DEXTsk9qUapKl53pTLid4WuXpUWM93ZZWE5WXvvDv4aW2hy5gBRkO71DbtE 19 | RVdLKyD7uruMwPtikHzg1fOVKWEPTpIlVL3iHEDubRiBp7nhraWpcO0Pvq0FifpW 20 | NFuCYG9XUsgPZL16LdtL/uBfCXb5NVfDuJz1DjM77v9Xyp9Ev+agCtxieZkDKlbX 21 | nKu4hLAG08tjYpvyFv69dPEyBTiYZ97AB9vUukmfEUU6X6LeH+LJThhxOFRgwEsk 22 | aQ2IP+nX9OJH0EEb6ArVuVqm731VSeNS0fvdNWEuw6amNsT8k3nRuvBYKNNVKXHJ 23 | HrM0/RLJMZCd3W59mnDWeDQVuyC1aXnpTL/h858rE/7gCY+kLizN1dM3RXe+VMjH 24 | R5VTbsDDucvShk8A8CTlAoIBAQDcnYPFnOsWyVBSNFaX+GSqAoGzqJKzuY9RlvXT 25 | L7gfeUUGEBFfniuSlNGjxyYuQ2N3sonaxZ+BTb0EpsL2asb1tzlaZ661FYChz8qG 26 | Xwp81N6gDXIF8AK+VW3w7NmlV1BaW4N/MY09UD9SCjx3I5Vpdrh7BYtvV1ogpL4y 27 | 4uAUs7kUpmD8tZWhtci9zKbA8M7cocI4RA6tP8Og8q6z2hTqRkXa8Q/XQk/DF/OX 28 | GSPw94peVDgRLsumkEiGcLRxoT2I5Dz9f1lrjg8A+2nk13lZh0rZMiVHv9+mrocA 29 | Ds24y9cwARvdVdielFfuGl5nyW6Tn4EfBVEh7Rw/9urdVm47AoIBAQChLbpsgag4 30 | xWV/wbdpllr7ssSP8iZOsYfjFCALuvneZuIT2G3zK0E+qeUR8Dxx4XRfzQ87AbVi 31 | bu1e992WmukWmAAGEkjftVsei7haLd8YM/tgO7qSCnMjEiX98zAGjfp6BoFzMfnA 32 | H81WjN+pbcK6r8bzLkFgjf77ezNvJunqfCx04gT+ZSUssLuAVSpoB9ncfsGPW81c 33 | rFlWmHPsdFPME6eiM7NMrA27wlyaaVWe3gs/TmTwvIan9t7QYWFq1pkMAt8VnWx5 34 | YaXNJQNSe/T+QuR5+0DeGFMhIJkkFK+vce1UQmchqDVlWrH5QXZMlnfB4JCLfjx9 35 | CZBWaCyrxBQ7AoIBABNRq9djPWb6bBE2yhp2wwHsREViTq50YmuHp7E9rYb8DKJS 36 | R+Myq6gjZhRMfThx6ET0GoRQ5/3fu9yraclAYnnj+J7FE5SB9Ii29Io9ymJSFci4 37 | RLLmaKcYPg9p/kkvUzcGaoZ55V9oJpNdmBvqvsvEfLUxMBuo75iKNKmqGmtVrbB0 38 | PWwDzr2heJm+UR/2Nnc1pxdcRli/i7cNaWUQhOJbEwTwNS3pfaic8VTa+72/P5ux 39 | DlPa4DSOz/sN5Y9JymSxb/HW9BKqFwBEA+rpP5Qqd9B7Rq54y/IBuIq1wSgcmZR3 40 | lGQWf2irnuOX5Yp7JjbBMgJwacFpzR6A1eJaDj8CggEAG3sWfAWnTvapFZ1Lzl7q 41 | aFvUk85vpZ8zUg5OYHGixL658TRHmtxXJX4GJobVZ31PQD5QSHlEGeJb6oNMbq8+ 42 | 6rFD38UiU7IyNQURi56imRfT1laYuum5M9OWUrQyDgwLFt8hFykAMje55PXj9JlY 43 | SEhMHY5Xa0UKooog/OSfoXFGiy82NsG8EuCXN4xNzKr6BFRAYFcArrpcCR25Nimn 44 | V/ZJ8Kfr/Dihps++xj0Lcijdtg+BumIKe3zmBJV17KdmNNwQeTj8E74IbO/QnzGv 45 | VeLF3d5u8u8mzVEnizVTtxAvrNwdhWmOoRdTnPICX5CQHqhRPNdrLM6Co5jjdO3b 46 | nQKCAQAtrqN3+DvOlWlq9Onyv/Pal9YZhy8R0sH6eYFfdt4CGwxdCgFzuybFEfqx 47 | bsn/kxbVYG4yDZ4SNOSdX4aihuvPkawv3i8EnD3cdvRUipb/+3HNHtCC9H0lg9C9 48 | cBmsRnuu5jxC+n7i3b3C/SzHgxXIVJye9xud8ZBWJLk9ANnxiw2t64b+aNuUI7u0 49 | qyydb+hcY1BBE9NogwwOVjiRKsnoEVO9LbYXbWBKHhbRw/gTnFRzn8fcjMG120f0 50 | RzBrTG7YCVFmmDohOqUZIzawDf/lf7o0tyiGmRdq9djgS8l0/0+kkMdVTGWS2IN+ 51 | XVEH0VishgwbYXOJCSBlZD7h0yS2 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /auth/auth-service/src/main/java/pro/teamlead/kubepay/auth/domain/service/AuthService.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.auth.domain.service; 2 | 3 | import pro.teamlead.kubepay.auth.api.domain.exception.InvalidCredentialsException; 4 | import pro.teamlead.kubepay.auth.api.domain.exception.UserAlreadyExistsException; 5 | import pro.teamlead.kubepay.auth.configuration.JwtConfiguration; 6 | import pro.teamlead.kubepay.auth.domain.model.JwtToken; 7 | import pro.teamlead.kubepay.auth.sdk.user.ServicePrincipal; 8 | import pro.teamlead.kubepay.auth.sdk.util.JwtTokenProvider; 9 | import pro.teamlead.kubepay.auth.sdk.authority.Authority; 10 | import pro.teamlead.kubepay.user.api.domain.exception.UserNotFoundException; 11 | import pro.teamlead.kubepay.user.api.domain.model.CreateUserRequest; 12 | import pro.teamlead.kubepay.user.client.UserClient; 13 | import io.micrometer.observation.annotation.Observed; 14 | import lombok.RequiredArgsConstructor; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.security.crypto.password.PasswordEncoder; 18 | import org.springframework.stereotype.Service; 19 | import org.springframework.web.server.ResponseStatusException; 20 | 21 | 22 | @Slf4j 23 | @Service 24 | @RequiredArgsConstructor 25 | @Observed 26 | public class AuthService { 27 | 28 | private final UserClient userClient; 29 | 30 | private final JwtTokenProvider jwtTokenProvider; 31 | 32 | private final ServicePrincipal servicePrincipal; 33 | 34 | private final JwtConfiguration jwtConfiguration; 35 | 36 | private final PasswordEncoder passwordEncoder; 37 | 38 | public JwtToken createUser(String user, String password) { 39 | 40 | if (isUserExists(user)) { 41 | throw new UserAlreadyExistsException(); 42 | } 43 | 44 | var request = new CreateUserRequest(user, passwordEncoder.encode(password)); 45 | 46 | userClient.createUser(servicePrincipal, request); 47 | 48 | var token = jwtTokenProvider.createToken(user, jwtConfiguration.getJwtTTL(), 49 | Authority.ROLE_USER); 50 | 51 | return new JwtToken(token); 52 | } 53 | 54 | public JwtToken authUser(String user, String password) { 55 | 56 | var passwordHash = getPasswordHash(user); 57 | 58 | if (!passwordEncoder.matches(password, passwordHash)) { 59 | throw new InvalidCredentialsException(); 60 | } 61 | 62 | var token = jwtTokenProvider.createToken(user, jwtConfiguration.getJwtTTL(), 63 | Authority.ROLE_USER); 64 | 65 | return new JwtToken(token); 66 | } 67 | 68 | private boolean isUserExists(String user) { 69 | try { 70 | userClient.getUserInfo(servicePrincipal, user); 71 | return true; 72 | } catch (UserNotFoundException e) { 73 | return false; 74 | } catch (Exception e) { 75 | throw new ResponseStatusException( 76 | HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e); 77 | } 78 | } 79 | 80 | private String getPasswordHash(String user) { 81 | try { 82 | return userClient.getPasswordHash(servicePrincipal, user); 83 | } catch (UserNotFoundException e) { 84 | throw new InvalidCredentialsException(); 85 | } catch (Exception e) { 86 | throw new ResponseStatusException( 87 | HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /common/swagger/src/main/java/pro/teamlead/kubepay/common/swagger/configuration/SwaggerConfiguration.java: -------------------------------------------------------------------------------- 1 | package pro.teamlead.kubepay.common.swagger.configuration; 2 | 3 | import io.swagger.v3.oas.models.Components; 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | import io.swagger.v3.oas.models.info.Info; 6 | import io.swagger.v3.oas.models.security.SecurityRequirement; 7 | import io.swagger.v3.oas.models.security.SecurityScheme; 8 | import io.swagger.v3.oas.models.servers.Server; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 14 | import org.springframework.boot.info.BuildProperties; 15 | import org.springframework.boot.info.GitProperties; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | 19 | import java.util.List; 20 | 21 | @Configuration 22 | @ConditionalOnWebApplication 23 | @ConditionalOnClass(OpenAPI.class) 24 | public class SwaggerConfiguration { 25 | 26 | @Value("${spring.application.name}") 27 | private String serviceName; 28 | 29 | @Value("${spring.application.description}") 30 | private String title; 31 | 32 | @Value("${server.standalone}") 33 | private boolean standalone; 34 | 35 | @Bean 36 | @ConditionalOnMissingBean 37 | public OpenAPI openAPI(@Autowired(required = false) BuildProperties buildProperties, 38 | @Autowired(required = false) GitProperties gitProperties) { 39 | 40 | var version = buildProperties != null ? 41 | buildProperties.getVersion() : "?"; 42 | 43 | var git = gitProperties != null ? 44 | String.format("Git: %s (%s) %s", 45 | gitProperties.getShortCommitId(), 46 | gitProperties.getBranch(), 47 | gitProperties.get("commit.message.full")) : "No Git info"; 48 | 49 | var description = String.format(""" 50 | %s Documentation
51 | Version: %s
52 | %s
53 | Standalone: %s 54 | """, 55 | title, 56 | version, 57 | git, 58 | standalone 59 | ); 60 | 61 | var securitySchemeName = "bearerAuth"; 62 | var server = new Server(); 63 | 64 | if (standalone) { 65 | server.setUrl("/"); 66 | } else { 67 | server.setUrl("/" + serviceName); 68 | } 69 | 70 | return new OpenAPI() 71 | .servers(List.of(server)) 72 | .info((new Info()) 73 | .title(title) 74 | .description(description) 75 | .version(version)) 76 | .addSecurityItem(new SecurityRequirement() 77 | .addList(securitySchemeName)) 78 | .components(new Components() 79 | .addSecuritySchemes(securitySchemeName, new SecurityScheme() 80 | .name(securitySchemeName) 81 | .type(SecurityScheme.Type.HTTP) 82 | .scheme("bearer") 83 | .bearerFormat("JWT"))); 84 | } 85 | 86 | } 87 | --------------------------------------------------------------------------------