diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/proxy/WalletProxyImpl.kt b/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/proxy/WalletProxyImpl.kt index 480ee1f1d..1ba445d11 100644 --- a/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/proxy/WalletProxyImpl.kt +++ b/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/proxy/WalletProxyImpl.kt @@ -11,11 +11,15 @@ import java.net.URI import java.time.LocalDateTime inline fun typeRef(): ParameterizedTypeReference = object : ParameterizedTypeReference() {} -data class TransferResult( - val date: Long, - val sourceBalanceBeforeAction: Amount, - val sourceBalanceAfterAction: Amount, - val amount: Amount +data class TransferResult(val date: Long + , val sourceUuid: String + , val sourceWalletType: String + , val sourceBalanceBeforeAction: Amount + , val sourceBalanceAfterAction: Amount + , val amount: Amount + , val destUuid: String + , val destWalletType: String + , val receivedAmount: Amount ) data class Amount(val currency: Currency, val amount: BigDecimal) diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/ExtendedEventListenerProvider.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/ExtendedEventListenerProvider.kt index a29f9ffc3..91f2fbec8 100644 --- a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/ExtendedEventListenerProvider.kt +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/ExtendedEventListenerProvider.kt @@ -26,8 +26,8 @@ class ExtendedEventListenerProvider(private val session: KeycloakSession) : Even val username: String, val enabled: Boolean, val emailVerified: Boolean, - val firstName: String, - val lastName: String, + val firstName: String?, + val lastName: String?, val email: String ) diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt index e80ade9ca..e5c122c99 100644 --- a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt @@ -2,12 +2,12 @@ package co.nilin.opex.auth.gateway.model class UserCreatedEvent: AuthEvent { lateinit var uuid: String - lateinit var firstName: String - lateinit var lastName: String + var firstName: String? = null + var lastName: String? = null lateinit var email: String - constructor(uuid: String, firstName: String, lastName: String, email: String) : super() { + constructor(uuid: String, firstName: String?, lastName: String?, email: String) : super() { this.uuid = uuid this.firstName = firstName this.lastName = lastName diff --git a/UserManagement/keycloak-gateway/src/main/resources/opex-realm.json b/UserManagement/keycloak-gateway/src/main/resources/opex-realm.json index bccc3db87..ce3da1d48 100644 --- a/UserManagement/keycloak-gateway/src/main/resources/opex-realm.json +++ b/UserManagement/keycloak-gateway/src/main/resources/opex-realm.json @@ -200,10 +200,10 @@ "query-users", "query-realms", "view-users", - "manage-authorization", "impersonation", - "view-events", - "manage-events" + "manage-authorization", + "manage-events", + "view-events" ] } }, @@ -378,7 +378,17 @@ ] } }, - "groups": [], + "groups": [ + { + "id": "91c5e3b8-3142-400c-b19f-f54b3915e8bc", + "name": "finance-admin", + "path": "/finance-admin", + "attributes": {}, + "realmRoles": [], + "clientRoles": {}, + "subGroups": [] + } + ], "defaultRoles": [ "offline_access", "uma_authorization" @@ -565,8 +575,8 @@ "clientAuthenticatorType": "client-secret", "secret": "fae6f87e-5b66-435c-b5aa-fd42c7641604", "redirectUris": [ - "http://localhost:3000/*", "/realms/opex/account/*", + "http://localhost:3000/*", "https://opex.dev/*" ], "webOrigins": [ @@ -613,12 +623,27 @@ "consentRequired": false, "config": { "user.session.note": "clientHost", + "userinfo.token.claim": "true", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "clientHost", "jsonType.label": "String" } }, + { + "id": "586da23c-ccf3-41fe-a520-7b7ad01355ae", + "name": "Group Mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-group-membership-mapper", + "consentRequired": false, + "config": { + "full.path": "false", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "userinfo.token.claim": "true" + } + }, { "id": "6b335962-bc9b-4095-ad36-48163e443a6f", "name": "Client ID", @@ -627,6 +652,7 @@ "consentRequired": false, "config": { "user.session.note": "clientId", + "userinfo.token.claim": "true", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "clientId", @@ -641,6 +667,7 @@ "consentRequired": false, "config": { "user.session.note": "clientAddress", + "userinfo.token.claim": "true", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "clientAddress", @@ -680,7 +707,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "secret": "**********", + "secret": "fae6f87e-5b66-435c-b5aa-fd42c7641604", "redirectUris": [], "webOrigins": [ "*" @@ -717,6 +744,22 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "b03c07a5-a9e8-4d83-9426-f1f04db690c3", + "name": "Group Mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-group-membership-mapper", + "consentRequired": false, + "config": { + "full.path": "false", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "userinfo.token.claim": "true" + } + } + ], "defaultClientScopes": [ "web-origins", "trust", @@ -741,7 +784,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "secret": "**********", + "secret": "fae6f87e-5b66-435c-b5aa-fd42c7641604", "redirectUris": [ "http://localhost:3000/*", "https://opex.dev/*" @@ -802,7 +845,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "secret": "**********", + "secret": "fae6f87e-5b66-435c-b5aa-fd42c7641604", "redirectUris": [ "http://localhost:8082/new-client/login/oauth2/code/custom", "http://localhost:3000/*", @@ -868,7 +911,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "secret": "**********", + "secret": "fae6f87e-5b66-435c-b5aa-fd42c7641604", "redirectUris": [], "webOrigins": [], "notBefore": 0, @@ -909,7 +952,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "secret": "**********", + "secret": "fae6f87e-5b66-435c-b5aa-fd42c7641604", "redirectUris": [ "/admin/opex/console/*" ], @@ -1606,14 +1649,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "oidc-address-mapper", - "oidc-sha256-pairwise-sub-mapper", - "oidc-usermodel-property-mapper", "saml-role-list-mapper", - "saml-user-property-mapper", - "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", "oidc-full-name-mapper", - "saml-user-attribute-mapper" + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-property-mapper" ] } }, @@ -1633,14 +1676,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "saml-user-property-mapper", - "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", + "oidc-address-mapper", "oidc-usermodel-property-mapper", + "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", - "oidc-address-mapper", - "oidc-full-name-mapper" + "oidc-full-name-mapper", + "saml-role-list-mapper" ] } } @@ -1688,7 +1731,7 @@ "supportedLocales": [], "authenticationFlows": [ { - "id": "1994872d-58ab-40e9-9f3c-bd94d498fde9", + "id": "d5324c32-660b-466c-8b14-a2de428b3010", "alias": "Handle Existing Account", "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId": "basic-flow", @@ -1712,7 +1755,7 @@ ] }, { - "id": "2d9ee8ef-c756-41b6-a504-2b500650a966", + "id": "22ca78fc-3fe1-418e-8ac5-7cbf41998082", "alias": "Handle Existing Account - Alternatives - 0", "description": "Subflow of Handle Existing Account with alternative executions", "providerId": "basic-flow", @@ -1736,7 +1779,7 @@ ] }, { - "id": "424ef9c8-da39-4813-9093-964f16f8eed9", + "id": "a3f59bfb-7a49-41df-b732-897339ae062d", "alias": "Verify Existing Account by Re-authentication", "description": "Reauthentication of existing account", "providerId": "basic-flow", @@ -1760,7 +1803,7 @@ ] }, { - "id": "5321e985-f444-4772-8a7d-44eeab19d8fa", + "id": "ff973911-a4f4-4e7f-867b-eeac2d11fb90", "alias": "Verify Existing Account by Re-authentication - auth-otp-form - Conditional", "description": "Flow to determine if the auth-otp-form authenticator should be used or not.", "providerId": "basic-flow", @@ -1784,7 +1827,7 @@ ] }, { - "id": "06842669-a459-456d-b9fb-11d9236c3377", + "id": "ba9859bb-5ebf-457d-8c33-2fdf14ec0bcc", "alias": "browser", "description": "browser based authentication", "providerId": "basic-flow", @@ -1822,7 +1865,7 @@ ] }, { - "id": "e869296d-430f-4721-a5cd-2d5fe66294e0", + "id": "d95536b1-aee4-4931-97c1-bea50bab2697", "alias": "clients", "description": "Base authentication for clients", "providerId": "client-flow", @@ -1860,7 +1903,7 @@ ] }, { - "id": "0e7775f8-7b6d-41a2-9f55-72416864fd87", + "id": "928c7484-6fc1-4c29-8014-1f02a8b99442", "alias": "direct grant", "description": "OpenID Connect Resource Owner Grant", "providerId": "basic-flow", @@ -1891,7 +1934,7 @@ ] }, { - "id": "5fa95cf1-9d79-4066-93bb-86da3c4a1fa5", + "id": "e78b6c52-7613-4eea-acbf-a9810057e2dc", "alias": "direct grant - direct-grant-validate-otp - Conditional", "description": "Flow to determine if the direct-grant-validate-otp authenticator should be used or not.", "providerId": "basic-flow", @@ -1915,7 +1958,7 @@ ] }, { - "id": "89c5f0a2-310b-4388-b9fc-be14f35cb36a", + "id": "b05b9dbd-b7a1-49b4-a193-665bd8bce0c5", "alias": "docker auth", "description": "Used by Docker clients to authenticate against the IDP", "providerId": "basic-flow", @@ -1932,7 +1975,7 @@ ] }, { - "id": "ba4ec48d-de84-4847-9c53-cb16a9114cf8", + "id": "74e18bf8-15d7-4b63-8498-6fee3be39798", "alias": "first broker login", "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId": "basic-flow", @@ -1957,7 +2000,7 @@ ] }, { - "id": "fdccc5f5-2ef8-4e94-acd2-34ed9b12c3d1", + "id": "68c70e7f-4049-4ba4-a509-afe22c4719bc", "alias": "first broker login - Alternatives - 0", "description": "Subflow of first broker login with alternative executions", "providerId": "basic-flow", @@ -1982,7 +2025,7 @@ ] }, { - "id": "ceef545f-aade-4066-9aa2-13d21abda764", + "id": "f0576988-077e-4853-849c-e7e4085a6ca9", "alias": "forms", "description": "Username, password, otp and other auth forms.", "providerId": "basic-flow", @@ -2006,7 +2049,7 @@ ] }, { - "id": "031b60ca-098a-4301-acdc-5e91e3c00de6", + "id": "358fddc7-333e-4dae-88b0-c3c398b90bf5", "alias": "forms - auth-otp-form - Conditional", "description": "Flow to determine if the auth-otp-form authenticator should be used or not.", "providerId": "basic-flow", @@ -2030,7 +2073,7 @@ ] }, { - "id": "303db066-ce61-40e1-99c5-d8dc28903585", + "id": "5bb5bf9e-2ce2-407c-a9df-a022c582fd27", "alias": "http challenge", "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId": "basic-flow", @@ -2068,7 +2111,7 @@ ] }, { - "id": "52600ab5-b57f-4ad6-85ea-397afe32cbda", + "id": "d4092961-f513-4d0a-8c57-af04f33563ca", "alias": "registration", "description": "registration flow", "providerId": "basic-flow", @@ -2086,7 +2129,7 @@ ] }, { - "id": "895289c7-ab6f-4388-9088-1ccb21725996", + "id": "ddeade11-8049-4a18-ae2d-df2b5d621959", "alias": "registration form", "description": "registration form", "providerId": "form-flow", @@ -2124,7 +2167,7 @@ ] }, { - "id": "dcc7bdcc-548c-4b40-af94-b3cad69a3b87", + "id": "68406db8-97f1-4fe9-a3a3-3c53389e74d3", "alias": "reset credentials", "description": "Reset credentials for a user if they forgot their password or something", "providerId": "basic-flow", @@ -2162,7 +2205,7 @@ ] }, { - "id": "96dff9e4-56be-4c62-a146-2507d00b88c9", + "id": "2771dfd6-c629-41bd-9e3e-bdea6bdcbc4a", "alias": "reset credentials - reset-otp - Conditional", "description": "Flow to determine if the reset-otp authenticator should be used or not.", "providerId": "basic-flow", @@ -2186,7 +2229,7 @@ ] }, { - "id": "fc10429a-01a5-4e8a-bad3-7b4794ee7688", + "id": "1dd04ebd-d60f-4a2a-b662-62f2c646d057", "alias": "saml ecp", "description": "SAML ECP Profile Authentication Flow", "providerId": "basic-flow", @@ -2205,14 +2248,14 @@ ], "authenticatorConfig": [ { - "id": "cda25df3-6d3b-49f9-bb52-b24ab4d1ee57", + "id": "880e3447-bf92-4ea2-b454-f4e2644a14a3", "alias": "create unique user config", "config": { "require.password.update.after.registration": "false" } }, { - "id": "fbf2034e-a602-44d7-8c11-8d27450f930b", + "id": "4f2cd341-2571-4611-aa14-b80898dc5d75", "alias": "review profile config", "config": { "update.profile.on.first.login": "missing" diff --git a/Wallet/wallet-app/pom.xml b/Wallet/wallet-app/pom.xml index 75143ea27..82f80de08 100644 --- a/Wallet/wallet-app/pom.xml +++ b/Wallet/wallet-app/pom.xml @@ -123,6 +123,11 @@ springfox-boot-starter 3.0.0 + + co.nilin.opex + interceptors + ${utility.version} + diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/RestConfig.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/RestConfig.kt new file mode 100644 index 000000000..be0640005 --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/RestConfig.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.wallet.app.config + +import co.nilin.opex.utility.interceptor.FormDataWorkaroundFilter +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.server.WebFilter +@Configuration +class RestConfig { + @Bean + fun formDataWebFilter(): WebFilter { + return FormDataWorkaroundFilter() + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt index a87dd4114..0153ee983 100644 --- a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt @@ -1,9 +1,12 @@ package co.nilin.opex.wallet.app.config +import net.minidev.json.JSONArray import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean +import org.springframework.security.authorization.AuthorizationDecision import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.oauth2.jwt.Jwt import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder import org.springframework.security.web.server.SecurityWebFilterChain @@ -17,15 +20,26 @@ class SecurityConfig(private val webClient: WebClient) { @Bean fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { + http.csrf().disable() - .authorizeExchange() - .pathMatchers("/balanceOf/**").hasAuthority("SCOPE_trust") - .pathMatchers("/owner/**").hasAuthority("SCOPE_trust") - .pathMatchers("/**").permitAll() - .anyExchange().authenticated() - .and() - .oauth2ResourceServer() - .jwt() + .authorizeExchange() + .pathMatchers("/balanceOf/**").hasAuthority("SCOPE_trust") + .pathMatchers("/owner/**").hasAuthority("SCOPE_trust") + .pathMatchers("/withdraw").hasAuthority("SCOPE_trust") + .pathMatchers("/withdraw/**").hasAuthority("SCOPE_trust") + .pathMatchers("/admin/**").access { mono, authorizationContext -> + mono.map { auth -> + auth.authorities.any { authority -> authority.authority == "SCOPE_trust" } + && ((auth.principal as Jwt) + .claims.get("groups") as JSONArray).contains("finance-admin") + }.map { granted -> + AuthorizationDecision(granted) + } + } + .anyExchange().authenticated() + .and() + .oauth2ResourceServer() + .jwt() return http.build() } diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt index 714a9d207..65ee083f9 100644 --- a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt @@ -1,5 +1,6 @@ package co.nilin.opex.wallet.app.controller +import co.nilin.opex.wallet.core.spi.CurrencyService import co.nilin.opex.wallet.core.spi.WalletManager import co.nilin.opex.wallet.core.spi.WalletOwnerManager import io.swagger.annotations.ApiResponse @@ -14,7 +15,9 @@ import java.security.Principal @RestController class BalanceController( - val walletManager: WalletManager, val walletOwnerManager: WalletOwnerManager + val walletManager: WalletManager + , val walletOwnerManager: WalletOwnerManager + , val currencyService: CurrencyService ) { val logger = LoggerFactory.getLogger(BalanceController::class.java) @@ -38,7 +41,7 @@ class BalanceController( ): BalanceResponse { val owner = walletOwnerManager.findWalletOwner(principal.name) if (owner != null) { - val wallet = walletManager.findWalletByOwnerAndCurrencyAndType(owner, walletType, Symbol(currency)) + val wallet = walletManager.findWalletByOwnerAndCurrencyAndType(owner, walletType, currencyService.getCurrency(currency)) return BalanceResponse(wallet?.balance()?.amount ?: BigDecimal.ZERO) } return BalanceResponse(BigDecimal.ZERO) diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/InquiryController.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/InquiryController.kt index 1aff512a9..4af72dfb9 100644 --- a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/InquiryController.kt +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/InquiryController.kt @@ -1,6 +1,7 @@ package co.nilin.opex.wallet.app.controller import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.spi.CurrencyService import co.nilin.opex.wallet.core.spi.WalletManager import co.nilin.opex.wallet.core.spi.WalletOwnerManager import io.swagger.annotations.ApiResponse @@ -14,7 +15,9 @@ import java.math.BigDecimal @RestController class InquiryController( - val walletManager: WalletManager, val walletOwnerManager: WalletOwnerManager + val walletManager: WalletManager + , val walletOwnerManager: WalletOwnerManager + , val currencyService: CurrencyService ) { val logger = LoggerFactory.getLogger(InquiryController::class.java) @@ -39,7 +42,7 @@ class InquiryController( logger.info("canFullFill: {} {} {} {}", uuid, currency, walletType, amount) val owner = walletOwnerManager.findWalletOwner(uuid) if (owner != null) { - val wallet = walletManager.findWalletByOwnerAndCurrencyAndType(owner, walletType, Symbol(currency)) + val wallet = walletManager.findWalletByOwnerAndCurrencyAndType(owner, walletType, currencyService.getCurrency(currency)) if (wallet != null) { return BooleanResponse( walletManager.isWithdrawAllowed(wallet, amount) diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/Symbol.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/Symbol.kt deleted file mode 100644 index cff978685..000000000 --- a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/Symbol.kt +++ /dev/null @@ -1,17 +0,0 @@ -package co.nilin.opex.wallet.app.controller - -import co.nilin.opex.wallet.core.model.Currency - -class Symbol(val symbol_: String): Currency { - override fun getSymbol(): String { - TODO("Not yet implemented") - } - - override fun getName(): String { - return symbol_ - } - - override fun getPrecision(): Int { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/TransferController.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/TransferController.kt index 03f0d2a80..0a386c48b 100644 --- a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/TransferController.kt +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/TransferController.kt @@ -4,6 +4,7 @@ import co.nilin.opex.wallet.core.inout.TransferCommand import co.nilin.opex.wallet.core.inout.TransferResult import co.nilin.opex.wallet.core.model.Amount import co.nilin.opex.wallet.core.service.TransferService +import co.nilin.opex.wallet.core.spi.CurrencyService import co.nilin.opex.wallet.core.spi.WalletManager import co.nilin.opex.wallet.core.spi.WalletOwnerManager import io.swagger.annotations.ApiResponse @@ -11,13 +12,17 @@ import io.swagger.annotations.Example import io.swagger.annotations.ExampleProperty import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController import java.lang.IllegalArgumentException import java.math.BigDecimal @RestController class TransferController( - val transferService: TransferService, val walletManager: WalletManager, val walletOwnerManager: WalletOwnerManager + val transferService: TransferService + , val currencyService: CurrencyService + , val walletManager: WalletManager + , val walletOwnerManager: WalletOwnerManager ) { @PostMapping("/transfer/{amount}_{symbol}/from/{senderUuid}_{senderWalletType}/to/{receiverUuid}_{receiverWalletType}") @ApiResponse( @@ -40,21 +45,25 @@ class TransferController( @PathVariable("description") description: String?, @PathVariable("transferRef") transferRef: String? ): TransferResult { + if ( senderWalletType.equals("cashout") + || receiverWalletType.equals("cashout") ) + throw IllegalArgumentException("Use withdraw services") + val currency = currencyService.getCurrency(symbol) val sourceOwner = walletOwnerManager.findWalletOwner(senderUuid) ?: throw IllegalArgumentException() val sourceWallet = - walletManager.findWalletByOwnerAndCurrencyAndType(sourceOwner, senderWalletType, Symbol(symbol)) + walletManager.findWalletByOwnerAndCurrencyAndType(sourceOwner, senderWalletType, currency) ?: throw IllegalArgumentException() val receiverOwner = walletOwnerManager.findWalletOwner(receiverUuid) ?: walletOwnerManager.createWalletOwner( senderUuid, - "noset", + "not set", "" ) val receiverWallet = walletManager.findWalletByOwnerAndCurrencyAndType( - receiverOwner, receiverWalletType, Symbol(symbol) + receiverOwner, receiverWalletType, currency ) ?: walletManager.createWallet( receiverOwner, - Amount(Symbol(symbol), BigDecimal.ZERO), - Symbol(symbol), + Amount(currency, BigDecimal.ZERO), + currency, receiverWalletType ) return transferService.transfer( @@ -62,7 +71,7 @@ class TransferController( sourceWallet, receiverWallet, Amount(sourceWallet.currency(), amount), - description, transferRef + description, transferRef, emptyMap() ) ) } diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt new file mode 100644 index 000000000..e04204f23 --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt @@ -0,0 +1,219 @@ +package co.nilin.opex.wallet.app.controller + +import co.nilin.opex.port.wallet.postgres.dao.WithdrawRepository +import co.nilin.opex.port.wallet.postgres.model.WithdrawModel +import co.nilin.opex.wallet.core.inout.TransferCommand +import co.nilin.opex.wallet.core.inout.TransferResult +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.service.TransferService +import co.nilin.opex.wallet.core.spi.CurrencyService +import co.nilin.opex.wallet.core.spi.WalletManager +import co.nilin.opex.wallet.core.spi.WalletOwnerManager +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactive.awaitFirstOrElse +import org.springframework.beans.factory.annotation.Value +import org.springframework.web.bind.annotation.* +import java.lang.IllegalArgumentException +import java.lang.RuntimeException +import java.math.BigDecimal +import java.security.Principal + +@RestController +class WithdrawController( + val withdrawRepository: WithdrawRepository + , val transferService: TransferService + , val walletManager: WalletManager + , val walletOwnerManager: WalletOwnerManager + , val currencyService: CurrencyService + , @Value("\${app.system.uuid}") val systemUuid: String +) { + + @GetMapping("/admin/withdraw") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun searchWithdraws( + @RequestParam("uuid", required = false) uuid: String?, + @RequestParam("transaction_ref", required = false) txRef: String?, + @RequestParam("dest_transaction_ref", required = false) destTxRef: String?, + @RequestParam("dest_address", required = false) destAddress: String?, + @RequestParam("status", required = false) status: List? + ): List { + return withdrawRepository + .findByCriteria(uuid, txRef, destTxRef, destAddress, status?.isEmpty() ?: true, status ?: listOf("")) + .toList() + } + + @GetMapping("/withdraw") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun getMyWithdraws( + principal: Principal, + @RequestParam("transaction_ref", required = false) txRef: String?, + @RequestParam("dest_transaction_ref", required = false) destTxRef: String?, + @RequestParam("dest_address", required = false) destAddress: String?, + @RequestParam("status", required = false) status: List? + ): List { + return withdrawRepository + .findByCriteria(principal.name, txRef, destTxRef, destAddress, status?.isEmpty() ?: true, status ?: listOf("")) + .toList() + } + + @PostMapping( + "/withdraw/{amount}_{symbol}" + ) + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun requestWithdraw( + principal: Principal, + @PathVariable("symbol") symbol: String, + @PathVariable("amount") amount: BigDecimal, + @PathVariable("description") description: String?, + @PathVariable("transferRef") transferRef: String?, + @RequestParam("fee", required = false) fee: Double?, + @RequestParam("destCurrency") destCurrency: String?, + @RequestParam("destAddress") destAddress: String?, + @RequestParam("destNote", required = false) destNote: String?, + ): TransferResult { + val currency = currencyService.getCurrency(symbol) + val owner = walletOwnerManager.findWalletOwner(principal.name) ?: throw IllegalArgumentException() + val sourceWallet = + walletManager.findWalletByOwnerAndCurrencyAndType(owner, "main", currency) + ?: throw IllegalArgumentException() + val receiverWallet = walletManager.findWalletByOwnerAndCurrencyAndType( + owner, "cashout", currency + ) ?: walletManager.createWallet( + owner, + Amount(currency, BigDecimal.ZERO), + currency, + "cashout" + ) + val allAdditionalData = mutableMapOf() + allAdditionalData["fee"] = fee?.toString() + allAdditionalData["destCurrency"] = destCurrency + allAdditionalData["destAddress"] = destAddress + allAdditionalData["destNote"] = destNote + allAdditionalData["description"] = description + return transferService.transfer( + TransferCommand( + sourceWallet, + receiverWallet, + Amount(sourceWallet.currency(), amount), + description, transferRef, allAdditionalData + ) + ) + } + + @PostMapping( + "/admin/withdraw/{id}/reject" + ) + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun rejectWithdraw( + @PathVariable("id") withdrawId: String, + @RequestParam("statusReason", required = false) statusReason: String?, + @RequestParam("destNote", required = false) destNote: String? + ): TransferResult { + val withdraw = withdrawRepository.findById(withdrawId).awaitFirstOrElse { throw RuntimeException("No matching withdraw request") } + val sourceWallet = walletManager.findWalletById(withdraw.wallet) ?: throw RuntimeException("Wallet not found") + val receiverWallet = walletManager.findWalletByOwnerAndCurrencyAndType( + sourceWallet.owner(), "main", sourceWallet.currency() + ) ?: walletManager.createWallet( + sourceWallet.owner(), + Amount(sourceWallet.currency(), BigDecimal.ZERO), + sourceWallet.currency(), + "main" + ) + val allAdditionalData = mutableMapOf() + allAdditionalData["statusReason"] = statusReason + allAdditionalData["destNote"] = destNote + allAdditionalData["transactionId"] = withdraw.transactionId + allAdditionalData["status"] = "REJECTED" + return transferService.transfer( + TransferCommand( + sourceWallet, + receiverWallet, + Amount(sourceWallet.currency(), withdraw.amount), + withdraw.description, allAdditionalData["transactionRef"], allAdditionalData + ) + ) + } + + @PostMapping( + "/admin/withdraw/{id}/accept" + ) + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun acceptWithdraw( + @PathVariable("id") withdrawId: String, + @RequestParam("destTransactionRef", required = false) destTransactionRef: String?, + @RequestParam("destNote", required = false) destNote: String? + ): TransferResult { + val system = walletOwnerManager.findWalletOwner(systemUuid) ?: throw IllegalArgumentException() + val withdraw = withdrawRepository.findById(withdrawId).awaitFirstOrElse { throw RuntimeException("No matching withdraw request") } + val sourceWallet = walletManager.findWalletById(withdraw.wallet) ?: throw RuntimeException("Wallet not found") + val receiverWallet = walletManager.findWalletByOwnerAndCurrencyAndType( + system, "main", sourceWallet.currency() + ) ?: walletManager.createWallet( + system, + Amount(sourceWallet.currency(), BigDecimal.ZERO), + sourceWallet.currency(), + "main" + ) + val allAdditionalData = mutableMapOf() + allAdditionalData["destNote"] = destNote + allAdditionalData["destTransactionRef"] = destTransactionRef + allAdditionalData["transactionId"] = withdraw.transactionId + allAdditionalData["status"] = "DONE" + return transferService.transfer( + TransferCommand( + sourceWallet, + receiverWallet, + Amount(sourceWallet.currency(), withdraw.amount), + withdraw.description, allAdditionalData["transactionRef"], allAdditionalData + ) + ) + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/listener/WalletListenerImpl.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/listener/WalletListenerImpl.kt index 083b9d9aa..1894c7db8 100644 --- a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/listener/WalletListenerImpl.kt +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/listener/WalletListenerImpl.kt @@ -1,24 +1,54 @@ package co.nilin.opex.wallet.app.listener +import co.nilin.opex.port.wallet.postgres.dao.WithdrawRepository +import co.nilin.opex.port.wallet.postgres.model.WithdrawModel import co.nilin.opex.wallet.core.model.Amount import co.nilin.opex.wallet.core.model.Wallet import co.nilin.opex.wallet.core.spi.WalletListener +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrElse import org.springframework.stereotype.Component +import java.lang.RuntimeException import java.math.BigDecimal @Component -class WalletListenerImpl: WalletListener { - override fun onDeposit( +class WalletListenerImpl(val withdrawRepository: WithdrawRepository) : WalletListener { + override suspend fun onDeposit( me: Wallet, sourceWallet: Wallet, amount: Amount, finalAmount: BigDecimal, - transaction: String + transaction: String, + additionalData: Map? ) { - + if (me.type().equals("cashout")) { + val fee = (additionalData?.get("fee") ?: "0").toBigDecimal() + withdrawRepository.save( + WithdrawModel( + null, transaction , me.id()!!, finalAmount, fee, finalAmount.subtract(fee), additionalData?.get("destCurrency"), additionalData?.get("destAddress"), additionalData?.get("destNote"), null, additionalData?.get("description"), null, "CREATED" + ) + ).awaitFirst() + } } - override fun onWithdraw(me: Wallet, destWallet: Wallet, amount: Amount, transaction: String) { - + override suspend fun onWithdraw(me: Wallet, destWallet: Wallet, amount: Amount, transaction: String, additionalData: Map?) { + if (me.type().equals("cashout")) { + val withdrawModel = withdrawRepository.findByWalletAndTransactionId( + me.id()!!, additionalData!!.get("transactionId") ?: throw RuntimeException("transactionId is required") + ).awaitFirstOrElse { throw RuntimeException("No matching withdraw request") } + if (withdrawModel!!.status != "CREATED") { + throw RuntimeException("This withdraw request processed before") + } + val newStatus = additionalData.get("status") ?: throw RuntimeException("status is required") + withdrawModel.status = newStatus + withdrawModel.statusReason = additionalData.get("statusReason") + if ( additionalData.get("destNote") != null) { + withdrawModel.destNote += "\n---------------\n" + additionalData.get("destNote") + } + if (newStatus == "DONE") { + withdrawModel.destTransactionRef = additionalData.get("destTransactionRef") + } + withdrawRepository.save(withdrawModel).awaitFirst() + } } } \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/UserRegistrationService.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/UserRegistrationService.kt index 349ad7523..fe7286957 100644 --- a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/UserRegistrationService.kt +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/UserRegistrationService.kt @@ -1,9 +1,9 @@ package co.nilin.opex.wallet.app.service import co.nilin.opex.auth.gateway.model.UserCreatedEvent -import co.nilin.opex.wallet.app.controller.Symbol import co.nilin.opex.wallet.core.model.Amount import co.nilin.opex.wallet.core.model.Wallet +import co.nilin.opex.wallet.core.spi.CurrencyService import co.nilin.opex.wallet.core.spi.WalletManager import co.nilin.opex.wallet.core.spi.WalletOwnerManager import org.springframework.beans.factory.annotation.Value @@ -15,8 +15,9 @@ import java.math.BigDecimal class UserRegistrationService( val walletOwnerManager: WalletOwnerManager, val walletManager: WalletManager, + val currencyService: CurrencyService, @Value("\${app.gift.symbol}") - val symbol: Symbol, + val symbol: String, @Value("\${app.gift.amount}") val amount: BigDecimal ) { @@ -25,7 +26,7 @@ class UserRegistrationService( val owner = walletOwnerManager.createWalletOwner(event.uuid, "${event.firstName} ${event.lastName}", "1") - val btcSymbol = Symbol("btc") + val btcSymbol = currencyService.getCurrency("btc") //TODO REMOVE LATER walletManager.createWallet( owner, @@ -34,10 +35,11 @@ class UserRegistrationService( "main" ) + val giftSymbol = currencyService.getCurrency(symbol) return walletManager.createWallet( owner, - Amount(symbol, amount), - symbol, + Amount(giftSymbol, amount), + giftSymbol, "main" ) } diff --git a/Wallet/wallet-app/src/main/resources/application-docker.yml b/Wallet/wallet-app/src/main/resources/application-docker.yml index 4c9921286..4ed955f3b 100644 --- a/Wallet/wallet-app/src/main/resources/application-docker.yml +++ b/Wallet/wallet-app/src/main/resources/application-docker.yml @@ -15,10 +15,4 @@ spring: cloud: consul: host: ${CONSUL_HOST} - port: 8500 - main: - allow-bean-definition-overriding: true - -app: - auth: - cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs \ No newline at end of file + port: 8500 \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/resources/application.yml b/Wallet/wallet-app/src/main/resources/application.yml index 1f82b108a..4fba204a6 100644 --- a/Wallet/wallet-app/src/main/resources/application.yml +++ b/Wallet/wallet-app/src/main/resources/application.yml @@ -30,9 +30,13 @@ app: gift: symbol: usdt amount: 1000 + auth: + cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs + system: + uuid: 1 logging: level: - org.apache.kafka: DEBUG + org.apache.kafka: ERROR co.nilin: DEBUG reactor.netty.http.client: DEBUG diff --git a/Wallet/wallet-core/pom.xml b/Wallet/wallet-core/pom.xml index 366ecc638..a4d080f28 100644 --- a/Wallet/wallet-core/pom.xml +++ b/Wallet/wallet-core/pom.xml @@ -33,6 +33,12 @@ kotlin-stdlib-jdk8 + + org.springframework + spring-tx + provided + + org.springframework.boot spring-boot-starter-test diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferCommand.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferCommand.kt index 5f0891eaa..5752c1a52 100644 --- a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferCommand.kt +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferCommand.kt @@ -3,4 +3,4 @@ package co.nilin.opex.wallet.core.inout import co.nilin.opex.wallet.core.model.Amount import co.nilin.opex.wallet.core.model.Wallet -data class TransferCommand(val sourceWallet: Wallet, val destWallet: Wallet, val amount: Amount, val description: String?, val transferRef: String?) +data class TransferCommand(val sourceWallet: Wallet, val destWallet: Wallet, val amount: Amount, val description: String?, val transferRef: String?, val additionalData: Map?) diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferResult.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferResult.kt index 32988898d..9cdbb74dc 100644 --- a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferResult.kt +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferResult.kt @@ -3,4 +3,13 @@ package co.nilin.opex.wallet.core.inout import co.nilin.opex.wallet.core.model.Amount import java.time.LocalDateTime -data class TransferResult(val date: Long, val sourceBalanceBeforeAction: Amount, val sourceBalanceAfterAction: Amount, val amount: Amount) \ No newline at end of file +data class TransferResult(val date: Long +, val sourceUuid: String +, val sourceWalletType: String +, val sourceBalanceBeforeAction: Amount +, val sourceBalanceAfterAction: Amount +, val amount: Amount +, val destUuid: String +, val destWalletType: String +, val receivedAmount: Amount +) \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/TransferService.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/TransferService.kt index 05ffec62b..7ccfdef51 100644 --- a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/TransferService.kt +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/TransferService.kt @@ -14,6 +14,7 @@ import co.nilin.opex.wallet.core.spi.WalletListener import co.nilin.opex.wallet.core.spi.WalletManager import co.nilin.opex.wallet.core.spi.WalletOwnerManager import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional import java.time.LocalDateTime import java.util.Date @@ -25,7 +26,7 @@ class TransferService( val walletOwnerManager: WalletOwnerManager, val transactionManager: TransactionManager ) { - + @Transactional suspend fun transfer(transferCommand: TransferCommand): TransferResult { //pre transfer hook (dispatch pre transfer event) val srcWallet = transferCommand.sourceWallet @@ -64,12 +65,20 @@ class TransferService( ) ) //get the result and add to return result type - walletListener.onDeposit(destWallet, srcWallet, transferCommand.amount, amountToTransfer, tx) - walletListener.onWithdraw(srcWallet, destWallet, transferCommand.amount, tx) - + walletListener.onDeposit(destWallet, srcWallet, transferCommand.amount, amountToTransfer, tx, transferCommand.additionalData) + walletListener.onWithdraw(srcWallet, destWallet, transferCommand.amount, tx, transferCommand.additionalData) //post transfer hook(dispatch post transfer event) //notify balance change - return TransferResult(Date().time, srcWalletBalance, srcWallet.balance(), balance) + return TransferResult( + Date().time, srcWalletOwner.uuid() + , srcWallet.type() + , srcWalletBalance + , walletManager.findWalletById(srcWallet.id()!!)!!.balance() + , transferCommand.amount + , destWalletOwner.uuid() + , destWallet.type() + , Amount(destWallet.currency(), amountToTransfer) + ) } } \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/CurrencyService.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/CurrencyService.kt new file mode 100644 index 000000000..55dcf8207 --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/CurrencyService.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.wallet.core.spi + +import co.nilin.opex.wallet.core.model.Currency + +interface CurrencyService { + suspend fun getCurrency(symbol: String): Currency +} \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletListener.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletListener.kt index da58cc6cc..927cd3144 100644 --- a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletListener.kt +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletListener.kt @@ -5,6 +5,6 @@ import co.nilin.opex.wallet.core.model.Wallet import java.math.BigDecimal interface WalletListener { - fun onDeposit(me: Wallet, sourceWallet: Wallet, amount: Amount, finalAmount: BigDecimal, transaction: String) - fun onWithdraw(me: Wallet, destWallet: Wallet, amount: Amount, transaction: String) + suspend fun onDeposit(me: Wallet, sourceWallet: Wallet, amount: Amount, finalAmount: BigDecimal, transaction: String, additionalData: Map?) + suspend fun onWithdraw(me: Wallet, destWallet: Wallet, amount: Amount, transaction: String, additionalData: Map?) } \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt index 9ebdbbce8..97159a69f 100644 --- a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt @@ -20,4 +20,6 @@ interface WalletManager { currency: Currency, type: String ): Wallet + + suspend fun findWalletById(walletId: Long): Wallet? } \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/resources/application.properties b/Wallet/wallet-core/src/main/resources/application.properties deleted file mode 100644 index 8b1378917..000000000 --- a/Wallet/wallet-core/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt index b15485c56..5af714c43 100644 --- a/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt +++ b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt @@ -2,12 +2,12 @@ package co.nilin.opex.auth.gateway.model class UserCreatedEvent: AuthEvent { lateinit var uuid: String - lateinit var firstName: String - lateinit var lastName: String + var firstName: String? = null + var lastName: String? = null lateinit var email: String - constructor(uuid: String, firstName: String, lastName: String, email: String) : super() { + constructor(uuid: String, firstName: String?, lastName: String?, email: String) : super() { this.uuid = uuid this.firstName = firstName this.lastName = lastName @@ -17,6 +17,6 @@ class UserCreatedEvent: AuthEvent { constructor() : super() override fun toString(): String { - return "UserCreatedEvent(uuid='$uuid', firstName='$firstName', lastName='$lastName', email='$email')" + return "UserCreatedEvent(uuid='$uuid', firstName='${firstName}', lastName='$lastName', email='$email')" } } \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/pom.xml b/Wallet/wallet-ports/wallet-persister-postgres/pom.xml index 51d4de352..b58ebde0f 100644 --- a/Wallet/wallet-ports/wallet-persister-postgres/pom.xml +++ b/Wallet/wallet-ports/wallet-persister-postgres/pom.xml @@ -62,6 +62,11 @@ org.jetbrains.kotlinx kotlinx-coroutines-core + + com.fasterxml.jackson.core + jackson-annotations + provided + io.projectreactor reactor-test diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/config/PostgresConfig.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/config/PostgresConfig.kt index 2abec8c14..a7b644481 100644 --- a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/config/PostgresConfig.kt +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/config/PostgresConfig.kt @@ -82,6 +82,22 @@ class PostgresConfig(db: DatabaseClient) { CREATE TABLE IF NOT EXISTS wallet_config ( name VARCHAR(20) PRIMARY KEY, main_currency VARCHAR(25) NOT NULL + ); + + CREATE TABLE IF NOT EXISTS withdraws ( + id SERIAL PRIMARY KEY, + transaction_id VARCHAR(20) NOT NULL UNIQUE, + wallet numeric, + amount decimal, + fee decimal, + net_amount decimal, + dest_currency VARCHAR(20), + dest_address VARCHAR(80), + dest_notes VARCHAR(2000), + dest_transaction_ref VARCHAR(100), + description VARCHAR(2000), + status_reason VARCHAR(2000), + status VARCHAR(20) ); insert into wallet_owner(id, uuid, title, level) values(1, '1', 'system', 'basic') ON CONFLICT DO NOTHING; diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WithdrawRepository.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WithdrawRepository.kt new file mode 100644 index 000000000..feacaca27 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WithdrawRepository.kt @@ -0,0 +1,51 @@ +package co.nilin.opex.port.wallet.postgres.dao + +import co.nilin.opex.port.wallet.postgres.model.WithdrawModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface WithdrawRepository : ReactiveCrudRepository { + @Query("select * from withdraws where wallet = :wallet") + fun findByWallet( + @Param("wallet") wallet: Long + ): Flow + + @Query( + "select * from withdraws wth " + + " join wallet wm on wm.id = wth.wallet " + + " where wm.owner = :owner" + ) + fun findByOwner( + @Param("owner") owner: Long + ): Flow + + @Query( + "select * from withdraws wth " + + " join wallet wm on wm.id = wth.wallet " + + " join wallet_owner wo on wm.owner = wo.id " + + " where ( :owner is null or wo.uuid = :owner) " + + " and (:tx_id is null or wth.transaction_id = :tx_id )" + + " and (:dest_transaction_ref is null or wth.dest_transaction_ref = :dest_transaction_ref)"+ + " and (:dest_address is null or wth.dest_address = :dest_address)"+ + " and (:no_status IS TRUE or wth.status in (:status))" + ) + fun findByCriteria( + @Param("owner") ownerUuid: String?, + @Param("tx_id") withdrawId: String?, + @Param("dest_transaction_ref") destTxRef: String?, + @Param("dest_address") destAddress: String?, + @Param("no_status") noStatus: Boolean, + @Param("status") status: List? + ): Flow + + @Query("select * from withdraws where wallet = :wallet and transaction_id = :tx_id") + fun findByWalletAndTransactionId( + @Param("wallet") wallet: Long, @Param("tx_id") txId: String + ): Mono + +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/CurrencyServiceImpl.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/CurrencyServiceImpl.kt new file mode 100644 index 000000000..e9d147d7b --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/CurrencyServiceImpl.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.port.wallet.postgres.impl + +import co.nilin.opex.port.wallet.postgres.dao.CurrencyRepository +import co.nilin.opex.wallet.core.model.Currency +import co.nilin.opex.wallet.core.spi.CurrencyService +import kotlinx.coroutines.reactive.awaitFirst +import org.springframework.stereotype.Service + +@Service +class CurrencyServiceImpl(val currencyRepository: CurrencyRepository) : CurrencyService { + override suspend fun getCurrency(symbol: String): Currency { + return currencyRepository.findById(symbol).awaitFirst() + } +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/WalletManagerImpl.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/WalletManagerImpl.kt index e0d1b8d79..8e72dec1f 100644 --- a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/WalletManagerImpl.kt +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/WalletManagerImpl.kt @@ -187,4 +187,20 @@ class WalletManagerImpl( return wallet } + + override suspend fun findWalletById( + walletId: Long + ): Wallet? { + val walletModel = walletRepository.findById(walletId).awaitFirstOrNull() + if (walletModel == null) + return null + val existingCurrency = currencyRepository.findById(walletModel.currency).awaitFirst() + return SavedWallet( + walletModel.id!!, + walletOwnerRepository.findById(walletModel.owner).awaitFirst(), + Amount(existingCurrency, walletModel.balance), + existingCurrency, + walletModel.type + ) + } } \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/CurrencyModel.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/CurrencyModel.kt index 4786dba55..3beed44a9 100644 --- a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/CurrencyModel.kt +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/CurrencyModel.kt @@ -2,15 +2,16 @@ package co.nilin.opex.port.wallet.postgres.model import co.nilin.opex.wallet.core.model.Currency +import com.fasterxml.jackson.annotation.JsonIgnore import org.springframework.data.annotation.Id import org.springframework.data.relational.core.mapping.Column import org.springframework.data.relational.core.mapping.Table @Table("currency") class CurrencyModel( - @Id @Column("name") val name_: String, - @Column("symbol") val symbol_: String, - @Column("precision") val precision_: Int + @JsonIgnore @Id @Column("name") val name_: String, + @JsonIgnore @Column("symbol") val symbol_: String, + @JsonIgnore @Column("precision") val precision_: Int ): Currency { override fun getSymbol(): String { return symbol_ diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WithdrawModel.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WithdrawModel.kt new file mode 100644 index 000000000..dea70fee0 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WithdrawModel.kt @@ -0,0 +1,22 @@ +package co.nilin.opex.port.wallet.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table("withdraws") +class WithdrawModel(@Id var id: Long?, + @Column("transaction_id") val transactionId: String, + @Column("wallet") val wallet: Long, + @Column("amount") val amount: BigDecimal, + @Column("fee") val fee: BigDecimal, + @Column("net_amount") val netAmount: BigDecimal, + @Column("dest_currency") val destCurrency: String?, + @Column("dest_address") val destAddress: String?, + @Column("dest_notes") var destNote: String?, + @Column("dest_transaction_ref") var destTransactionRef: String?, + @Column("description") val description: String?, + @Column("status_reason") var statusReason: String?, + @Column("status") var status: String +) \ No newline at end of file