diff --git a/accountant/accountant-app/src/main/resources/application.yml b/accountant/accountant-app/src/main/resources/application.yml index 0107e6aaa..6e5e58484 100644 --- a/accountant/accountant-app/src/main/resources/application.yml +++ b/accountant/accountant-app/src/main/resources/application.yml @@ -13,9 +13,6 @@ spring: bootstrap-servers: ${KAFKA_IP_PORT:localhost:9092} consumer: group-id: accountant - redis: - hostname: ${REDIS_HOST:localhost} - port: 6379 r2dbc: url: r2dbc:postgresql://${DB_IP_PORT:localhost}/opex_accountant username: ${dbusername:opex} diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairConfig.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairConfig.kt index 6df4d4e82..7d1672e58 100644 --- a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairConfig.kt +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairConfig.kt @@ -5,5 +5,6 @@ class PairConfig( val leftSideWalletSymbol: String, //can be same as pair left side val rightSideWalletSymbol: String, //can be same as pair right side val leftSideFraction: Double, - val rightSideFraction: Double + val rightSideFraction: Double, + val rate: Double = 0.0 ) \ No newline at end of file diff --git a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/ApiApp.kt b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/ApiApp.kt index 60cf684dd..ba5850a24 100644 --- a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/ApiApp.kt +++ b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/ApiApp.kt @@ -7,7 +7,6 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2 @SpringBootApplication @ComponentScan("co.nilin.opex") -@EnableSwagger2 class ApiApp fun main(args: Array) { diff --git a/api/api-app/src/main/resources/application.yml b/api/api-app/src/main/resources/application.yml index cf33eb4c5..a1487f4d9 100644 --- a/api/api-app/src/main/resources/application.yml +++ b/api/api-app/src/main/resources/application.yml @@ -13,9 +13,6 @@ spring: bootstrap-servers: ${KAFKA_IP_PORT:localhost:9092} consumer: group-id: api - redis: - host: ${REDIS_HOST:localhost} - port: 6379 r2dbc: url: r2dbc:postgresql://${DB_IP_PORT:localhost}/opex_api username: ${dbusername:opex} @@ -51,7 +48,7 @@ app: accountant: url: lb://opex-accountant matching-gateway: - url: lb://opex-gateway + url: lb://opex-matching-gateway wallet: url: lb://opex-wallet opex-bc-gateway: @@ -59,4 +56,4 @@ app: auth: cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs -swagger.authUrl: https://api.opex.dev \ No newline at end of file +swagger.authUrl: https://api.opex.dev diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt index 240c7cd9a..0e36d28e5 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt @@ -19,7 +19,6 @@ class SecurityConfig(private val webClient: WebClient) { fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { http.csrf().disable() .authorizeExchange() - .pathMatchers("/hello").permitAll() .pathMatchers("/actuator/**").permitAll() .pathMatchers("/swagger-ui/**").permitAll() .pathMatchers("/swagger-resources/**").permitAll() diff --git a/bc-gateway/bc-gateway-app/pom.xml b/bc-gateway/bc-gateway-app/pom.xml index 9b213843c..c8a5fa93b 100644 --- a/bc-gateway/bc-gateway-app/pom.xml +++ b/bc-gateway/bc-gateway-app/pom.xml @@ -27,10 +27,6 @@ com.fasterxml.jackson.module jackson-module-kotlin - - org.jetbrains.kotlin - kotlin-stdlib - io.projectreactor.kotlin reactor-kotlin-extensions diff --git a/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/SecurityConfig.kt b/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/SecurityConfig.kt index ccd180c93..525dfd1bc 100644 --- a/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/SecurityConfig.kt +++ b/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/SecurityConfig.kt @@ -21,6 +21,9 @@ class SecurityConfig(@Qualifier("loadBalanced") private val webClient: WebClient fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { http.csrf().disable() .authorizeExchange() + .pathMatchers("/actuator/**").permitAll() + .pathMatchers("/swagger-ui/**").permitAll() + .pathMatchers("/swagger-resources/**").permitAll() .pathMatchers("/filter/**").hasAuthority("SCOPE_trust") .pathMatchers("/admin/**").hasRole("SCOPE_trust", "system-admin") .pathMatchers("/address/**").permitAll() diff --git a/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/WebClientConfig.kt b/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/WebClientConfig.kt index 70e11ad58..0efc5a1e6 100644 --- a/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/WebClientConfig.kt +++ b/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/WebClientConfig.kt @@ -12,7 +12,6 @@ import org.springframework.web.reactive.function.client.WebClient @Configuration class WebClientConfig { - @Bean @Qualifier("loadBalanced") fun loadBalancedWebClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient { @@ -26,7 +25,6 @@ class WebClientConfig { } @Bean - fun webClient(): WebClient { return WebClient.builder() .exchangeStrategies( @@ -36,5 +34,4 @@ class WebClientConfig { ) .build() } - } diff --git a/bc-gateway/bc-gateway-app/src/main/resources/application.yml b/bc-gateway/bc-gateway-app/src/main/resources/application.yml index f754b5bc4..b442ffc7d 100644 --- a/bc-gateway/bc-gateway-app/src/main/resources/application.yml +++ b/bc-gateway/bc-gateway-app/src/main/resources/application.yml @@ -9,9 +9,6 @@ spring: bootstrap-servers: ${KAFKA_IP_PORT:localhost:9092} consumer: group-id: bc-gateway - redis: - host: ${REDIS_HOST:localhost} - port: 6379 r2dbc: url: r2dbc:postgresql://${DB_IP_PORT:localhost}/opex_bc_gateway username: ${dbusername:opex} @@ -47,10 +44,9 @@ logging: level: org.apache.kafka: ERROR co.nilin: DEBUG - swagger.authUrl: https://api.opex.dev app: auth: cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs wallet: - url: lb://opex-wallet \ No newline at end of file + url: lb://opex-wallet diff --git a/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/ChainEndpointHandlerImpl.kt b/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/ChainEndpointHandlerImpl.kt index 59c3643d0..4bb18cbc5 100644 --- a/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/ChainEndpointHandlerImpl.kt +++ b/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/ChainEndpointHandlerImpl.kt @@ -10,12 +10,13 @@ import co.nilin.opex.bcgateway.ports.postgres.model.ChainEndpointModel import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component import org.springframework.web.reactive.function.client.WebClient @Component class ChainEndpointHandlerImpl( - private val webClient: WebClient, + @Qualifier("loadBalanced") private val webClient: WebClient, private val chainRepository: ChainRepository, private val endpointRepository: ChainEndpointRepository ) : ChainEndpointHandler { diff --git a/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/resources/data.sql b/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/resources/data.sql index 8f4e93d4b..d28213b91 100644 --- a/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/resources/data.sql +++ b/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/resources/data.sql @@ -19,7 +19,8 @@ SELECT setval(pg_get_serial_sequence('address_types', 'id'), (SELECT MAX(id) FRO INSERT INTO chain_address_types(chain_name, addr_type_id) VALUES ('bitcoin', 1), - ('ethereum', 2) + ('ethereum', 2), + ('bsc', 2) ON CONFLICT DO NOTHING; INSERT INTO currency_implementations(id, @@ -40,13 +41,17 @@ ON CONFLICT DO NOTHING; SELECT setval(pg_get_serial_sequence('currency_implementations', 'id'), (SELECT MAX(id) FROM currency_implementations)); INSERT INTO chain_endpoints(id, chain_name, endpoint_url) -VALUES (1, 'bitcoin', 'lb://gateway/bitcoin/transfers'), - (2, 'ethereum', 'lb://gateway/eth/transfers') +VALUES (1, 'bitcoin', 'lb://chain-scan-gateway/bitcoin/transfers'), + (2, 'ethereum', 'lb://chain-scan-gateway/eth/transfers'), + (3, 'bsc', 'lb://chain-scan-gateway/bsc/transfers') ON CONFLICT DO NOTHING; +SELECT setval(pg_get_serial_sequence('chain_endpoints', 'id'), (SELECT MAX(id) FROM chain_endpoints)); + INSERT INTO chain_sync_schedules VALUES ('bitcoin', CURRENT_DATE, 600, 60), - ('ethereum', CURRENT_DATE, 90, 60) + ('ethereum', CURRENT_DATE, 90, 60), + ('bsc', CURRENT_DATE, 90, 60) ON CONFLICT DO NOTHING; INSERT INTO wallet_sync_schedules diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 80ab2c255..062d9d812 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -25,4 +25,7 @@ services: - "5446:5432" postgres-bc-gateway: ports: - - "5447:5432" \ No newline at end of file + - "5447:5432" + postgres-referral: + ports: + - "5448:5432" diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 8c374f16f..5ff138293 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -25,4 +25,7 @@ services: - "5436:5432" postgres-bc-gateway: ports: - - "5437:5432" \ No newline at end of file + - "5437:5432" + postgres-referral: + ports: + - "5438:5432" diff --git a/docker-compose.yml b/docker-compose.yml index 517874716..2fd79dd58 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -119,6 +119,8 @@ services: environment: - VAULT_URL_DEFAULT=http://vault:8200 - VAULT_AUTH_DEFAULT=USERNAMEPASSWORD + depends_on: + - vault consul: image: consul environment: @@ -231,6 +233,20 @@ services: deploy: restart_policy: condition: on-failure + postgres-referral: + image: postgres:14-alpine + environment: + - POSTGRES_USER=${DB_USER:-opex} + - POSTGRES_PASSWORD=${DB_PASS:-hiopex} + - POSTGRES_DB=opex_referral + - POSTGRES_BACKUP_USER=${DB_BACKUP_USER:-opex_backup} + - POSTGRES_BACKUP_PASSWORD=${DB_BACKUP_PASS:-hiopex} + volumes: + - ./resources/postgres/init-backup-user.sh:/docker-entrypoint-initdb.d/init-backup-user.sh + - $DATA/referral-data:/var/lib/postgresql/data/ + deploy: + restart_policy: + condition: on-failure accountant: build: context: accountant/accountant-app @@ -238,7 +254,6 @@ services: - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 - SPRING_PROFILES_ACTIVE=scheduled - KAFKA_IP_PORT=kafka-1:29092,kafka-2:29092,kafka-3:29092 - - REDIS_HOST=redis - CONSUL_HOST=consul - DB_IP_PORT=postgres-accountant - BACKEND_USER=${BACKEND_USER} @@ -246,11 +261,10 @@ services: networks: - default depends_on: - - zookeeper - kafka-1 - kafka-2 - kafka-3 - - redis + - wallet - consul - vault - postgres-accountant @@ -260,7 +274,6 @@ services: environment: - JAVA_OPTS=-Xmx256m - KAFKA_IP_PORT=kafka-1:29092,kafka-2:29092,kafka-3:29092 - - REDIS_HOST=redis - CONSUL_HOST=consul - DB_IP_PORT=postgres-eventlog - BACKEND_USER=${BACKEND_USER} @@ -268,11 +281,9 @@ services: networks: - default depends_on: - - zookeeper - kafka-1 - kafka-2 - kafka-3 - - redis - consul - vault - postgres-eventlog @@ -286,7 +297,6 @@ services: networks: - default depends_on: - - zookeeper - kafka-1 - kafka-2 - kafka-3 @@ -297,23 +307,22 @@ services: environment: - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 - KAFKA_IP_PORT=kafka-1:29092,kafka-2:29092,kafka-3:29092 - - REDIS_HOST=redis - CONSUL_HOST=consul networks: - default depends_on: - - zookeeper - kafka-1 - kafka-2 - kafka-3 + - auth - consul + - matching-engine auth: build: context: user-management/keycloak-gateway environment: - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 - KAFKA_IP_PORT=kafka-1:29092,kafka-2:29092,kafka-3:29092 - - REDIS_HOST=redis - CONSUL_HOST=consul - DB_IP_PORT=postgres-auth - PROXY_ADDRESS_FORWARDING=true @@ -324,11 +333,9 @@ services: - VAULT_URL=http://vault:8200 - VAULT_HOST=vault depends_on: - - zookeeper - kafka-1 - kafka-2 - kafka-3 - - redis - consul - vault - postgres-auth @@ -343,17 +350,15 @@ services: environment: - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 - KAFKA_IP_PORT=kafka-1:29092,kafka-2:29092,kafka-3:29092 - - REDIS_HOST=redis - CONSUL_HOST=consul - DB_IP_PORT=postgres-wallet - BACKEND_USER=${BACKEND_USER} - VAULT_HOST=vault depends_on: - - zookeeper - kafka-1 - kafka-2 - kafka-3 - - redis + - auth - consul - vault - postgres-wallet @@ -368,17 +373,20 @@ services: environment: - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 - KAFKA_IP_PORT=kafka-1:29092,kafka-2:29092,kafka-3:29092 - - REDIS_HOST=redis - CONSUL_HOST=consul - DB_IP_PORT=postgres-api - BACKEND_USER=${BACKEND_USER} - VAULT_HOST=vault depends_on: - - zookeeper - kafka-1 - kafka-2 - kafka-3 - - redis + - accountant + - matching-gateway + - wallet + - bc-gateway + - auth + - referral - consul - vault - postgres-api @@ -398,10 +406,10 @@ services: - BACKEND_USER=${BACKEND_USER} - VAULT_HOST=vault depends_on: - - zookeeper - kafka-1 - kafka-2 - kafka-3 + - auth - consul - vault - postgres-api @@ -422,11 +430,11 @@ services: - BACKEND_USER=${BACKEND_USER} - VAULT_HOST=vault depends_on: - - zookeeper - kafka-1 - kafka-2 - kafka-3 - - redis + - auth + - wallet - consul - vault - postgres-bc-gateway @@ -445,12 +453,35 @@ services: volumes: - $DATA/storage-data:/storage depends_on: + - auth - consul networks: - default deploy: restart_policy: condition: on-failure + referral: + build: + context: referral/referral-app + environment: + - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044 + - CONSUL_HOST=consul + - DB_IP_PORT=postgres-referral + - KAFKA_IP_PORT=kafka-1:29092,kafka-2:29092,kafka-3:29092 + - BACKEND_USER=${BACKEND_USER} + - VAULT_HOST=vault + depends_on: + - auth + - accountant + - kafka-1 + - kafka-2 + - kafka-3 + - consul + - postgres-referral + - vault + deploy: + restart_policy: + condition: on-failure admin: build: context: admin/admin-app @@ -463,11 +494,12 @@ services: volumes: - $DATA/admin-data:/admin depends_on: - - zookeeper - kafka-1 - kafka-2 - kafka-3 - consul + - auth + - vault networks: - default deploy: diff --git a/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/MatchingGatewayApp.kt b/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/MatchingGatewayApp.kt index b3c3545f5..e27767f48 100644 --- a/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/MatchingGatewayApp.kt +++ b/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/MatchingGatewayApp.kt @@ -9,9 +9,8 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2 @SpringBootApplication @ComponentScan("co.nilin.opex") @EnableOpexErrorHandler -@EnableSwagger2 class MatchingGatewayApp fun main(args: Array) { runApplication(*args) -} \ No newline at end of file +} diff --git a/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/config/SecurityConfig.kt b/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/config/SecurityConfig.kt index 0bb36ae95..ed4019789 100644 --- a/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/config/SecurityConfig.kt +++ b/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/config/SecurityConfig.kt @@ -19,7 +19,6 @@ class SecurityConfig(private val webClient: WebClient) { fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { http.csrf().disable() .authorizeExchange() - .pathMatchers("/hello").permitAll() .pathMatchers("/actuator/**").permitAll() .pathMatchers("/swagger-ui/**").permitAll() .pathMatchers("/swagger-resources/**").permitAll() diff --git a/matching-gateway/matching-gateway-app/src/main/resources/application.yml b/matching-gateway/matching-gateway-app/src/main/resources/application.yml index e544263a4..02a759094 100644 --- a/matching-gateway/matching-gateway-app/src/main/resources/application.yml +++ b/matching-gateway/matching-gateway-app/src/main/resources/application.yml @@ -5,7 +5,7 @@ logging: reactor.netty.http.client: DEBUG spring: application: - name: opex-gateway + name: opex-matching-gateway main: allow-bean-definition-overriding: false kafka: @@ -28,4 +28,4 @@ app: accountant: url: lb://opex-accountant auth: - cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs \ No newline at end of file + cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs diff --git a/pom.xml b/pom.xml index c65d87bda..b579b3498 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,7 @@ wallet websocket admin + referral diff --git a/referral/pom.xml b/referral/pom.xml new file mode 100644 index 000000000..d010dd142 --- /dev/null +++ b/referral/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + + OPEX-Core + co.nilin.opex + 1.0-SNAPSHOT + + + co.nilin.opex.referral + referral + pom + + + referral-app + referral-core + referral-ports/referral-persister-postgres + referral-ports/referral-eventlistener-kafka + referral-ports/referral-wallet-proxy + referral-ports/referral-api-proxy + + + + + org.springframework.boot + spring-boot-starter-test + + + + + + + co.nilin.opex.referral.core + referral-core + ${project.version} + + + co.nilin.opex.accountant.core + accountant-core + ${project.version} + + + co.nilin.opex.matching.engine.core + matching-engine-core + ${project.version} + + + co.nilin.opex.referral.ports.postgres + referral-persister-postgres + ${project.version} + + + co.nilin.opex.referral.ports.kafka.listener + referral-eventlistener-kafka + ${project.version} + + + co.nilin.opex.referral.ports.api.proxy + referral-api-proxy + ${project.version} + + + co.nilin.opex.referral.ports.wallet.proxy + referral-wallet-proxy + ${project.version} + + + co.nilin.opex.utility.error + error-handler + ${project.version} + + + co.nilin.opex.utility.log + logging-handler + ${project.version} + + + co.nilin.opex.utility.interceptors + interceptors + ${project.version} + + + + diff --git a/referral/referral-app/Dockerfile b/referral/referral-app/Dockerfile new file mode 100644 index 000000000..6053984ee --- /dev/null +++ b/referral/referral-app/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:11 +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] diff --git a/referral/referral-app/pom.xml b/referral/referral-app/pom.xml new file mode 100644 index 000000000..413dd75b6 --- /dev/null +++ b/referral/referral-app/pom.xml @@ -0,0 +1,121 @@ + + + 4.0.0 + + + referral + co.nilin.opex.referral + 1.0-SNAPSHOT + + + co.nilin.opex.referral.app + referral-app + + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.springframework.boot + spring-boot-starter + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + io.projectreactor + reactor-test + test + + + org.springframework.cloud + spring-cloud-starter-consul-all + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + co.nilin.opex.referral.core + referral-core + + + co.nilin.opex.referral.ports.kafka.listener + referral-eventlistener-kafka + + + co.nilin.opex.referral.ports.postgres + referral-persister-postgres + + + co.nilin.opex.referral.ports.api.proxy + referral-api-proxy + + + co.nilin.opex.referral.ports.wallet.proxy + referral-wallet-proxy + + + co.nilin.opex.utility.log + logging-handler + + + co.nilin.opex.utility.error + error-handler + + + co.nilin.opex.utility.interceptors + interceptors + + + org.springframework.cloud + spring-cloud-starter-vault-config + + + io.springfox + springfox-boot-starter + 3.0.0 + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/ReferralApp.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/ReferralApp.kt new file mode 100644 index 000000000..c78cf218d --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/ReferralApp.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.referral.app + +import co.nilin.opex.utility.error.EnableOpexErrorHandler +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.ComponentScan +import springfox.documentation.swagger2.annotations.EnableSwagger2 + +@SpringBootApplication +@ComponentScan("co.nilin.opex") +@EnableOpexErrorHandler +class ReferralApp + +fun main(args: Array) { + runApplication(*args) +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/AppConfig.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/AppConfig.kt new file mode 100644 index 000000000..eafdb3834 --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/AppConfig.kt @@ -0,0 +1,56 @@ +package co.nilin.opex.referral.app.config + +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.referral.core.api.CommissionRewardCalculator +import co.nilin.opex.referral.core.spi.CheckoutHandler +import co.nilin.opex.referral.core.spi.CommissionRewardPersister +import co.nilin.opex.referral.ports.kafka.listener.consumer.RichTradeKafkaListener +import co.nilin.opex.referral.ports.kafka.listener.spi.RichTradeListener +import kotlinx.coroutines.runBlocking +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class AppConfig { + @Bean + fun referralListener( + commissionRewardPersister: CommissionRewardPersister, + commissionRewardCalculator: CommissionRewardCalculator, + checkoutHandler: CheckoutHandler + ): ReferralListenerImpl { + return ReferralListenerImpl(commissionRewardPersister, commissionRewardCalculator, checkoutHandler) + } + + @Autowired + fun configureListeners( + richTradeKafkaListener: RichTradeKafkaListener, + appListener: ReferralListenerImpl + ) { + richTradeKafkaListener.addTradeListener(appListener) + } + + class ReferralListenerImpl( + private val commissionRewardPersister: CommissionRewardPersister, + private val commissionRewardCalculator: CommissionRewardCalculator, + private val checkoutHandler: CheckoutHandler + ) : RichTradeListener { + override fun id() = "ReferralListener" + + override fun onTrade( + richTrade: RichTrade, + partition: Int, + offset: Long, + timestamp: Long + ) { + runBlocking { + val makerCommissions = commissionRewardCalculator.calculate(richTrade.makerOuid, richTrade) + val takerCommissions = commissionRewardCalculator.calculate(richTrade.takerOuid, richTrade) + makerCommissions.forEach { commissionRewardPersister.save(it) } + takerCommissions.forEach { commissionRewardPersister.save(it) } + checkoutHandler.checkoutById(richTrade.makerUuid) + if (richTrade.makerUuid != richTrade.takerUuid) checkoutHandler.checkoutById(richTrade.takerUuid) + } + } + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/AppDispatchers.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/AppDispatchers.kt new file mode 100644 index 000000000..6334d8d45 --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/AppDispatchers.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.referral.app.config + +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors + +object AppDispatchers { + val kafkaExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher() +} \ No newline at end of file diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/RestConfig.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/RestConfig.kt new file mode 100644 index 000000000..99cadb64d --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/RestConfig.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.referral.app.config + +import co.nilin.opex.utility.interceptors.FormDataWorkaroundFilter +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.format.Formatter +import org.springframework.web.server.WebFilter +import java.util.* + +@Configuration +class RestConfig { + @Bean + fun dateFormatter(): Formatter? { + return object : Formatter { + override fun print(date: Date, locale: Locale): String { + return date.time.toString() + } + + override fun parse(date: String, locale: Locale): Date { + return Date(date.toLong()) + } + } + } + + @Bean + fun formDataWebFilter(): WebFilter { + return FormDataWorkaroundFilter() + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/SecurityConfig.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/SecurityConfig.kt new file mode 100644 index 000000000..c409a34ee --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/SecurityConfig.kt @@ -0,0 +1,42 @@ +package co.nilin.opex.referral.app.config + +import co.nilin.opex.referral.app.utils.hasRole +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder +import org.springframework.security.web.server.SecurityWebFilterChain +import org.springframework.web.reactive.function.client.WebClient + +@EnableWebFluxSecurity +class SecurityConfig(private val webClient: WebClient) { + @Value("\${app.auth.cert-url}") + private lateinit var jwkUrl: String + + @Bean + fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { + http.csrf().disable() + .authorizeExchange() + .pathMatchers("/actuator/**").permitAll() + .pathMatchers("/swagger-ui/**").permitAll() + .pathMatchers("/swagger-resources/**").permitAll() + .pathMatchers("/v2/api-docs").permitAll() + .pathMatchers("/checkouts/**", "/commissions/**", "/references").hasRole("SCOPE_trust", "finance-admin") + .pathMatchers("/**").hasAuthority("SCOPE_trust") + .anyExchange().authenticated() + .and() + .oauth2ResourceServer() + .jwt() + return http.build() + } + + @Bean + @Throws(Exception::class) + fun reactiveJwtDecoder(): ReactiveJwtDecoder? { + return NimbusReactiveJwtDecoder.withJwkSetUri(jwkUrl) + .webClient(webClient) + .build() + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/SwaggerConfig.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/SwaggerConfig.kt new file mode 100644 index 000000000..b4dcc1ca0 --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/SwaggerConfig.kt @@ -0,0 +1,105 @@ +package co.nilin.opex.referral.app.config + +import co.nilin.opex.referral.app.controller.* +import com.fasterxml.classmate.TypeResolver +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.core.annotation.CurrentSecurityContext +import springfox.documentation.builders.ApiInfoBuilder +import springfox.documentation.builders.OAuthBuilder +import springfox.documentation.builders.PathSelectors +import springfox.documentation.service.* +import springfox.documentation.spi.DocumentationType +import springfox.documentation.spi.service.contexts.SecurityContext +import springfox.documentation.spring.web.plugins.Docket +import springfox.documentation.swagger.web.SecurityConfiguration +import springfox.documentation.swagger.web.SecurityConfigurationBuilder +import java.security.Principal +import java.util.* + +@Configuration +class SwaggerConfig { + @Value("\${swagger.authUrl}") + private lateinit var authUrl: String + + @Autowired + private lateinit var typeResolver: TypeResolver + + @Bean + fun opexBCGateway(): Docket { + return Docket(DocumentationType.SWAGGER_2) + .groupName("opex-referral") + .apiInfo(apiInfo()) + .select() + .paths(PathSelectors.regex("^/actuator.*").negate()) + .build() + .additionalModels( + typeResolver.resolve(CheckoutController.CheckoutRecordBody::class.java), + typeResolver.resolve(CommissionController.CommissionRewardBody::class.java), + typeResolver.resolve(CodeController.ReferralCodeBody::class.java), + typeResolver.resolve(ReferenceController.ReferenceBody::class.java), + typeResolver.resolve(ReportController.ReferrerReportBody::class.java), + typeResolver.resolve(ConfigController.ConfigsBody::class.java) + ) + .ignoredParameterTypes( + AuthenticationPrincipal::class.java, + CurrentSecurityContext::class.java, + Principal::class.java + ) + .useDefaultResponseMessages(false) + .securitySchemes(Collections.singletonList(oauth())) + .securityContexts(Collections.singletonList(securityContext())) + } + + private fun apiInfo(): ApiInfo { + return ApiInfoBuilder() + .title("OPEX API") + .description("Backend for opex exchange.") + .license("MIT License") + .licenseUrl("https://github.com/opexdev/Back-end/blob/feature/1-MVP/LICENSE") + .version("0.1") + .build() + } + + private fun oauth(): SecurityScheme { + return OAuthBuilder() + .name("opex") + .grantTypes(grantTypes()) + .scopes(scopes()) + .build() + } + + private fun scopes(): List { + return listOf(AuthorizationScope("openid", "OpenId")) + } + + private fun grantTypes(): List { + val tokenUrl = "$authUrl/auth/realms/opex/protocol/openid-connect/token" + val grantType = ResourceOwnerPasswordCredentialsGrant(tokenUrl) + return Collections.singletonList(grantType) + } + + private fun securityContext(): SecurityContext { + val securityReference = SecurityReference.builder() + .reference("opex") + .scopes(emptyArray()) + .build() + return SecurityContext.builder() + .securityReferences(Collections.singletonList(securityReference)) + .operationSelector { true } + .build() + } + + @Bean + fun securityInfo(): SecurityConfiguration { + return SecurityConfigurationBuilder.builder() + .clientId("admin-cli") + .realm("opex") + .appName("opex") + .scopeSeparator(",") + .build() + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/WebClientConfig.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/WebClientConfig.kt new file mode 100644 index 000000000..080965477 --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/config/WebClientConfig.kt @@ -0,0 +1,23 @@ +package co.nilin.opex.referral.app.config + +import org.springframework.cloud.client.ServiceInstance +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer +import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.reactive.function.client.WebClient + +@Configuration +class WebClientConfig { + @Bean + fun webClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient { + return WebClient.builder() + .filter( + ReactorLoadBalancerExchangeFilterFunction( + loadBalancerFactory, LoadBalancerProperties(), emptyList() + ) + ) + .build() + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/CheckoutController.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/CheckoutController.kt new file mode 100644 index 000000000..8d931bde4 --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/CheckoutController.kt @@ -0,0 +1,103 @@ +package co.nilin.opex.referral.app.controller + +import co.nilin.opex.matching.engine.core.model.OrderDirection +import co.nilin.opex.referral.core.model.CheckoutState +import co.nilin.opex.referral.core.spi.CheckoutHandler +import co.nilin.opex.referral.core.spi.CheckoutRecordHandler +import co.nilin.opex.referral.core.spi.ConfigHandler +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.* +import java.math.BigDecimal +import java.time.ZoneId +import java.util.* + +@RestController +@RequestMapping("/checkouts") +class CheckoutController( + private val checkoutHandler: CheckoutHandler, + private val configHandler: ConfigHandler, + private val checkoutRecordHandler: CheckoutRecordHandler +) { + data class CheckoutRecordBody( + var commissionRewardsId: Long, + var rewardedUuid: String, + var referentUuid: String, + var referralCode: String, + var richTrade: Long, + var referentOrderDirection: OrderDirection, + var share: BigDecimal, + var createDate: Date, + var checkoutState: CheckoutState, + var transferRef: String?, + var updateDate: Date + ) + + @ApiOperation( + value = "Checkout pending commissions", + notes = "Checkout pending commissions." + ) + @ApiResponse( + message = "OK", + code = 200 + ) + @PutMapping("/checkout-all", produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun checkoutAll() { + val min = configHandler.findConfig("default")!!.minPaymentAmount + checkoutHandler.checkoutEveryCandidate(min) + } + + @ApiOperation( + value = "Get all checkouts", + notes = "Get all checkouts by status." + ) + @ApiResponse( + message = "OK", + code = 200, + response = CheckoutRecordBody::class, + responseContainer = "List", + examples = Example( + ExampleProperty( + mediaType = "application/json", + value = """ +[ + { + "commissionRewardsId": 1, + "rewardedUuid": "b3e4f2bd-15c6-4912-bdef-161445a98193", + "referentUuid": "a5e510f9-bda8-4ecb-b500-0980f525dc52", + "referralCode": "10000", + "richTrade": 1, + "referentOrderDirection": "BID", + "share": 0.001, + "createDate": 1646213088, + "checkoutState": "PENDING", + "transferRef": "wallet-transaction-id", + "updateDate": 1646213088 + } +] + """ + ) + ) + ) + @GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun get(@RequestParam status: CheckoutState): List { + return checkoutRecordHandler.findCommissionsByCheckoutState(status).map { + CheckoutRecordBody( + it.commissionReward.id, + it.commissionReward.rewardedUuid, + it.commissionReward.referentUuid, + it.commissionReward.referralCode, + it.commissionReward.richTrade.first, + it.commissionReward.referentOrderDirection, + it.commissionReward.share, + Date.from(it.commissionReward.createDate.atZone(ZoneId.systemDefault()).toInstant()), + it.checkoutState, + it.transferRef, + Date.from(it.updateDate.atZone(ZoneId.systemDefault()).toInstant()) + ) + } + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/CodeController.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/CodeController.kt new file mode 100644 index 000000000..2ccb96575 --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/CodeController.kt @@ -0,0 +1,176 @@ +package co.nilin.opex.referral.app.controller + +import co.nilin.opex.referral.core.spi.ConfigHandler +import co.nilin.opex.referral.core.spi.ReferenceHandler +import co.nilin.opex.referral.core.spi.ReferralCodeHandler +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import com.nimbusds.jose.shaded.json.JSONArray +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.springframework.http.MediaType +import org.springframework.security.oauth2.jwt.Jwt +import org.springframework.web.bind.annotation.* +import java.math.BigDecimal +import java.security.Principal + +@RestController +@RequestMapping("/codes") +class CodeController( + private val referralCodeHandler: ReferralCodeHandler, + private val referenceHandler: ReferenceHandler, + private val configHandler: ConfigHandler +) { + data class PostReferralBody( + val uuid: String, + val referentCommission: BigDecimal + ) + + data class PatchReferralBody( + val referentCommission: BigDecimal + ) + + data class ReferralCodeBody( + val uuid: String, + val code: String, + val referentCommission: BigDecimal + ) + + private val reThrow: (Throwable) -> Unit = { e -> + when (e) { + is IllegalArgumentException -> throw OpexException(OpexError.BadRequest, e.message) + is OpexException -> throw e + else -> throw OpexException(OpexError.InternalServerError, e.message) + } + } + + @ApiOperation( + value = "Create new referral code", + notes = "Send user information to create new referral code. referentCommission is a value in range [0, 1]." + ) + @ApiResponse( + message = "OK", + code = 200, + response = String::class, + examples = Example( + ExampleProperty( + mediaType = "application/json", + value = "10000" + ) + ) + ) + @PostMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun generateReferralCode( + @RequestBody body: PostReferralBody, + principal: Principal + ): String { + if (body.uuid != principal.name) throw OpexException(OpexError.UnAuthorized) + return referralCodeHandler.runCatching { + val maxReferralCodePerUser = configHandler.findConfig("default")!!.maxReferralCodePerUser + val count = referralCodeHandler.findByReferrerUuid(body.uuid).size + if (count >= maxReferralCodePerUser) throw OpexException( + OpexError.Forbidden, + "You have reached maximum number of referral codes" + ) + generateReferralCode(body.uuid, body.referentCommission) + }.onFailure(reThrow).getOrThrow() + } + + @ApiOperation( + value = "Update referral code", + notes = "Edit referral code properties. The id code is immutable, you can not change it. referentCommission is a value in range [0, 1]." + ) + @ApiResponse(message = "OK", code = 200) + @PatchMapping("/{code}", produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun updateReferralCodeByCode( + @PathVariable code: String, + @RequestBody body: PatchReferralBody, + principal: Principal + ) { + val referralCode = referralCodeHandler.findByCode(code) ?: throw OpexException( + OpexError.BadRequest, + "Referral code is invalid" + ) + if (referralCode.uuid != principal.name) throw OpexException(OpexError.UnAuthorized) + referralCodeHandler.runCatching { updateCommissions(code, body.referentCommission) } + .onFailure(reThrow) + } + + @ApiOperation(value = "Get referral codes info", notes = "Get referral codes info.") + @ApiResponse( + message = "OK", + code = 200, + response = ReferralCodeBody::class, + examples = Example( + ExampleProperty( + mediaType = "application/json", + value = """ +{ + "uuid": "b3e4f2bd-15c6-4912-bdef-161445a98193", + "code": "10000", + "referentCommission": 0 +} + """, + ) + ) + ) + @GetMapping("/{code}", produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun getReferralCodeByCode( + @PathVariable code: String, + principal: Principal + ): ReferralCodeBody { + val referralCode = referralCodeHandler.findByCode(code) ?: throw OpexException(OpexError.NotFound) + if (referralCode.uuid != principal.name) throw OpexException(OpexError.UnAuthorized) + return ReferralCodeBody(referralCode.uuid, referralCode.code, referralCode.referentCommission) + } + + @ApiOperation(value = "Get all referral codes", notes = "Get all of referral codes.") + @ApiResponse( + message = "OK", + code = 200, + response = ReferralCodeBody::class, + responseContainer = "List", + examples = Example( + ExampleProperty( + mediaType = "application/json", + value = """ +[ + { + "uuid": "b3e4f2bd-15c6-4912-bdef-161445a98193", + "code": "10000", + "referentCommission": 0 + } +] + """, + ) + ) + ) + @GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun getAllReferralCodes( + @RequestParam(required = false) uuid: String?, + principal: Principal + ): List { + return uuid?.takeIf { uuid == principal.name }?.let { id -> + referralCodeHandler.findByReferrerUuid(id).map { ReferralCodeBody(it.uuid, it.code, it.referentCommission) } + } ?: run { + val isAdmin = ((principal as Jwt).claims["roles"] as? JSONArray)?.contains("finance-admin") ?: false + return if (isAdmin) referralCodeHandler.findAll() + .map { ReferralCodeBody(it.uuid, it.code, it.referentCommission) } + else throw OpexException(OpexError.UnAuthorized) + } + } + + @ApiOperation(value = "Delete referral code", notes = "Delete referral codes by its id.") + @ApiResponse(message = "OK", code = 200) + @DeleteMapping("/{code}", produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun deleteReferralCode( + @PathVariable code: String, + principal: Principal + ) { + val referralCode = referralCodeHandler.findByCode(code) ?: throw OpexException(OpexError.NotFound) + if (referralCode.uuid != principal.name) throw OpexException(OpexError.UnAuthorized) + referralCodeHandler.deleteByCode(code) + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/CommissionController.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/CommissionController.kt new file mode 100644 index 000000000..31e0752e9 --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/CommissionController.kt @@ -0,0 +1,101 @@ +package co.nilin.opex.referral.app.controller + +import co.nilin.opex.matching.engine.core.model.OrderDirection +import co.nilin.opex.referral.core.spi.CommissionRewardHandler +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.* +import java.math.BigDecimal +import java.time.ZoneId +import java.util.* + +@RestController +class CommissionController(private val commissionRewardHandler: CommissionRewardHandler) { + data class CommissionRewardBody( + var rewardedUuid: String, + var referentUuid: String, + var referralCode: String, + var richTrade: Long, + var referentOrderDirection: OrderDirection, + var share: BigDecimal, + var createDate: Date + ) + + @ApiOperation( + value = "Get all commissions", + notes = "Get all commissions by referer or referent." + ) + @ApiResponse( + message = "OK", + code = 200, + response = CommissionRewardBody::class, + responseContainer = "List", + examples = Example( + ExampleProperty( + mediaType = "application/json", + value = """ +[ + { + "rewardedUuid": "b3e4f2bd-15c6-4912-bdef-161445a98193", + "referentUuid": "a5e510f9-bda8-4ecb-b500-0980f525dc52", + "referralCode": "10000", + "richTrade": 1, + "referentOrderDirection": "BID", + "share": 0.01, + "createDate": 1646213088 + } +] + """ + ) + ) + ) + @GetMapping("/commissions", produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun getCommissions( + @RequestParam(required = false) code: String?, + @RequestParam(required = false) rewardedUuid: String?, + @RequestParam(required = false) referentUuid: String? + ): List { + return commissionRewardHandler.findCommissions( + referralCode = code, + referentUuid = referentUuid, + rewardedUuid = rewardedUuid + ).map { + CommissionRewardBody( + it.rewardedUuid, + it.referentUuid, + it.referralCode, + it.richTrade.first, + it.referentOrderDirection, + it.share, + Date.from(it.createDate.atZone(ZoneId.systemDefault()).toInstant()) + ) + } + } + + @ApiOperation( + value = "Batch delete commissions", + notes = "Delete commissions base on given information." + ) + @ApiResponse(message = "OK", code = 200) + @DeleteMapping("/commissions", produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun deleteCommissions( + @RequestParam(required = false) code: String?, + @RequestParam(required = false) referrerUuid: String?, + @RequestParam(required = false) referentUuid: String? + ) { + commissionRewardHandler.deleteCommissions(code, referrerUuid, referentUuid) + } + + @ApiOperation( + value = "Delete commission record", + notes = "Delete commission record by id." + ) + @ApiResponse(message = "OK", code = 200) + @DeleteMapping("/commissions/{id}", produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun deleteCommissionById(@PathVariable id: Long) { + commissionRewardHandler.deleteCommissionById(id) + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/ConfigController.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/ConfigController.kt new file mode 100644 index 000000000..35d91d4bc --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/ConfigController.kt @@ -0,0 +1,64 @@ +package co.nilin.opex.referral.app.controller + +import co.nilin.opex.referral.core.spi.ConfigHandler +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import java.math.BigDecimal + +@RestController +@RequestMapping("/configs") +class ConfigController( + private val configHandler: ConfigHandler +) { + data class ConfigsBody( + var name: String, + var referralCommissionReward: BigDecimal, + var paymentCurrency: String, + var minPaymentAmount: BigDecimal, + var paymentWindowSeconds: Int, + var maxReferralCodePerUser: Int + ) + + @ApiOperation(value = "Get referral configs", notes = "Get referral configs.") + @ApiResponse( + message = "OK", + code = 200, + response = ConfigsBody::class, + examples = Example( + ExampleProperty( + mediaType = "application/json", + value = """ +{ + "name": "default", + "referralCommissionReward": 0.3, + "paymentCurrency": "usdt", + "minPaymentAmount": 0, + "paymentWindowSeconds": 604800, + "maxReferralCodePerUser": 20 +} + """, + ) + ) + ) + @GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun getReferralCodeByCode(): ConfigsBody { + return configHandler.findConfig("default")?.let { + ConfigsBody( + it.name, + it.referralCommissionReward, + it.paymentCurrency, + it.minPaymentAmount, + it.paymentWindowSeconds, + it.maxReferralCodePerUser + ) + } ?: throw OpexException(OpexError.InternalServerError, "Config profile (default) not found") + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/ReferenceController.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/ReferenceController.kt new file mode 100644 index 000000000..0bc2832a2 --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/ReferenceController.kt @@ -0,0 +1,85 @@ +package co.nilin.opex.referral.app.controller + +import co.nilin.opex.referral.core.model.Reference +import co.nilin.opex.referral.core.spi.ReferenceHandler +import co.nilin.opex.referral.core.spi.ReferralCodeHandler +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import java.security.Principal + +@RestController +class ReferenceController( + private val referralCodeHandler: ReferralCodeHandler, + private val referenceHandler: ReferenceHandler +) { + data class ReferenceBody( + var referralCode: String, + var referentUuid: String, + ) + + private val reThrow: (Throwable) -> Unit = { e -> + when (e) { + is IllegalArgumentException -> throw OpexException(OpexError.BadRequest, e.message) + is OpexException -> throw e + else -> throw OpexException(OpexError.InternalServerError, e.message) + } + } + + @ApiOperation( + value = "Refer a user by referral code", + notes = "Referrer can not be one of your referents. Also you can not refer yourself." + ) + @ApiResponse(message = "OK", code = 200) + @PutMapping("/references/assign", produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun assignReferrer( + @RequestParam code: String, + @RequestParam(required = false) uuid: String?, + principal: Principal + ) { + val id = uuid ?: principal.name + if (id != principal.name) throw OpexException(OpexError.UnAuthorized) + referralCodeHandler.runCatching { assign(code, id) }.onFailure(reThrow) + } + + @ApiOperation(value = "Get referral code's references", notes = "Get uuid of all referral code's references.") + @ApiResponse( + message = "OK", + code = 200, + response = String::class, + responseContainer = "List", + examples = Example( + ExampleProperty( + mediaType = "application/json", + value = """ +[ + "b3e4f2bd-15c6-4912-bdef-161445a98193" +] + """, + ) + ) + ) + @GetMapping("/references", produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun getReferenceByCodeAndUuid( + @RequestParam(required = false) uuid: String?, + @RequestParam(required = false) code: String?, + principal: Principal + ): List { + fun List.res() = map { ReferenceBody(it.referralCode.code, it.referentUuid) } + return when (uuid ?: code) { + null -> throw OpexException(OpexError.BadRequest, "One of (uuid, code) parameters must be provided") + uuid -> referenceHandler.findByReferrerUuid(uuid) + .let { it.takeIf { code == null } ?: it.filter { v -> v.referralCode.code == code } }.res() + code -> referenceHandler.findByCode(code).res() + else -> throw OpexException(OpexError.InternalServerError, "All of (code, uuid) are null") + } + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/ReportController.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/ReportController.kt new file mode 100644 index 000000000..39e310d6d --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/controller/ReportController.kt @@ -0,0 +1,58 @@ +package co.nilin.opex.referral.app.controller + +import co.nilin.opex.referral.core.spi.CommissionRewardHandler +import co.nilin.opex.referral.core.spi.ReferenceHandler +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RestController +import java.math.BigDecimal +import java.security.Principal + +@RestController +class ReportController( + private val commissionRewardHandler: CommissionRewardHandler, + private val referenceHandler: ReferenceHandler +) { + data class ReferrerReportBody( + val referentsCount: Long, + val share: BigDecimal + ) + + @ApiOperation( + value = "Get report by uuid", + notes = "Get report by uuid." + ) + @ApiResponse( + message = "OK", + code = 200, + response = ReferrerReportBody::class, + examples = Example( + ExampleProperty( + mediaType = "application/json", + value = """ +{ + "referentsCount": 1, + "share": 0.001 +} + """, + ) + ) + ) + @GetMapping("/reports/{uuid}", produces = [MediaType.APPLICATION_JSON_VALUE]) + suspend fun getReportByRewardedUuid( + @PathVariable uuid: String, + principal: Principal + ): ReferrerReportBody { + if (uuid != principal.name) throw OpexException(OpexError.UnAuthorized) + val referencesCount = referenceHandler.findByReferrerUuid(uuid).size.toLong() + val commissions = commissionRewardHandler.findCommissions(rewardedUuid = uuid) + return ReferrerReportBody(referencesCount, commissions.sumOf { it.share }) + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/service/CheckoutSchedule.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/service/CheckoutSchedule.kt new file mode 100644 index 000000000..ff514574b --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/service/CheckoutSchedule.kt @@ -0,0 +1,21 @@ +package co.nilin.opex.referral.app.service + +import co.nilin.opex.referral.core.spi.CheckoutHandler +import co.nilin.opex.referral.core.spi.ConfigHandler +import kotlinx.coroutines.runBlocking +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import java.sql.Timestamp +import java.util.* + +@Service +class CheckoutSchedule(private val checkoutHandler: CheckoutHandler, private val configHandler: ConfigHandler) { + @Scheduled(fixedDelay = 12 * 60 * 60 * 1000) + fun pay() { + runBlocking { + val config = configHandler.findConfig("default")!! + val minDate = Date.from(Timestamp(Date().time - config.paymentWindowSeconds * 1000).toInstant()) + checkoutHandler.checkoutOlderThan(minDate) + } + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/utils/Extensions.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/utils/Extensions.kt new file mode 100644 index 000000000..45bdb566e --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/referral/app/utils/Extensions.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.referral.app.utils + +import com.nimbusds.jose.shaded.json.JSONArray +import org.springframework.security.authorization.AuthorizationDecision +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.oauth2.jwt.Jwt + +fun ServerHttpSecurity.AuthorizeExchangeSpec.Access.hasRole( + authority: String, + role: String +): ServerHttpSecurity.AuthorizeExchangeSpec = access { mono, _ -> + mono.map { auth -> + val hasAuthority = auth.authorities.any { it.authority == authority } + val hasRole = ((auth.principal as Jwt).claims["roles"] as JSONArray?)?.contains(role) == true + AuthorizationDecision(hasAuthority && hasRole) + } +} diff --git a/referral/referral-app/src/main/kotlin/co/nilin/opex/util/vault/VaultUserIdMechanism.kt b/referral/referral-app/src/main/kotlin/co/nilin/opex/util/vault/VaultUserIdMechanism.kt new file mode 100644 index 000000000..e9efaf92a --- /dev/null +++ b/referral/referral-app/src/main/kotlin/co/nilin/opex/util/vault/VaultUserIdMechanism.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.util.vault + +import org.springframework.vault.authentication.AppIdUserIdMechanism + +class VaultUserIdMechanism() : AppIdUserIdMechanism { + override fun createUserId(): String { + return System.getenv("BACKEND_USER") + } +} \ No newline at end of file diff --git a/referral/referral-app/src/main/resources/application.yml b/referral/referral-app/src/main/resources/application.yml new file mode 100644 index 000000000..6cb0ede16 --- /dev/null +++ b/referral/referral-app/src/main/resources/application.yml @@ -0,0 +1,54 @@ +server.port: 8080 +logging: + level: + co.nilin: DEBUG + reactor.netty.http.client: DEBUG +spring: + application: + name: opex-referral + main: + allow-bean-definition-overriding: false + allow-circular-references: true + kafka: + bootstrap-servers: ${KAFKA_IP_PORT:localhost:9092} + consumer: + group-id: referral + r2dbc: + url: r2dbc:postgresql://${DB_IP_PORT:localhost}/opex_referral + username: ${dbusername:opex} + password: ${dbpassword:hiopex} + initialization-mode: always + cloud: + bootstrap: + enabled: true + vault: + host: ${VAULT_HOST} + port: 8200 + scheme: http + authentication: APPID + app-id: + user-id: co.nilin.opex.util.vault.VaultUserIdMechanism + fail-fast: true + kv: + enabled: true + backend: secret + profile-separator: '/' + application-name: ${spring.application.name} + consul: + host: ${CONSUL_HOST:localhost} + port: 8500 + discovery: + #healthCheckPath: ${management.context-path}/health + instance-id: ${spring.application.name}:${server.port} + healthCheckInterval: 20s + prefer-ip-address: true + config: + import: vault://secret/${spring.application.name} +swagger.authUrl: https://api.opex.dev +app: + wallet: + url: lb://opex-wallet/ + api: + url: lb://opex-api/ + auth: + cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs diff --git a/referral/referral-core/pom.xml b/referral/referral-core/pom.xml new file mode 100644 index 000000000..131f4ccf1 --- /dev/null +++ b/referral/referral-core/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + referral + co.nilin.opex.referral + 1.0-SNAPSHOT + + + co.nilin.opex.referral.core + referral-core + referral-core + + + + co.nilin.opex.accountant.core + accountant-core + + + org.springframework.boot + spring-boot-starter-webflux + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + \ No newline at end of file diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/api/CommissionRewardCalculator.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/api/CommissionRewardCalculator.kt new file mode 100644 index 000000000..93f81ce82 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/api/CommissionRewardCalculator.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.referral.core.api + +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.referral.core.model.CommissionReward + +interface CommissionRewardCalculator { + suspend fun calculate(ouid: String, richTrade: RichTrade): List +} \ No newline at end of file diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/api/SymbolPriceCalculator.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/api/SymbolPriceCalculator.kt new file mode 100644 index 000000000..0952039c0 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/api/SymbolPriceCalculator.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.referral.core.api + +import java.math.BigDecimal + +interface SymbolPriceCalculator { + suspend fun getPrice(symbol: String, paymentCurrency: String): BigDecimal +} diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/CheckoutRecord.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/CheckoutRecord.kt new file mode 100644 index 000000000..cef7417d0 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/CheckoutRecord.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.referral.core.model + +import java.time.LocalDateTime + +data class CheckoutRecord( + var commissionReward: CommissionReward, + var checkoutState: CheckoutState, + var transferRef: String?, + var updateDate: LocalDateTime +) diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/CheckoutState.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/CheckoutState.kt new file mode 100644 index 000000000..10eb79524 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/CheckoutState.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.referral.core.model + +enum class CheckoutState { + PENDING, + CHECKED_OUT +} diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/CommissionReward.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/CommissionReward.kt new file mode 100644 index 000000000..c5989cd90 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/CommissionReward.kt @@ -0,0 +1,18 @@ +package co.nilin.opex.referral.core.model + +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.matching.engine.core.model.OrderDirection +import java.math.BigDecimal +import java.time.LocalDateTime + +data class CommissionReward( + var id: Long, + var rewardedUuid: String, + var referentUuid: String, + var referralCode: String, + var richTrade: Pair, + var referentOrderDirection: OrderDirection, + var share: BigDecimal, + var paymentCurrency: String, + var createDate: LocalDateTime +) diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/Config.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/Config.kt new file mode 100644 index 000000000..8ed15e4a3 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/Config.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.referral.core.model + +import java.math.BigDecimal + +data class Config( + var name: String, + var referralCommissionReward: BigDecimal, + var paymentCurrency: String, + var minPaymentAmount: BigDecimal, + var paymentWindowSeconds: Int, + var maxReferralCodePerUser: Int +) diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/Reference.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/Reference.kt new file mode 100644 index 000000000..1a3fcf762 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/Reference.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.referral.core.model + +data class Reference( + var referralCode: ReferralCode, + var referentUuid: String, +) diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/ReferralCode.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/ReferralCode.kt new file mode 100644 index 000000000..5ebf2345d --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/model/ReferralCode.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.referral.core.model + +import java.math.BigDecimal + +data class ReferralCode( + var uuid: String, + var code: String, + var referentCommission: BigDecimal +) diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/service/CommissionRewardCalculatorImpl.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/service/CommissionRewardCalculatorImpl.kt new file mode 100644 index 000000000..3fdf47101 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/service/CommissionRewardCalculatorImpl.kt @@ -0,0 +1,70 @@ +package co.nilin.opex.referral.core.service + +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.referral.core.api.CommissionRewardCalculator +import co.nilin.opex.referral.core.api.SymbolPriceCalculator +import co.nilin.opex.referral.core.model.CommissionReward +import co.nilin.opex.referral.core.spi.ConfigHandler +import co.nilin.opex.referral.core.spi.ReferenceHandler +import org.springframework.stereotype.Service +import java.math.BigDecimal +import java.time.LocalDateTime + +@Service +class CommissionRewardCalculatorImpl( + private val symbolPriceCalculator: SymbolPriceCalculator, + private val referenceHandler: ReferenceHandler, + private val configHandler: ConfigHandler +) : CommissionRewardCalculator { + override suspend fun calculate(ouid: String, richTrade: RichTrade): List { + if (ouid != richTrade.makerOuid && ouid != richTrade.takerOuid) throw IllegalArgumentException("Order is not correct") + val paymentCurrency = configHandler.findConfig("default")!!.paymentCurrency + val uuid = if (ouid == richTrade.makerOuid) richTrade.makerUuid else richTrade.takerUuid + val reference = referenceHandler.findByReferentUuid(uuid) + ?: return emptyList() + val commission = + if (ouid == richTrade.makerOuid) richTrade.makerCommision * symbolPriceCalculator.getPrice( + richTrade.makerCommisionAsset, + paymentCurrency + ) + else richTrade.takerCommision * symbolPriceCalculator.getPrice( + richTrade.takerCommisionAsset, + paymentCurrency + ) + val direction = if (ouid == richTrade.makerOuid) richTrade.makerDirection else richTrade.takerDirection + val ret = mutableListOf() + if (commission > BigDecimal.ZERO) { + if (reference.referralCode.referentCommission < BigDecimal.ONE) { + ret.add( + CommissionReward( + 0, + reference.referralCode.uuid, + reference.referentUuid, + reference.referralCode.code, + richTrade.id to richTrade, + direction, + commission * (BigDecimal.ONE - reference.referralCode.referentCommission), + paymentCurrency, + LocalDateTime.now() + ) + ) + } + if (reference.referralCode.referentCommission > BigDecimal.ZERO) { + ret.add( + CommissionReward( + 0, + reference.referentUuid, + reference.referentUuid, + reference.referralCode.code, + richTrade.id to richTrade, + direction, + commission * reference.referralCode.referentCommission, + paymentCurrency, + LocalDateTime.now() + ) + ) + } + } + return ret + } +} diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ApiProxy.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ApiProxy.kt new file mode 100644 index 000000000..cb0483fca --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ApiProxy.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.referral.core.spi + +import java.math.BigDecimal + +interface ApiProxy { + suspend fun fetchLastPrice(pairSymbol: String): BigDecimal? +} diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CheckoutHandler.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CheckoutHandler.kt new file mode 100644 index 000000000..4499f4d0f --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CheckoutHandler.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.referral.core.spi + +import java.math.BigDecimal +import java.util.* + +interface CheckoutHandler { + suspend fun checkoutById(uuid: String) + suspend fun checkoutEveryCandidate(min: BigDecimal) + suspend fun checkoutOlderThan(date: Date) +} diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CheckoutRecordHandler.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CheckoutRecordHandler.kt new file mode 100644 index 000000000..11f5ff355 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CheckoutRecordHandler.kt @@ -0,0 +1,15 @@ +package co.nilin.opex.referral.core.spi + +import co.nilin.opex.referral.core.model.CheckoutRecord +import co.nilin.opex.referral.core.model.CheckoutState +import java.math.BigDecimal +import java.util.* + +interface CheckoutRecordHandler { + suspend fun findCommissionsByCheckoutState(checkoutState: CheckoutState): List + suspend fun findUserCommissionsWhereTotalGreaterAndEqualTo(uuid: String, value: BigDecimal): List + suspend fun findAllCommissionsWhereTotalGreaterAndEqualTo(value: BigDecimal): List + suspend fun findCommissionsWherePendingDateLessOrEqualThan(date: Date): List + suspend fun updateCheckoutState(id: Long, value: CheckoutState) + suspend fun checkout(id: Long, transferRef: String) +} diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CommissionRewardHandler.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CommissionRewardHandler.kt new file mode 100644 index 000000000..0f23b1b49 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CommissionRewardHandler.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.referral.core.spi + +import co.nilin.opex.referral.core.model.CommissionReward + +interface CommissionRewardHandler { + suspend fun findCommissions( + referralCode: String? = null, + rewardedUuid: String? = null, + referentUuid: String? = null + ): List + + suspend fun deleteCommissions( + referralCode: String? = null, + rewardedUuid: String? = null, + referentUuid: String? = null + ) + + suspend fun deleteCommissionById(id: Long) +} diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CommissionRewardPersister.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CommissionRewardPersister.kt new file mode 100644 index 000000000..0251c2e9e --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/CommissionRewardPersister.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.referral.core.spi + +import co.nilin.opex.referral.core.model.CommissionReward + +interface CommissionRewardPersister { + suspend fun save(commissionReward: CommissionReward) +} \ No newline at end of file diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ConfigHandler.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ConfigHandler.kt new file mode 100644 index 000000000..e502deb44 --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ConfigHandler.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.referral.core.spi + +import co.nilin.opex.referral.core.model.Config + +interface ConfigHandler { + suspend fun findConfig(name: String): Config? +} diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ReferenceHandler.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ReferenceHandler.kt new file mode 100644 index 000000000..9c320c0ae --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ReferenceHandler.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.referral.core.spi + +import co.nilin.opex.referral.core.model.Reference + +interface ReferenceHandler { + suspend fun findAll(): List + suspend fun findByReferentUuid(uuid: String): Reference? + suspend fun findByReferrerUuid(uuid: String): List + suspend fun findByCode(code: String): List +} diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ReferralCodeHandler.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ReferralCodeHandler.kt new file mode 100644 index 000000000..490571b4b --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/ReferralCodeHandler.kt @@ -0,0 +1,20 @@ +package co.nilin.opex.referral.core.spi + +import co.nilin.opex.referral.core.model.ReferralCode +import java.math.BigDecimal + +interface ReferralCodeHandler { + suspend fun generateReferralCode( + uuid: String, + referentCommission: BigDecimal + ): String + + suspend fun findAll(): List + suspend fun findByReferentUuid(uuid: String): ReferralCode? + suspend fun findByReferrerUuid(uuid: String): List + suspend fun findByCode(code: String): ReferralCode? + suspend fun assign(code: String, referentUuid: String) + suspend fun updateCommissions(code: String, referentCommission: BigDecimal) + suspend fun deleteByCode(code: String) + suspend fun deleteByReferrerUuid(uuid: String) +} diff --git a/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/WalletProxy.kt b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/WalletProxy.kt new file mode 100644 index 000000000..bc192405d --- /dev/null +++ b/referral/referral-core/src/main/kotlin/co/nilin/opex/referral/core/spi/WalletProxy.kt @@ -0,0 +1,18 @@ +package co.nilin.opex.referral.core.spi + +import java.math.BigDecimal + +interface WalletProxy { + suspend fun transfer( + symbol: String, + senderWalletType: String, + senderUuid: String, + receiverWalletType: String, + receiverUuid: String, + amount: BigDecimal, + description: String?, + transferRef: String? + ) + + suspend fun canFulfil(symbol: String, walletType: String, uuid: String, amount: BigDecimal): Boolean +} \ No newline at end of file diff --git a/referral/referral-ports/referral-api-proxy/pom.xml b/referral/referral-ports/referral-api-proxy/pom.xml new file mode 100644 index 000000000..1beb0ee61 --- /dev/null +++ b/referral/referral-ports/referral-api-proxy/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + + referral + co.nilin.opex.referral + 1.0-SNAPSHOT + ../../pom.xml + + + co.nilin.opex.referral.ports.api.proxy + referral-api-proxy + referral-api-proxy + + + + org.jetbrains.kotlin + kotlin-reflect + + + org.springframework.boot + spring-boot-starter + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + co.nilin.opex.referral.core + referral-core + + + co.nilin.opex.utility.log + logging-handler + + + org.springframework.cloud + spring-cloud-starter-consul-all + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + diff --git a/referral/referral-ports/referral-api-proxy/src/main/kotlin/co/nilin/opex/referral/ports/api/proxy/impl/SymbolPriceCalculatorImpl.kt b/referral/referral-ports/referral-api-proxy/src/main/kotlin/co/nilin/opex/referral/ports/api/proxy/impl/SymbolPriceCalculatorImpl.kt new file mode 100644 index 000000000..c3bddf944 --- /dev/null +++ b/referral/referral-ports/referral-api-proxy/src/main/kotlin/co/nilin/opex/referral/ports/api/proxy/impl/SymbolPriceCalculatorImpl.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.referral.ports.api.proxy.impl + +import co.nilin.opex.referral.core.api.SymbolPriceCalculator +import co.nilin.opex.referral.core.spi.ApiProxy +import org.springframework.stereotype.Service +import java.math.BigDecimal + +@Service +class SymbolPriceCalculatorImpl(private val apiProxy: ApiProxy) : SymbolPriceCalculator { + override suspend fun getPrice(symbol: String, paymentCurrency: String): BigDecimal { + return if (paymentCurrency == symbol) BigDecimal.ONE else + apiProxy.fetchLastPrice("$symbol$paymentCurrency") ?: apiProxy.fetchLastPrice("$paymentCurrency$symbol") + ?.takeIf { it > BigDecimal.ZERO } + ?.let { BigDecimal.ONE / it } ?: BigDecimal.ZERO + } +} diff --git a/referral/referral-ports/referral-api-proxy/src/main/kotlin/co/nilin/opex/referral/ports/api/proxy/proxy/ApiProxyImpl.kt b/referral/referral-ports/referral-api-proxy/src/main/kotlin/co/nilin/opex/referral/ports/api/proxy/proxy/ApiProxyImpl.kt new file mode 100644 index 000000000..78906dfc2 --- /dev/null +++ b/referral/referral-ports/referral-api-proxy/src/main/kotlin/co/nilin/opex/referral/ports/api/proxy/proxy/ApiProxyImpl.kt @@ -0,0 +1,34 @@ +package co.nilin.opex.referral.ports.api.proxy.proxy + +import co.nilin.opex.referral.core.spi.ApiProxy +import kotlinx.coroutines.reactive.awaitFirst +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.ParameterizedTypeReference +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import java.math.BigDecimal +import java.net.URI + +data class PriceTickerResponse( + val symbol: String?, + val price: String? +) + +inline fun typeRef(): ParameterizedTypeReference = object : ParameterizedTypeReference() {} + +@Component +class ApiProxyImpl( + @Value("\${app.api.url}") val apiBaseUrl: String, val webClient: WebClient +) : ApiProxy { + override suspend fun fetchLastPrice(pairSymbol: String): BigDecimal? { + val list = webClient.get() + .uri(URI.create("$apiBaseUrl/v3/ticker/price?symbol=${pairSymbol.uppercase()}")) + .header("Content-Type", "application/json") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono(typeRef>()) + .log() + .awaitFirst() + return list.first().price?.let { BigDecimal(it) } + } +} diff --git a/referral/referral-ports/referral-eventlistener-kafka/pom.xml b/referral/referral-ports/referral-eventlistener-kafka/pom.xml new file mode 100644 index 000000000..814f07332 --- /dev/null +++ b/referral/referral-ports/referral-eventlistener-kafka/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + + co.nilin.opex.referral + referral + 1.0-SNAPSHOT + ../../pom.xml + + + co.nilin.opex.referral.ports.kafka.listener + referral-eventlistener-kafka + referral-eventlistener-kafka + Referral kafka listener of Opex + + + + org.springframework.boot + spring-boot-starter + + + co.nilin.opex.matching.engine.core + matching-engine-core + + + co.nilin.opex.accountant.core + accountant-core + + + org.springframework.kafka + spring-kafka + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.kafka + spring-kafka-test + test + + + diff --git a/referral/referral-ports/referral-eventlistener-kafka/src/main/kotlin/co/nilin/opex/referral/ports/kafka/listener/config/KafkaConfig.kt b/referral/referral-ports/referral-eventlistener-kafka/src/main/kotlin/co/nilin/opex/referral/ports/kafka/listener/config/KafkaConfig.kt new file mode 100644 index 000000000..f36164f8e --- /dev/null +++ b/referral/referral-ports/referral-eventlistener-kafka/src/main/kotlin/co/nilin/opex/referral/ports/kafka/listener/config/KafkaConfig.kt @@ -0,0 +1,94 @@ +package co.nilin.opex.referral.ports.kafka.listener.config + +import co.nilin.opex.matching.engine.core.eventh.events.CoreEvent +import co.nilin.opex.referral.ports.kafka.listener.consumer.RichTradeKafkaListener +import co.nilin.opex.referral.ports.kafka.listener.spi.RichTradeListener +import org.apache.kafka.clients.admin.NewTopic +import org.apache.kafka.clients.consumer.ConsumerConfig +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.config.TopicConfig +import org.apache.kafka.common.serialization.StringDeserializer +import org.apache.kafka.common.serialization.StringSerializer +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.GenericApplicationContext +import org.springframework.kafka.config.TopicBuilder +import org.springframework.kafka.core.* +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer +import org.springframework.kafka.listener.ContainerProperties +import org.springframework.kafka.support.serializer.JsonDeserializer +import org.springframework.kafka.support.serializer.JsonSerializer +import java.util.function.Supplier +import java.util.regex.Pattern + +@Configuration +class KafkaConfig { + @Value("\${spring.kafka.bootstrap-servers}") + private val bootstrapServers: String? = null + + @Value("\${spring.kafka.consumer.group-id}") + private val groupId: String? = null + + @Bean("referralConsumerConfig") + fun consumerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ConsumerConfig.GROUP_ID_CONFIG] = groupId + props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java + props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java + props[JsonDeserializer.TRUSTED_PACKAGES] = "co.nilin.opex.*" + return props + } + + @Bean("referralConsumerFactory") + fun consumerFactory(@Qualifier("referralConsumerConfig") consumerConfigs: Map): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerConfigs) + } + + @Bean("referralProducerConfig") + fun producerConfigs(): Map { + val props: MutableMap = HashMap() + props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return props + } + + @Bean("referralProducerFactory") + fun producerFactory(@Qualifier("referralProducerConfig") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("referralKafkaTemplate") + fun kafkaTemplate(@Qualifier("referralProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + @Autowired + @ConditionalOnBean(RichTradeListener::class) + fun configureTradeListener( + richTradeListener: RichTradeKafkaListener, + @Qualifier("referralConsumerFactory") consumerFactory: ConsumerFactory + ) { + val containerProps = ContainerProperties(Pattern.compile("richTrade")) + containerProps.messageListener = richTradeListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("ReferralRichTradeKafkaListenerContainer") + container.start() + } + + @Autowired + fun createTopics(applicationContext: GenericApplicationContext) { + applicationContext.registerBean("topic_richTrade", NewTopic::class.java, Supplier { + TopicBuilder.name("richTrade") + .partitions(10) + .replicas(3) + .config(TopicConfig.MIN_IN_SYNC_REPLICAS_CONFIG, "2") + .build() + }) + } +} diff --git a/referral/referral-ports/referral-eventlistener-kafka/src/main/kotlin/co/nilin/opex/referral/ports/kafka/listener/consumer/RichTradeKafkaListener.kt b/referral/referral-ports/referral-eventlistener-kafka/src/main/kotlin/co/nilin/opex/referral/ports/kafka/listener/consumer/RichTradeKafkaListener.kt new file mode 100644 index 000000000..a50ff4f1b --- /dev/null +++ b/referral/referral-ports/referral-eventlistener-kafka/src/main/kotlin/co/nilin/opex/referral/ports/kafka/listener/consumer/RichTradeKafkaListener.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.referral.ports.kafka.listener.consumer + +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.referral.ports.kafka.listener.spi.RichTradeListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class RichTradeKafkaListener : MessageListener { + val tradeListeners = arrayListOf() + + override fun onMessage(data: ConsumerRecord) { + tradeListeners.forEach { tl -> + tl.onTrade(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + + fun addTradeListener(tl: RichTradeListener) { + tradeListeners.add(tl) + } + + fun removeTradeListener(tl: RichTradeListener) { + tradeListeners.removeIf { item -> + item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/referral/referral-ports/referral-eventlistener-kafka/src/main/kotlin/co/nilin/opex/referral/ports/kafka/listener/spi/RichTradeListener.kt b/referral/referral-ports/referral-eventlistener-kafka/src/main/kotlin/co/nilin/opex/referral/ports/kafka/listener/spi/RichTradeListener.kt new file mode 100644 index 000000000..5106c7599 --- /dev/null +++ b/referral/referral-ports/referral-eventlistener-kafka/src/main/kotlin/co/nilin/opex/referral/ports/kafka/listener/spi/RichTradeListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.referral.ports.kafka.listener.spi + +import co.nilin.opex.accountant.core.inout.RichTrade + +interface RichTradeListener { + fun id(): String + fun onTrade(richTrade: RichTrade, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/referral/referral-ports/referral-persister-postgres/pom.xml b/referral/referral-ports/referral-persister-postgres/pom.xml new file mode 100644 index 000000000..b84759bc9 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + referral + co.nilin.opex.referral + 1.0-SNAPSHOT + ../../pom.xml + + + co.nilin.opex.referral.ports.postgres + referral-persister-postgres + referral-persister-postgres + + + + org.jetbrains.kotlin + kotlin-reflect + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + runtime + + + org.postgresql + postgresql + runtime + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + co.nilin.opex.referral.core + referral-core + + + diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/config/PostgresConfig.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/config/PostgresConfig.kt new file mode 100644 index 000000000..65ef9e3e3 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/config/PostgresConfig.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.referral.ports.postgres.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Configuration +import org.springframework.core.io.Resource +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories +import org.springframework.r2dbc.core.DatabaseClient + +@Configuration +@EnableR2dbcRepositories(basePackages = ["co.nilin.opex"]) +class PostgresConfig( + db: DatabaseClient, + @Value("classpath:schema.sql") private val schemaResource: Resource, + @Value("classpath:data.sql") private val dataResource: Resource? +) { + init { + val schemaReader = schemaResource.inputStream.reader() + val schema = schemaReader.readText().trim() + schemaReader.close() + val dataReader = dataResource?.inputStream?.reader() + val data = dataReader?.readText()?.trim() ?: "" + dataReader?.close() + val initDb = db.sql { "$schema\n\n$data" } + initDb // initialize the database + .then() + .subscribe() // execute + } +} + diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/CheckoutRecord.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/CheckoutRecord.kt new file mode 100644 index 000000000..68ca59c24 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/CheckoutRecord.kt @@ -0,0 +1,33 @@ +package co.nilin.opex.referral.ports.postgres.dao + +import co.nilin.opex.matching.engine.core.model.OrderDirection +import co.nilin.opex.referral.core.model.CheckoutState +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal +import java.time.LocalDateTime + +@Table("checkout_records") +data class CheckoutRecord( + @Id var id: Long?, + var commissionRewardId: Long, + var transferRef: String?, + var updateDate: LocalDateTime, + var checkoutState: CheckoutState +) + +data class CheckoutRecordProjected( + @Id var id: Long?, + var commissionRewardId: Long, + var rewardedUuid: String, + var referentUuid: String, + var referralCode: String, + var richTradeId: Long, + var referentOrderDirection: OrderDirection, + var share: BigDecimal, + var paymentCurrency: String, + var transferRef: String?, + var createDate: LocalDateTime, + var updateDate: LocalDateTime, + var checkoutState: CheckoutState +) diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/CheckoutState.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/CheckoutState.kt new file mode 100644 index 000000000..f18de05c4 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/CheckoutState.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.referral.ports.postgres.dao + +import co.nilin.opex.referral.core.model.CheckoutState +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table + +@Table("checkout_states") +data class CheckoutState(@Id var state: CheckoutState) diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/CommissionReward.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/CommissionReward.kt new file mode 100644 index 000000000..9e16cd838 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/CommissionReward.kt @@ -0,0 +1,20 @@ +package co.nilin.opex.referral.ports.postgres.dao + +import co.nilin.opex.matching.engine.core.model.OrderDirection +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal +import java.time.LocalDateTime + +@Table("commission_rewards") +data class CommissionReward( + @Id var id: Long?, + var rewardedUuid: String, + var referentUuid: String, + var referralCode: String, + var richTradeId: Long, + var referentOrderDirection: OrderDirection, + var share: BigDecimal, + var paymentCurrency: String, + var createDate: LocalDateTime? = null +) diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/Config.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/Config.kt new file mode 100644 index 000000000..4829f674b --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/Config.kt @@ -0,0 +1,15 @@ +package co.nilin.opex.referral.ports.postgres.dao + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table("configs") +data class Config( + @Id var name: String, + var referralCommissionReward: BigDecimal, + var paymentCurrency: String, + var minPaymentAmount: BigDecimal, + var paymentWindowSeconds: Int, + var maxReferralCodePerUser: Int +) diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/Reference.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/Reference.kt new file mode 100644 index 000000000..eab8a63c9 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/Reference.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.referral.ports.postgres.dao + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table + +@Table("referral_code_references") +data class Reference( + @Id var id: Long?, + var referentUuid: String, + var referralCodeId: Long +) diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/ReferralCode.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/ReferralCode.kt new file mode 100644 index 000000000..ae9a587f8 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/dao/ReferralCode.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.referral.ports.postgres.dao + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table("referral_codes") +data class ReferralCode( + @Id var id: Long?, + var uuid: String, + var code: String, + var referentCommission: BigDecimal +) diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/CheckoutRecordHandlerImpl.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/CheckoutRecordHandlerImpl.kt new file mode 100644 index 000000000..aa6341826 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/CheckoutRecordHandlerImpl.kt @@ -0,0 +1,116 @@ +package co.nilin.opex.referral.ports.postgres.impl + +import co.nilin.opex.referral.core.model.CheckoutRecord +import co.nilin.opex.referral.core.model.CheckoutState +import co.nilin.opex.referral.core.model.CommissionReward +import co.nilin.opex.referral.core.spi.CheckoutRecordHandler +import co.nilin.opex.referral.ports.postgres.repository.CheckoutRecordRepository +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle +import org.springframework.stereotype.Service +import java.math.BigDecimal +import java.util.* + +@Service +class CheckoutRecordHandlerImpl(private val checkoutRecordRepository: CheckoutRecordRepository) : + CheckoutRecordHandler { + override suspend fun findCommissionsByCheckoutState(checkoutState: CheckoutState): List { + return checkoutRecordRepository.findByCheckoutStateProjected(checkoutState).map { + CheckoutRecord( + CommissionReward( + it.commissionRewardId, + it.rewardedUuid, + it.referentUuid, + it.referralCode, + Pair(it.richTradeId, null), + it.referentOrderDirection, + it.share, + it.paymentCurrency, + it.createDate + ), + it.checkoutState, + it.transferRef, + it.updateDate + ) + }.collectList().awaitFirstOrNull() ?: emptyList() + } + + override suspend fun findUserCommissionsWhereTotalGreaterAndEqualTo( + uuid: String, + value: BigDecimal + ): List { + return checkoutRecordRepository.findByUuidWhereTotalShareMoreThanProjected(uuid, value) + .collectList().awaitSingle().map { + CheckoutRecord( + CommissionReward( + it.commissionRewardId, + it.rewardedUuid, + it.referentUuid, + it.referralCode, + Pair(it.richTradeId, null), + it.referentOrderDirection, + it.share, + it.paymentCurrency, + it.createDate + ), + it.checkoutState, + it.transferRef, + it.updateDate + ) + } + } + + override suspend fun findAllCommissionsWhereTotalGreaterAndEqualTo(value: BigDecimal): List { + return checkoutRecordRepository.findAllWhereTotalShareMoreThanProjected(value) + .collectList().awaitSingle().map { + CheckoutRecord( + CommissionReward( + it.commissionRewardId, + it.rewardedUuid, + it.referentUuid, + it.referralCode, + Pair(it.richTradeId, null), + it.referentOrderDirection, + it.share, + it.paymentCurrency, + it.createDate + ), + it.checkoutState, + it.transferRef, + it.updateDate + ) + } + } + + override suspend fun findCommissionsWherePendingDateLessOrEqualThan(date: Date): List { + return checkoutRecordRepository.findByCheckoutStateWhereCreateDateLessThanProjected( + CheckoutState.PENDING, + date + ).collectList().awaitSingle().map { + CheckoutRecord( + CommissionReward( + it.commissionRewardId, + it.rewardedUuid, + it.referentUuid, + it.referralCode, + Pair(it.richTradeId, null), + it.referentOrderDirection, + it.share, + it.paymentCurrency, + it.createDate + ), + it.checkoutState, + it.transferRef, + it.updateDate + ) + } + } + + override suspend fun updateCheckoutState(id: Long, value: CheckoutState) { + checkoutRecordRepository.updateCheckoutStateById(id, value) + } + + override suspend fun checkout(id: Long, transferRef: String) { + checkoutRecordRepository.checkout(id, transferRef) + } +} diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/CommissionRewardHandlerImpl.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/CommissionRewardHandlerImpl.kt new file mode 100644 index 000000000..4f264d83f --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/CommissionRewardHandlerImpl.kt @@ -0,0 +1,48 @@ +package co.nilin.opex.referral.ports.postgres.impl + +import co.nilin.opex.referral.core.model.CommissionReward +import co.nilin.opex.referral.core.spi.CommissionRewardHandler +import co.nilin.opex.referral.ports.postgres.repository.CommissionRewardRepository +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.stereotype.Service + +@Service +class CommissionRewardHandlerImpl( + private val commissionRewardRepository: CommissionRewardRepository +) : CommissionRewardHandler { + override suspend fun findCommissions( + referralCode: String?, + rewardedUuid: String?, + referentUuid: String? + ): List { + return commissionRewardRepository.findByReferralCodeAndRewardedUuidAndReferentUuid( + referralCode, + rewardedUuid, + referentUuid + ).map { + CommissionReward( + it.id!!, + it.rewardedUuid, + it.referentUuid, + it.referralCode, + Pair(it.richTradeId, null), + it.referentOrderDirection, + it.share, + it.paymentCurrency, + it.createDate!! + ) + }.collectList().awaitSingleOrNull() ?: emptyList() + } + + override suspend fun deleteCommissions(referralCode: String?, rewardedUuid: String?, referentUuid: String?) { + commissionRewardRepository.deleteByReferralCodeAndRewardedUuidAndReferentUuid( + referralCode, + rewardedUuid, + referentUuid + ).awaitSingleOrNull() + } + + override suspend fun deleteCommissionById(id: Long) { + commissionRewardRepository.deleteById(id).awaitSingleOrNull() + } +} diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/CommissionRewardPersisterImpl.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/CommissionRewardPersisterImpl.kt new file mode 100644 index 000000000..a0b3982a1 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/CommissionRewardPersisterImpl.kt @@ -0,0 +1,26 @@ +package co.nilin.opex.referral.ports.postgres.impl + +import co.nilin.opex.referral.core.model.CommissionReward +import co.nilin.opex.referral.core.spi.CommissionRewardPersister +import co.nilin.opex.referral.ports.postgres.repository.CommissionRewardRepository +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.stereotype.Service + +@Service +class CommissionRewardPersisterImpl(private val commissionRewardRepository: CommissionRewardRepository) : + CommissionRewardPersister { + override suspend fun save(commissionReward: CommissionReward) { + commissionRewardRepository.save( + co.nilin.opex.referral.ports.postgres.dao.CommissionReward( + if (commissionReward.id == 0L) null else commissionReward.id, + commissionReward.rewardedUuid, + commissionReward.referentUuid, + commissionReward.referralCode, + commissionReward.richTrade.first, + commissionReward.referentOrderDirection, + commissionReward.share, + commissionReward.paymentCurrency + ) + ).awaitSingleOrNull() + } +} diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/ConfigHandlerImpl.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/ConfigHandlerImpl.kt new file mode 100644 index 000000000..9c933fe9f --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/ConfigHandlerImpl.kt @@ -0,0 +1,25 @@ +package co.nilin.opex.referral.ports.postgres.impl + +import co.nilin.opex.referral.core.model.Config +import co.nilin.opex.referral.core.spi.ConfigHandler +import co.nilin.opex.referral.ports.postgres.repository.ConfigRepository +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.stereotype.Service + +@Service +class ConfigHandlerImpl(private val configRepository: ConfigRepository) : ConfigHandler { + override suspend fun findConfig(name: String): Config? { + return configRepository.findById(name) + .map { + Config( + it.name, + it.referralCommissionReward, + it.paymentCurrency, + it.minPaymentAmount, + it.paymentWindowSeconds, + it.maxReferralCodePerUser + ) + } + .awaitSingleOrNull() + } +} diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/ReferenceHandlerImpl.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/ReferenceHandlerImpl.kt new file mode 100644 index 000000000..bb0accd0f --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/ReferenceHandlerImpl.kt @@ -0,0 +1,50 @@ +package co.nilin.opex.referral.ports.postgres.impl + +import co.nilin.opex.referral.core.model.Reference +import co.nilin.opex.referral.core.model.ReferralCode +import co.nilin.opex.referral.core.spi.ReferenceHandler +import co.nilin.opex.referral.ports.postgres.repository.ReferenceRepository +import co.nilin.opex.referral.ports.postgres.repository.ReferralCodeRepository +import kotlinx.coroutines.reactor.awaitSingle +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.stereotype.Service + +@Service +class ReferenceHandlerImpl( + private val referralCodeRepository: ReferralCodeRepository, + private val referenceRepository: ReferenceRepository +) : ReferenceHandler { + override suspend fun findAll(): List { + val refs = referenceRepository.findAll().collectList().awaitSingle() + return refs.map { ref -> + val referralCode = referralCodeRepository.findById(ref.referralCodeId).map { + ReferralCode(it.uuid, it.code, it.referentCommission) + }.awaitSingle() + Reference(referralCode, ref.referentUuid) + } + } + + override suspend fun findByReferentUuid(uuid: String): Reference? { + val ref = referenceRepository.findByReferentUuid(uuid).awaitSingleOrNull() ?: return null + val referralCode = referralCodeRepository.findById(ref.referralCodeId).map { + ReferralCode(it.uuid, it.code, it.referentCommission) + }.awaitSingle() + return Reference(referralCode, ref.referentUuid) + } + + override suspend fun findByReferrerUuid(uuid: String): List { + val referralCode = referralCodeRepository.findByUuid(uuid) + .map { it.id!! to ReferralCode(it.uuid, it.code, it.referentCommission) } + .collectList().awaitSingle().toMap() + return if (referralCode.isNotEmpty()) referenceRepository.findByReferrerUuid(uuid).collectList().awaitSingle() + .map { Reference(referralCode.getValue(it.referralCodeId), it.referentUuid) } else emptyList() + } + + override suspend fun findByCode(code: String): List { + val ref = referenceRepository.findByCode(code).collectList().awaitSingle() + val referralCode = referralCodeRepository.findByCode(code).map { + ReferralCode(it.uuid, it.code, it.referentCommission) + }.awaitSingle() + return ref.map { Reference(referralCode, it.referentUuid) } + } +} diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/ReferralCodeHandlerImpl.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/ReferralCodeHandlerImpl.kt new file mode 100644 index 000000000..4aa950e96 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/impl/ReferralCodeHandlerImpl.kt @@ -0,0 +1,85 @@ +package co.nilin.opex.referral.ports.postgres.impl + +import co.nilin.opex.referral.core.model.ReferralCode +import co.nilin.opex.referral.core.spi.ReferralCodeHandler +import co.nilin.opex.referral.ports.postgres.dao.Reference +import co.nilin.opex.referral.ports.postgres.repository.ReferenceRepository +import co.nilin.opex.referral.ports.postgres.repository.ReferralCodeRepository +import kotlinx.coroutines.reactive.awaitSingle +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.stereotype.Service +import java.math.BigDecimal +import java.math.BigInteger + +@Service +class ReferralCodeHandlerImpl( + private val referralCodeRepository: ReferralCodeRepository, + private val referenceRepository: ReferenceRepository +) : ReferralCodeHandler { + override suspend fun generateReferralCode( + uuid: String, + referentCommission: BigDecimal + ): String { + if (referentCommission < BigDecimal.ZERO || referentCommission > BigDecimal.ONE) + throw IllegalArgumentException("Commission value must be in range of [0, 1]") + val lastId = referralCodeRepository.findMaxId().awaitSingleOrNull() ?: 0 + val codeInteger = BigInteger.TEN.pow(4).toLong() + lastId + if (codeInteger >= BigInteger.TEN.pow(10).toLong()) throw Exception("No referral code available") + val code = codeInteger.toString() + val referralCode = co.nilin.opex.referral.ports.postgres.dao.ReferralCode(null, uuid, code, referentCommission) + referralCodeRepository.save(referralCode).awaitSingleOrNull() + return code + } + + override suspend fun findAll(): List { + return referralCodeRepository.findAll().map { ReferralCode(it.uuid, it.code, it.referentCommission) } + .collectList().awaitSingle() + } + + override suspend fun findByReferentUuid(uuid: String): ReferralCode? { + val referral = referenceRepository.findByReferentUuid(uuid).awaitSingleOrNull() ?: return null + return referralCodeRepository.findById(referral.referralCodeId) + .map { ReferralCode(it.uuid, it.code, it.referentCommission) } + .awaitSingleOrNull() + } + + override suspend fun findByReferrerUuid(uuid: String): List { + return referralCodeRepository.findByUuid(uuid).map { ReferralCode(it.uuid, it.code, it.referentCommission) } + .collectList() + .awaitSingleOrNull() ?: emptyList() + } + + override suspend fun findByCode(code: String): ReferralCode? { + return referralCodeRepository.findByCode(code) + .map { ReferralCode(it.uuid, it.code, it.referentCommission) } + .awaitSingle() + } + + override suspend fun assign(code: String, referentUuid: String) { + val referralCode = referralCodeRepository.findByCode(code).awaitSingleOrNull() + ?: throw IllegalArgumentException("Referral code doesn't exist") + if (referentUuid == referralCode.uuid) throw IllegalArgumentException("Can't assign referral code to referrer") + val referents = referenceRepository.findByReferrerUuid(referentUuid) + val isChild = referents.any { it.referentUuid == referralCode.uuid }.awaitSingle() + if (isChild) throw IllegalArgumentException("Referrer can't be child of referent") + val reference = Reference(null, referentUuid, referralCode.id!!) + referenceRepository.save(reference).awaitSingleOrNull() + } + + override suspend fun updateCommissions( + code: String, + referentCommission: BigDecimal + ) { + if (referentCommission < BigDecimal.ZERO || referentCommission > BigDecimal.ONE) + throw IllegalArgumentException("Commission value must be in range of [0, 1]") + referralCodeRepository.updateByCode(code, referentCommission).awaitSingleOrNull() + } + + override suspend fun deleteByCode(code: String) { + referralCodeRepository.deleteByCode(code).awaitSingleOrNull() + } + + override suspend fun deleteByReferrerUuid(uuid: String) { + referralCodeRepository.deleteByUuid(uuid).awaitSingleOrNull() + } +} diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/CheckoutRecordRepository.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/CheckoutRecordRepository.kt new file mode 100644 index 000000000..3b8d117cc --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/CheckoutRecordRepository.kt @@ -0,0 +1,61 @@ +package co.nilin.opex.referral.ports.postgres.repository + +import co.nilin.opex.referral.core.model.CheckoutState +import co.nilin.opex.referral.ports.postgres.dao.CheckoutRecord +import co.nilin.opex.referral.ports.postgres.dao.CheckoutRecordProjected +import org.springframework.data.r2dbc.repository.Modifying +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux +import java.math.BigDecimal +import java.util.* + +@Repository +interface CheckoutRecordRepository : CheckoutRecordProjectedRepository, ReactiveCrudRepository { + @Modifying + @Query("INSERT INTO checkout_records(commission_rewards_id, checkout_state) VALUES (:id, :checkoutState)") + suspend fun updateCheckoutStateById(id: Long, checkoutState: CheckoutState) + + @Modifying + @Query("INSERT INTO checkout_records(commission_rewards_id, transfer_ref, checkout_state) VALUES (:id, :transferRef, 'CHECKED_OUT')") + suspend fun checkout(id: Long, transferRef: String) +} + +interface CheckoutRecordProjectedRepository { + @Query("SELECT * FROM checkout_records_projected WHERE checkout_state = :checkoutState") + suspend fun findByCheckoutStateProjected(checkoutState: CheckoutState): Flux + + @Query("SELECT * FROM checkout_records_projected WHERE checkout_state = :checkoutState AND create_date < :createDate") + suspend fun findByCheckoutStateWhereCreateDateLessThanProjected( + checkoutState: CheckoutState, + createData: Date + ): Flux + + @Query( + """ + WITH s AS ( + SELECT *, SUM(share) OVER (PARTITION BY rewarded_uuid) AS acc_share + FROM checkout_records_projected + WHERE checkout_state = 'PENDING' + ) + SELECT * FROM s WHERE acc_share >= :value + """ + ) + suspend fun findAllWhereTotalShareMoreThanProjected(value: BigDecimal): Flux + + @Query( + """ + WITH s AS ( + SELECT *, SUM(share) OVER (PARTITION BY rewarded_uuid) AS acc_share + FROM checkout_records_projected + WHERE checkout_state = 'PENDING' AND rewarded_uuid = :uuid + ) + SELECT * FROM s WHERE acc_share >= :value + """ + ) + suspend fun findByUuidWhereTotalShareMoreThanProjected( + uuid: String, + value: BigDecimal + ): Flux +} diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/CheckoutStateRepository.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/CheckoutStateRepository.kt new file mode 100644 index 000000000..343e7d407 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/CheckoutStateRepository.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.referral.ports.postgres.repository + +import co.nilin.opex.referral.ports.postgres.dao.CheckoutState +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface CheckoutStateRepository : ReactiveCrudRepository \ No newline at end of file diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/CommissionRewardRepository.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/CommissionRewardRepository.kt new file mode 100644 index 000000000..d2fef125b --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/CommissionRewardRepository.kt @@ -0,0 +1,25 @@ +package co.nilin.opex.referral.ports.postgres.repository + +import co.nilin.opex.referral.ports.postgres.dao.CommissionReward +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono + +@Repository +interface CommissionRewardRepository : ReactiveCrudRepository { + @Query("SELECT * FROM commission_rewards WHERE (:code is null OR referral_code = :code) AND (:rewardedUuid is null OR rewarded_uuid = :rewardedUuid) AND (:referentUuid is null OR referent_uuid = :referentUuid)") + fun findByReferralCodeAndRewardedUuidAndReferentUuid( + code: String?, + rewardedUuid: String?, + referentUuid: String? + ): Flux + + @Query("DELETE FROM commission_rewards WHERE (:code is null OR referral_code = :code) AND (:rewardedUuid is null OR rewarded_uuid = :rewardedUuid) AND (:referentUuid is null OR referent_uuid = :referentUuid)") + fun deleteByReferralCodeAndRewardedUuidAndReferentUuid( + code: String?, + rewardedUuid: String?, + referentUuid: String? + ): Mono +} \ No newline at end of file diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/ConfigRepository.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/ConfigRepository.kt new file mode 100644 index 000000000..a5dd31c76 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/ConfigRepository.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.referral.ports.postgres.repository + +import co.nilin.opex.referral.ports.postgres.dao.Config +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface ConfigRepository : ReactiveCrudRepository \ No newline at end of file diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/ReferenceRepository.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/ReferenceRepository.kt new file mode 100644 index 000000000..e6ad6a71a --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/ReferenceRepository.kt @@ -0,0 +1,20 @@ +package co.nilin.opex.referral.ports.postgres.repository + +import co.nilin.opex.referral.ports.postgres.dao.Reference +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono + +@Repository +interface ReferenceRepository : ReactiveCrudRepository { + @Query("SELECT * FROM referral_code_references LEFT JOIN referral_codes ON referral_code_id = referral_codes.id WHERE code = :code") + fun findByCode(code: String): Flux + + @Query("SELECT * FROM referral_code_references LEFT JOIN referral_codes ON referral_code_id = referral_codes.id WHERE referral_codes.uuid = :uuid") + fun findByReferrerUuid(referrerUuid: String): Flux + fun findByReferentUuid(referrerUuid: String): Mono + fun deleteByReferentUuid(referrerUuid: String) + fun deleteByReferralCodeId(id: Long): Flux +} diff --git a/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/ReferralCodeRepository.kt b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/ReferralCodeRepository.kt new file mode 100644 index 000000000..6ff1f721a --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/kotlin/co/nilin/opex/referral/ports/postgres/repository/ReferralCodeRepository.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.referral.ports.postgres.repository + +import co.nilin.opex.referral.ports.postgres.dao.ReferralCode +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import java.math.BigDecimal + +@Repository +interface ReferralCodeRepository : ReactiveCrudRepository { + + @Query("SELECT MAX(id) FROM referral_codes") + fun findMaxId(): Mono + fun findByCode(code: String): Mono + fun findByUuid(uuid: String): Flux + + @Query("UPDATE referral_codes SET referent_commission = COALESCE(:referentCommission, referent_commission) WHERE code = :code") + fun updateByCode(code: String, referentCommission: BigDecimal?): Mono + + fun deleteByUuid(uuid: String): Mono + fun deleteByCode(code: String): Mono +} diff --git a/referral/referral-ports/referral-persister-postgres/src/main/resources/data.sql b/referral/referral-ports/referral-persister-postgres/src/main/resources/data.sql new file mode 100644 index 000000000..f11e61c8b --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/resources/data.sql @@ -0,0 +1,3 @@ +INSERT INTO configs(name, referral_commission_reward, payment_currency, min_payment_amount, payment_window_seconds, max_referral_code_per_user) VALUES ('default', 0.3, 'usdt', 0, 604800, 20) ON CONFLICT DO NOTHING; + +INSERT INTO checkout_states(state) VALUES ('PENDING'), ('CHECKED_OUT') ON CONFLICT DO NOTHING; diff --git a/referral/referral-ports/referral-persister-postgres/src/main/resources/schema.sql b/referral/referral-ports/referral-persister-postgres/src/main/resources/schema.sql new file mode 100644 index 000000000..d4f70e6c9 --- /dev/null +++ b/referral/referral-ports/referral-persister-postgres/src/main/resources/schema.sql @@ -0,0 +1,84 @@ +CREATE TABLE IF NOT EXISTS configs ( + name VARCHAR(72) PRIMARY KEY, + referral_commission_reward DECIMAL NOT NULL, + payment_currency VARCHAR(20) NOT NULL, + min_payment_amount DECIMAL NOT NULL, + payment_window_seconds INTEGER NOT NULL, + max_referral_code_per_user INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS referral_codes ( + id SERIAL PRIMARY KEY, + uuid VARCHAR(72) NOT NULL, + code VARCHAR(255) NOT NULL UNIQUE, + referent_commission DECIMAL NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS code_index ON referral_codes(code); + +CREATE TABLE IF NOT EXISTS referral_code_references ( + id SERIAL PRIMARY KEY, + referent_uuid VARCHAR(72) NOT NULL UNIQUE, + referral_code_id INTEGER NOT NULL REFERENCES referral_codes(id), + UNIQUE(referent_uuid, referral_code_id) +); + +CREATE TABLE IF NOT EXISTS checkout_states ( + state VARCHAR(20) PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS commission_rewards ( + id BIGSERIAL PRIMARY KEY, + rewarded_uuid VARCHAR(72) NOT NULL, + referent_uuid VARCHAR(72) NOT NULL, + referral_code VARCHAR(72) NOT NULL REFERENCES referral_codes(code), + rich_trade_id BIGINT NOT NULL, + referent_order_direction VARCHAR(20) NOT NULL, + share DECIMAL NOT NULL, + payment_currency VARCHAR(20) NOT NULL, + create_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT reward_once_constraint UNIQUE (rich_trade_id, rewarded_uuid, referent_order_direction) +); + +CREATE TABLE IF NOT EXISTS checkout_records ( + id BIGSERIAL PRIMARY KEY, + commission_rewards_id BIGINT NOT NULL REFERENCES commission_rewards(id), + transfer_ref VARCHAR(255), + update_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + checkout_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' REFERENCES checkout_states(state) +); + +CREATE INDEX IF NOT EXISTS checkout_records_state_index ON checkout_records(checkout_state); + +DROP VIEW IF EXISTS checkout_records_projected; + +CREATE VIEW checkout_records_projected +AS SELECT DISTINCT ON (commission_rewards_id) + checkout_records.id, + commission_rewards.id AS commission_rewards_id, + rewarded_uuid, + referent_uuid, + referral_code, + rich_trade_id, + referent_order_direction, + share, + checkout_state, + transfer_ref, + create_date, + update_date +FROM checkout_records +LEFT JOIN commission_rewards +ON commission_rewards_id = commission_rewards.id +ORDER BY commission_rewards_id, update_date DESC; + +CREATE OR REPLACE FUNCTION on_insert_commission_rewards() RETURNS TRIGGER AS $$ BEGIN + INSERT INTO checkout_records(commission_rewards_id) VALUES (NEW.id); + RETURN NEW; +END; $$ LANGUAGE 'plpgsql'; + +DROP TRIGGER IF EXISTS commission_rewards_insert ON commission_rewards CASCADE; + +CREATE TRIGGER commission_rewards_insert AFTER INSERT +ON commission_rewards +FOR EACH ROW +EXECUTE PROCEDURE on_insert_commission_rewards(); diff --git a/referral/referral-ports/referral-wallet-proxy/pom.xml b/referral/referral-ports/referral-wallet-proxy/pom.xml new file mode 100644 index 000000000..854dc2e2e --- /dev/null +++ b/referral/referral-ports/referral-wallet-proxy/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + + referral + co.nilin.opex.referral + 1.0-SNAPSHOT + ../../pom.xml + + + co.nilin.opex.referral.ports.wallet.proxy + referral-wallet-proxy + referral-wallet-proxy + + + + org.jetbrains.kotlin + kotlin-reflect + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + runtime + + + org.postgresql + postgresql + runtime + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + co.nilin.opex.referral.core + referral-core + + + co.nilin.opex.utility.log + logging-handler + + + org.springframework.cloud + spring-cloud-starter-consul-all + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + diff --git a/referral/referral-ports/referral-wallet-proxy/src/main/kotlin/co/nilin/opex/referral/ports/wallet/proxy/impl/CheckoutHandlerImpl.kt b/referral/referral-ports/referral-wallet-proxy/src/main/kotlin/co/nilin/opex/referral/ports/wallet/proxy/impl/CheckoutHandlerImpl.kt new file mode 100644 index 000000000..0224fe63e --- /dev/null +++ b/referral/referral-ports/referral-wallet-proxy/src/main/kotlin/co/nilin/opex/referral/ports/wallet/proxy/impl/CheckoutHandlerImpl.kt @@ -0,0 +1,61 @@ +package co.nilin.opex.referral.ports.wallet.proxy.impl + +import co.nilin.opex.referral.core.model.CheckoutRecord +import co.nilin.opex.referral.core.spi.CheckoutHandler +import co.nilin.opex.referral.core.spi.CheckoutRecordHandler +import co.nilin.opex.referral.core.spi.ConfigHandler +import co.nilin.opex.referral.core.spi.WalletProxy +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import org.springframework.stereotype.Service +import java.math.BigDecimal +import java.util.* + +@Service +class CheckoutHandlerImpl( + private val configHandler: ConfigHandler, + private val checkoutRecordHandler: CheckoutRecordHandler, + private val walletProxy: WalletProxy +) : CheckoutHandler { + override suspend fun checkoutById(uuid: String) { + val config = configHandler.findConfig("default")!! + val min = config.minPaymentAmount + val commissions = checkoutRecordHandler.findUserCommissionsWhereTotalGreaterAndEqualTo(uuid, min) + checkout(uuid, commissions) + } + + override suspend fun checkoutEveryCandidate(min: BigDecimal) { + val commissions = checkoutRecordHandler.findAllCommissionsWhereTotalGreaterAndEqualTo(min) + .groupBy { it.commissionReward.rewardedUuid } + coroutineScope { commissions.forEach { (uuid, c) -> checkout(uuid, c) } } + } + + override suspend fun checkoutOlderThan(date: Date) { + val commissions = checkoutRecordHandler.findCommissionsWherePendingDateLessOrEqualThan(date) + .groupBy { it.commissionReward.rewardedUuid } + coroutineScope { commissions.forEach { (uuid, c) -> checkout(uuid, c) } } + } + + private suspend fun checkout(uuid: String, commissions: Iterable) { + val m = commissions.groupBy { it.commissionReward.paymentCurrency } + val transferRef = UUID.randomUUID().toString() + coroutineScope { + m.forEach { (paymentCurrency, c) -> + val totalShare = c.sumOf { it.commissionReward.share } + if (walletProxy.canFulfil(paymentCurrency, "main", "1", totalShare)) { + walletProxy.transfer( + paymentCurrency, + "main", + "1", + "main", + uuid, + totalShare, + "", + transferRef + ) + c.forEach { launch { checkoutRecordHandler.checkout(it.commissionReward.id, transferRef) } } + } + } + } + } +} diff --git a/referral/referral-ports/referral-wallet-proxy/src/main/kotlin/co/nilin/opex/referral/ports/wallet/proxy/proxy/WalletProxyImpl.kt b/referral/referral-ports/referral-wallet-proxy/src/main/kotlin/co/nilin/opex/referral/ports/wallet/proxy/proxy/WalletProxyImpl.kt new file mode 100644 index 000000000..d8c36212e --- /dev/null +++ b/referral/referral-ports/referral-wallet-proxy/src/main/kotlin/co/nilin/opex/referral/ports/wallet/proxy/proxy/WalletProxyImpl.kt @@ -0,0 +1,65 @@ +package co.nilin.opex.referral.ports.wallet.proxy.proxy + +import co.nilin.opex.referral.core.spi.WalletProxy +import kotlinx.coroutines.reactive.awaitFirst +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.ParameterizedTypeReference +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import java.math.BigDecimal +import java.net.URI + +inline fun typeRef(): ParameterizedTypeReference = object : ParameterizedTypeReference() {} + +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) +data class Currency(val name: String, val symbol: String, val precision: Int) + +@Component +class WalletProxyImpl( + @Value("\${app.wallet.url}") val walletBaseUrl: String, val webClient: WebClient +) : WalletProxy { + override suspend fun transfer( + symbol: String, + senderWalletType: String, + senderUuid: String, + receiverWalletType: String, + receiverUuid: String, + amount: BigDecimal, + description: String?, + transferRef: String? + ) { + webClient.post() + .uri(URI.create("$walletBaseUrl/transfer/${amount}_${symbol}/from/${senderUuid}_${senderWalletType}/to/${receiverUuid}_${receiverWalletType}?description=$description&transferRef=$transferRef")) + .header("Content-Type", "application/json") + .retrieve() + .onStatus({ t -> t.isError }, { throw RuntimeException() }) + .bodyToMono(typeRef()) + .log() + .awaitFirst() + } + + override suspend fun canFulfil(symbol: String, walletType: String, uuid: String, amount: BigDecimal): Boolean { + data class BooleanResponse(val result: Boolean) + return webClient.get() + .uri(URI.create("$walletBaseUrl/$uuid/wallet_type/${walletType}/can_withdraw/${amount}_${symbol}")) + .header("Content-Type", "application/json") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono(typeRef()) + .log() + .awaitFirst() + .result + } +} diff --git a/resources/vault/workflow-vault.sh b/resources/vault/workflow-vault.sh index b4c39cb69..461f5a4d5 100755 --- a/resources/vault/workflow-vault.sh +++ b/resources/vault/workflow-vault.sh @@ -59,9 +59,10 @@ vault write auth/app-id/map/app-id/opex-wallet value=backend-policy display_name vault write auth/app-id/map/app-id/opex-websocket value=backend-policy display_name=opex-websocket vault write auth/app-id/map/app-id/opex-payment value=backend-policy display_name=opex-payment vault write auth/app-id/map/app-id/opex-admin value=backend-policy display_name=opex-admin -vault write auth/app-id/map/app-id/opex-chain-scan-gateway value=backend-policy display_name=opex-chain-scan-gateway +vault write auth/app-id/map/app-id/chain-scan-gateway value=backend-policy display_name=chain-scan-gateway +vault write auth/app-id/map/app-id/opex-referral value=backend-policy display_name=opex-referral echo 'enable user-id' -vault write auth/app-id/map/user-id/${BACKEND_USER} value=opex-wallet,opex-websocket,opex-eventlog,opex-auth,opex-accountant,opex-api,opex-bc-gateway,opex-payment,opex-admin,opex-chain-scan-gateway +vault write auth/app-id/map/user-id/${BACKEND_USER} value=opex-wallet,opex-websocket,opex-eventlog,opex-auth,opex-accountant,opex-api,opex-bc-gateway,opex-payment,opex-admin,chain-scan-gateway,opex-referral echo 'check login appid' vault write auth/app-id/login/opex-accountant user_id=${BACKEND_USER} vault write auth/app-id/login/opex-api user_id=${BACKEND_USER} @@ -72,7 +73,8 @@ vault write auth/app-id/login/opex-wallet user_id=${BACKEND_USER} vault write auth/app-id/login/opex-websocket user_id=${BACKEND_USER} vault write auth/app-id/login/opex-payment user_id=${BACKEND_USER} vault write auth/app-id/login/opex-admin user_id=${BACKEND_USER} -vault write auth/app-id/login/opex-chain-scan-gateway user_id=${BACKEND_USER} +vault write auth/app-id/login/chain-scan-gateway user_id=${BACKEND_USER} +vault write auth/app-id/login/opex-referral user_id=${BACKEND_USER} # ## Add secret values @@ -87,7 +89,8 @@ vault kv put secret/opex-wallet dbusername=${DB_USER} dbpassword=${DB_PASS} db_b vault kv put secret/opex-websocket dbusername=${DB_USER} dbpassword=${DB_PASS} db_backup_username=${DB_BACKUP_USERNAME} db_backup_pass=${DB_BACKUP_PASS} vault kv put secret/opex-payment dbusername=${DB_USER} dbpassword=${DB_PASS} db_backup_username=${DB_BACKUP_USERNAME} db_backup_pass=${DB_BACKUP_PASS} vandar_api_key=${VANDAR_API_KEY} vault kv put secret/opex-admin keycloak_client_secret=${KEYCLOAK_CLIENT_SECRET} -vault kv put secret/opex-chain-scan-gateway dbusername=${DB_USER} dbpassword=${DB_PASS} +vault kv put secret/chain-scan-gateway dbusername=${DB_USER} dbpassword=${DB_PASS} +vault kv put secret/opex-referral dbusername=${DB_USER} dbpassword=${DB_PASS} db_backup_username=${DB_BACKUP_USERNAME} db_backup_pass=${DB_BACKUP_PASS} # Keep alive while pidof vault >/dev/null; do diff --git a/storage/storage-app/pom.xml b/storage/storage-app/pom.xml index dfe70c472..82c4455d8 100644 --- a/storage/storage-app/pom.xml +++ b/storage/storage-app/pom.xml @@ -63,6 +63,11 @@ json-smart 2.4.7 + + io.springfox + springfox-boot-starter + 3.0.0 + diff --git a/storage/storage-app/src/main/kotlin/co/nilin/opex/storage/app/StorageApp.kt b/storage/storage-app/src/main/kotlin/co/nilin/opex/storage/app/StorageApp.kt index f4bf3fd65..3a469332f 100644 --- a/storage/storage-app/src/main/kotlin/co/nilin/opex/storage/app/StorageApp.kt +++ b/storage/storage-app/src/main/kotlin/co/nilin/opex/storage/app/StorageApp.kt @@ -4,6 +4,7 @@ import co.nilin.opex.utility.error.EnableOpexErrorHandler import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.context.annotation.ComponentScan +import springfox.documentation.swagger2.annotations.EnableSwagger2 @SpringBootApplication @ComponentScan("co.nilin.opex") diff --git a/storage/storage-app/src/main/kotlin/co/nilin/opex/storage/app/config/SecurityConfig.kt b/storage/storage-app/src/main/kotlin/co/nilin/opex/storage/app/config/SecurityConfig.kt index c5b4017c9..aa21afc16 100644 --- a/storage/storage-app/src/main/kotlin/co/nilin/opex/storage/app/config/SecurityConfig.kt +++ b/storage/storage-app/src/main/kotlin/co/nilin/opex/storage/app/config/SecurityConfig.kt @@ -20,10 +20,10 @@ class SecurityConfig(private val webClient: WebClient) { fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { http.csrf().disable() .authorizeExchange() - .pathMatchers("/hello").permitAll() .pathMatchers("/actuator/**").permitAll() .pathMatchers("/swagger-ui/**").permitAll() .pathMatchers("/swagger-resources/**").permitAll() + .pathMatchers("/v2/api-docs").permitAll() .pathMatchers("/admin/**").hasRole("SCOPE_trust", "finance-admin") .pathMatchers("/**").hasAuthority("SCOPE_trust") .anyExchange().authenticated() diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/WalletApp.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/WalletApp.kt index e4b061ee7..24f105e0b 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/WalletApp.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/WalletApp.kt @@ -4,11 +4,9 @@ import co.nilin.opex.utility.error.EnableOpexErrorHandler import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.context.annotation.ComponentScan -import springfox.documentation.swagger2.annotations.EnableSwagger2 @SpringBootApplication @ComponentScan("co.nilin.opex") -@EnableSwagger2 @EnableOpexErrorHandler class WalletApp 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 55ad09520..760e15679 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 @@ -43,8 +43,8 @@ class TransferController( @PathVariable("receiverWalletType") receiverWalletType: String, @PathVariable("receiverUuid") receiverUuid: String, @PathVariable("amount") amount: BigDecimal, - @PathVariable("description") description: String?, - @PathVariable("transferRef") transferRef: String? + @RequestParam("description") description: String?, + @RequestParam("transferRef") transferRef: String? ): TransferResult { if (senderWalletType == "cashout" || receiverWalletType == "cashout") throw OpexException(OpexError.InvalidCashOutUsage) diff --git a/wallet/wallet-app/src/main/resources/application.yml b/wallet/wallet-app/src/main/resources/application.yml index ac2eff360..55dd8c5cd 100644 --- a/wallet/wallet-app/src/main/resources/application.yml +++ b/wallet/wallet-app/src/main/resources/application.yml @@ -9,9 +9,6 @@ spring: bootstrap-servers: ${KAFKA_IP_PORT:localhost:9092} consumer: group-id: wallet - redis: - host: ${REDIS_HOST:localhost} - port: 6379 r2dbc: url: r2dbc:postgresql://${DB_IP_PORT:localhost}/opex_wallet username: ${dbusername:opex}